@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
@@ -591,10 +591,6 @@ function buildWindowScopedEquanautContext({ windowId, groups = [], tabs = [] })
591
591
  "When the user switches windows, discard this window context and bind only to the new active window.",
592
592
  "Use only the tabs, tab groups, task threads, browser-session settings, and window instructions listed in this window context.",
593
593
  "Do not infer, inspect, summarize, or act on tabs or groups from any other browser window.",
594
- "For focus, display, fullscreen, or lock questions, treat host-supplied Comet diagnostics as in-scope local context.",
595
- "Relevant local diagnostics include CDP session status, Comet launch/profile configuration, top-display window policy, macOS Accessibility window bounds, AXFullScreen state, launch agents, and Comet-related processes.",
596
- "If those diagnostics are absent, name the exact missing diagnostic surface instead of saying the issue is outside browser scope.",
597
- "Never claim that local focus or display locking is unknowable when Comet-Bridge, host shell, or macOS Accessibility diagnostics can be requested by the owning agent.",
598
594
  "Keep responses synchronized with this active window's task threads.",
599
595
  "",
600
596
  `Parent window folder id: ${key || "unknown"}`,
@@ -3660,6 +3656,31 @@ function buildSidepanelGlobalContextCatalog() {
3660
3656
  status: agentState.status || "unknown",
3661
3657
  taskThreadId: taskGroup,
3662
3658
  }));
3659
+ const descriptionRecords = Object.entries(state.entityDescriptions || {}).map(([id, entry]) => ({
3660
+ id,
3661
+ title: id,
3662
+ entityType: entry?.entityType || id.split(":")[0],
3663
+ entityId: entry?.entityId || id.split(":").slice(1).join(":"),
3664
+ description: normalizeDescriptionText(typeof entry === "string" ? entry : entry?.description),
3665
+ extensionOwned: true,
3666
+ }));
3667
+ const windowLabelRecords = Object.entries(state.settings?.windowLabels || {}).map(
3668
+ ([windowId, entry]) => ({
3669
+ id: windowId,
3670
+ windowId,
3671
+ title: typeof entry === "string" ? entry : entry?.label || windowId,
3672
+ label: typeof entry === "string" ? entry : entry?.label || "",
3673
+ source: typeof entry === "string" ? "manual" : entry?.source || "manual",
3674
+ extensionOwned: true,
3675
+ })
3676
+ );
3677
+ const lifecycleBindings = Array.from(state.bindingWindows.entries()).map(([windowId, info]) => ({
3678
+ id: info?.binding?.bindingId || windowId,
3679
+ windowId,
3680
+ title: info?.binding?.codexSessionId || info?.binding?.projectThreadId || windowId,
3681
+ status: info?.bindingStatus || "unbound",
3682
+ extensionOwned: true,
3683
+ }));
3663
3684
  return SessionLogic.buildGlobalContextCatalog({
3664
3685
  liveSessions: (state.groups || []).map(buildSidepanelCatalogRecordFromGroup),
3665
3686
  archivedSessions: state.archived || [],
@@ -3668,9 +3689,26 @@ function buildSidepanelGlobalContextCatalog() {
3668
3689
  gatewayTools: [
3669
3690
  { id: "inspect", title: "Inspect task thread", status: "available" },
3670
3691
  { id: "search", title: "Search task threads", status: "available" },
3692
+ { id: "update_description", title: "Update metadata description", status: "available" },
3693
+ { id: "bulk_update_descriptions", title: "Bulk update descriptions", status: "available" },
3694
+ { id: "rename_window", title: "Rename window label", status: "available" },
3695
+ { id: "rename_group", title: "Rename tab group", status: "available" },
3696
+ { id: "rename_tab", title: "Rename archived tab row", status: "available" },
3697
+ { id: "rename_archive", title: "Rename archived task thread", status: "available" },
3698
+ { id: "update_task_status", title: "Update task-thread status", status: "available" },
3699
+ { id: "save_snapshot", title: "Save group snapshot", status: "available" },
3700
+ {
3701
+ id: "manage_selected_item",
3702
+ title: "Manage full-screen selected items",
3703
+ status: "available",
3704
+ },
3671
3705
  { id: "dispatch_to_fleet", title: "Dispatch to fleet", status: "gateway_required" },
3672
3706
  { id: "get_fleet_status", title: "Get fleet status", status: "available" },
3673
3707
  ],
3708
+ selectedItems: getPrimeSelectedItemCatalog(),
3709
+ lifecycleBindings,
3710
+ windowLabels: windowLabelRecords,
3711
+ descriptions: descriptionRecords,
3674
3712
  slashCommands: getEquanautSlashCommands(),
3675
3713
  plugins: getEquanautPlugins(),
3676
3714
  });
@@ -3744,7 +3782,7 @@ function formatRouterCatalogForPrompt(envelope) {
3744
3782
  "[Equanaut Global Read-Only Catalog]",
3745
3783
  "Use this catalog for search and summaries across saved task threads, archived sessions, recently closed sessions, and fleet state.",
3746
3784
  "Do not mutate catalog records unless a router intent validates the target against active-window and profile policy.",
3747
- `Counts: live=${counts.live || 0}, archived=${counts.archived || 0}, recent=${counts.recent || 0}, unified=${counts.unified || 0}, fleet=${counts.fleet || 0}, gatewayTools=${counts.gatewayTools || 0}, slashCommands=${counts.slashCommands || 0}, plugins=${counts.plugins || 0}`,
3785
+ `Counts: live=${counts.live || 0}, archived=${counts.archived || 0}, recent=${counts.recent || 0}, unified=${counts.unified || 0}, fleet=${counts.fleet || 0}, gatewayTools=${counts.gatewayTools || 0}, selectedItems=${counts.selectedItems || 0}, lifecycleBindings=${counts.lifecycleBindings || 0}, windowLabels=${counts.windowLabels || 0}, descriptions=${counts.descriptions || 0}, slashCommands=${counts.slashCommands || 0}, plugins=${counts.plugins || 0}`,
3748
3786
  ];
3749
3787
  const sections = [
3750
3788
  ["Live", catalog.liveSessions || []],
@@ -3752,6 +3790,11 @@ function formatRouterCatalogForPrompt(envelope) {
3752
3790
  ["Recently Closed", catalog.recentlyClosed || []],
3753
3791
  ["Unified Threads", catalog.unifiedThreads || []],
3754
3792
  ["Fleet", catalog.fleetMembers || []],
3793
+ ["Gateway Tools", catalog.gatewayTools || []],
3794
+ ["Selected Items", catalog.selectedItems || []],
3795
+ ["Lifecycle Bindings", catalog.lifecycleBindings || []],
3796
+ ["Window Labels", catalog.windowLabels || []],
3797
+ ["Descriptions", catalog.descriptions || []],
3755
3798
  ["Slash Commands", catalog.slashCommands || []],
3756
3799
  ["Plugins", catalog.plugins || []],
3757
3800
  ];
@@ -4249,12 +4292,13 @@ function createArchiveEntryNode(entry) {
4249
4292
  const isExpanded = state.expandedGroups.has(archiveKey);
4250
4293
  const urls = entry.urls || [];
4251
4294
  const archiveDescription = normalizeRecordDescription(entry);
4295
+ const status = fsWorkflowStatus(entry);
4252
4296
 
4253
4297
  const stateClasses = [
4254
4298
  entry.starred ? "starred" : "",
4255
4299
  entry.locked ? "locked" : "",
4256
4300
  entry.pinned ? "pinned" : "",
4257
- entry.status === "pending" ? "status-pending" : "",
4301
+ status === "pending" ? "status-pending" : "",
4258
4302
  ]
4259
4303
  .filter(Boolean)
4260
4304
  .join(" ");
@@ -4329,7 +4373,7 @@ function createArchiveEntryNode(entry) {
4329
4373
  indicators.push(el("span", { className: "archive-indicator", title: "Locked" }, "🔒"));
4330
4374
  if (entry.pinned)
4331
4375
  indicators.push(el("span", { className: "archive-indicator", title: "Pinned" }, "📌"));
4332
- if (entry.status === "pending")
4376
+ if (status === "pending")
4333
4377
  indicators.push(el("span", { className: "archive-indicator status-badge pending" }, "Pending"));
4334
4378
  if (archiveDescription)
4335
4379
  indicators.push(
@@ -5075,9 +5119,9 @@ function showArchiveContextMenu(e, entry) {
5075
5119
  action: () => updateStatusAction(entry.taskThreadId, "pending"),
5076
5120
  },
5077
5121
  {
5078
- label: "Mark as archived",
5079
- icon: "📦",
5080
- action: () => updateStatusAction(entry.taskThreadId, "archived"),
5122
+ label: "Mark as done",
5123
+ icon: "",
5124
+ action: () => updateStatusAction(entry.taskThreadId, "done"),
5081
5125
  },
5082
5126
  {
5083
5127
  label: entry.pinned ? "Unpin" : "Pin to top",
@@ -5975,6 +6019,213 @@ function isFullscreenMode() {
5975
6019
  );
5976
6020
  }
5977
6021
 
6022
+ // Figma node 5:27 "Header Menu" mapped into the full-page extension shell.
6023
+ const FS_HOME_ROUTE = "home";
6024
+ const FS_DASHBOARD_BASE_URL = "http://localhost:3847/";
6025
+ const FS_NAVIGATION_ITEMS = Object.freeze([
6026
+ {
6027
+ route: "home",
6028
+ label: "Home",
6029
+ icon: "⌂",
6030
+ kind: "internal",
6031
+ description: "Return to the full-page session manager home.",
6032
+ },
6033
+ {
6034
+ route: "capabilities",
6035
+ label: "Capabilities",
6036
+ icon: "⚙",
6037
+ kind: "dashboard",
6038
+ dashboardHash: "capabilities",
6039
+ description: "Open the unified capabilities dashboard.",
6040
+ },
6041
+ {
6042
+ route: "live-browser",
6043
+ label: "Live Browser",
6044
+ icon: "⚡",
6045
+ kind: "dashboard",
6046
+ dashboardHash: "live",
6047
+ description: "Open the live browser dashboard.",
6048
+ },
6049
+ {
6050
+ route: "sessions",
6051
+ label: "Sessions",
6052
+ icon: "📡",
6053
+ kind: "dashboard",
6054
+ dashboardHash: "sessions",
6055
+ description: "Open the unified sessions dashboard.",
6056
+ },
6057
+ {
6058
+ route: "dispatch",
6059
+ label: "Dispatch",
6060
+ icon: "↗",
6061
+ kind: "dashboard",
6062
+ dashboardHash: "dispatch",
6063
+ description: "Open the dispatch view.",
6064
+ },
6065
+ {
6066
+ route: "test-suite",
6067
+ label: "Test Suite",
6068
+ icon: "⌬",
6069
+ kind: "dashboard",
6070
+ dashboardHash: "tests",
6071
+ description: "Open the test suite dashboard.",
6072
+ },
6073
+ {
6074
+ route: "window-map",
6075
+ label: "Window Map",
6076
+ icon: "▦",
6077
+ kind: "dashboard",
6078
+ dashboardHash: "mapping",
6079
+ description: "Open the managed window map.",
6080
+ },
6081
+ {
6082
+ route: "task-threads",
6083
+ label: "Task Threads",
6084
+ icon: "▤",
6085
+ kind: "dashboard",
6086
+ dashboardHash: "threads",
6087
+ description: "Open the unified taskthreads dashboard.",
6088
+ },
6089
+ {
6090
+ route: "skill-reference",
6091
+ label: "Skill Reference",
6092
+ icon: "☰",
6093
+ kind: "docs",
6094
+ href: "https://github.com/EQUAStart/equa-comet-browser-control/blob/main/docs/SKILL-ULTIMATE.md",
6095
+ description: "Open the skill reference documentation.",
6096
+ },
6097
+ ]);
6098
+
6099
+ function fsGetNavigationItem(route) {
6100
+ return FS_NAVIGATION_ITEMS.find((item) => item.route === route);
6101
+ }
6102
+
6103
+ function fsReadFullscreenRoute() {
6104
+ const params = new URLSearchParams(window.location.search);
6105
+ const route = params.get("route") || window.location.hash.replace("#", "");
6106
+ return fsGetNavigationItem(route) ? route : FS_HOME_ROUTE;
6107
+ }
6108
+
6109
+ function fsBuildNavigationUrl(item) {
6110
+ if (item.kind === "dashboard") {
6111
+ const url = new URL(FS_DASHBOARD_BASE_URL);
6112
+ url.searchParams.set("returnUrl", fsBuildNavigationUrl(fsGetNavigationItem(FS_HOME_ROUTE)));
6113
+ url.hash = item.dashboardHash;
6114
+ return url.toString();
6115
+ }
6116
+ if (item.kind === "docs") return item.href;
6117
+
6118
+ const url = new URL(window.location.href);
6119
+ url.searchParams.set("mode", "fullscreen");
6120
+ url.searchParams.set("route", item.route);
6121
+ url.hash = "";
6122
+ return url.toString();
6123
+ }
6124
+
6125
+ function fsOpenNavigationUrl(url) {
6126
+ if (globalThis.chrome?.tabs?.create) {
6127
+ globalThis.chrome.tabs.create({ url });
6128
+ return;
6129
+ }
6130
+ window.open(url, "_blank", "noopener");
6131
+ }
6132
+
6133
+ function fsScrollAndFocus(element) {
6134
+ if (!element) return;
6135
+ element.scrollIntoView({ block: "start", behavior: "smooth" });
6136
+ if (!element.hasAttribute("tabindex")) element.setAttribute("tabindex", "-1");
6137
+ element.focus({ preventScroll: true });
6138
+ }
6139
+
6140
+ function fsSetFullscreenRoute(route) {
6141
+ const item = fsGetNavigationItem(route) || fsGetNavigationItem(FS_HOME_ROUTE);
6142
+ if (!item || item.kind !== "internal") return;
6143
+
6144
+ const url = new URL(window.location.href);
6145
+ url.searchParams.set("mode", "fullscreen");
6146
+ url.searchParams.set("route", item.route);
6147
+ url.hash = "";
6148
+ window.history.replaceState({ route: item.route }, "", url);
6149
+ fsApplyFullscreenRoute(item.route);
6150
+ }
6151
+
6152
+ function fsApplyFullscreenRoute(route = fsReadFullscreenRoute()) {
6153
+ const item = fsGetNavigationItem(route) || fsGetNavigationItem(FS_HOME_ROUTE);
6154
+ if (!item || item.kind !== "internal") return;
6155
+
6156
+ document.body.dataset.fullscreenRoute = item.route;
6157
+
6158
+ if (item.route === "home") {
6159
+ const scroll = document.getElementById("fs-main-scroll");
6160
+ const search = document.getElementById("fs-search-input");
6161
+ if (scroll) scroll.scrollTo({ top: 0, behavior: "smooth" });
6162
+ if (search) search.focus({ preventScroll: true });
6163
+ return;
6164
+ }
6165
+
6166
+ if (item.route === "sessions") {
6167
+ fsScrollAndFocus(document.getElementById("fs-groups-list"));
6168
+ return;
6169
+ }
6170
+
6171
+ if (item.route === "task-threads") {
6172
+ fsScrollAndFocus(document.getElementById("fs-tasks-panel"));
6173
+ }
6174
+ }
6175
+
6176
+ function fsNavigateFromMenu(item) {
6177
+ if (!item) return;
6178
+ if (item.kind === "internal") {
6179
+ fsSetFullscreenRoute(item.route);
6180
+ return;
6181
+ }
6182
+ fsOpenNavigationUrl(fsBuildNavigationUrl(item));
6183
+ }
6184
+
6185
+ function fsInitNavigationMenu() {
6186
+ const navBtn = document.getElementById("fs-btn-navigation");
6187
+ if (!navBtn) return;
6188
+
6189
+ navBtn.addEventListener("click", (event) => {
6190
+ event.stopPropagation();
6191
+ const activeRoute = fsReadFullscreenRoute();
6192
+ navBtn.setAttribute("aria-expanded", "true");
6193
+
6194
+ showContextMenu(
6195
+ navBtn,
6196
+ FS_NAVIGATION_ITEMS.map((item, index) => ({
6197
+ icon: item.icon,
6198
+ label: item.label,
6199
+ active: item.kind === "internal" && item.route === activeRoute,
6200
+ action: () => {
6201
+ navBtn.setAttribute("aria-expanded", "false");
6202
+ fsNavigateFromMenu(item);
6203
+ },
6204
+ divider: false,
6205
+ }))
6206
+ );
6207
+
6208
+ requestAnimationFrame(() => {
6209
+ const menu = document.getElementById("fs-context-menu");
6210
+ if (!menu) return;
6211
+ menu.setAttribute("aria-label", "Full-screen navigation");
6212
+ menu.querySelectorAll(".fs-context-menu-item").forEach((el, index) => {
6213
+ const item = FS_NAVIGATION_ITEMS[index];
6214
+ if (!item) return;
6215
+ el.dataset.route = item.route;
6216
+ el.title = item.description || item.label;
6217
+ if (index === 0) el.classList.add("fs-nav-home-link");
6218
+ });
6219
+ });
6220
+
6221
+ const resetExpanded = () => navBtn.setAttribute("aria-expanded", "false");
6222
+ setTimeout(() => {
6223
+ document.addEventListener("click", resetExpanded, { once: true });
6224
+ document.addEventListener("keydown", resetExpanded, { once: true });
6225
+ }, 0);
6226
+ });
6227
+ }
6228
+
5978
6229
  // ─── Reusable Components (T006-T008) ────────────────────────────────────────
5979
6230
 
5980
6231
  // T007: Toast notification
@@ -6147,7 +6398,7 @@ function fsRenderSidebar(groups) {
6147
6398
  totalTabs += tabCount;
6148
6399
  const groupId = group.taskThreadId || group.id;
6149
6400
  const isExpanded = fsSidebarExpanded.has(groupId);
6150
- const status = group.status || "archived";
6401
+ const status = fsWorkflowStatus(group);
6151
6402
 
6152
6403
  const li = document.createElement("li");
6153
6404
  li.className = "fs-sidebar-item";
@@ -6333,7 +6584,7 @@ function fsSidebarContextMenu(e, groupId, group) {
6333
6584
  const isLocked = fsLockedGroups.has(groupId);
6334
6585
  const isStarred = group.starred;
6335
6586
  const isPinned = group.pinned;
6336
- const status = group.status || "archived";
6587
+ const status = fsWorkflowStatus(group);
6337
6588
 
6338
6589
  // Create a temporary trigger at mouse position
6339
6590
  const trigger = document.createElement("div");
@@ -6398,12 +6649,6 @@ function fsSidebarContextMenu(e, groupId, group) {
6398
6649
  action: () => fsUpdateStatus(groupId, "done"),
6399
6650
  active: status === "done",
6400
6651
  },
6401
- {
6402
- icon: "\uD83D\uDCE6",
6403
- label: "Mark as archived",
6404
- action: () => fsUpdateStatus(groupId, "archived"),
6405
- active: status === "archived",
6406
- },
6407
6652
  { divider: true },
6408
6653
  { icon: "\uD83D\uDD17", label: "Copy URLs", action: () => fsShareAsUrl(group) },
6409
6654
  { icon: "\uD83D\uDCCB", label: "Export to text", action: () => fsExportToText(group) },
@@ -6461,8 +6706,9 @@ function fsRenderGroups(groups) {
6461
6706
  card.className = "fs-group-card";
6462
6707
  if (fsSelectedGroups.has(String(groupId))) card.classList.add("selected");
6463
6708
  if (fsIsGroupStale && fsIsGroupStale(group)) card.classList.add("stale");
6464
- if (group.status === "pending") card.classList.add("status-pending");
6465
- if (group.status === "done") card.classList.add("status-done");
6709
+ const status = fsWorkflowStatus(group);
6710
+ if (status === "pending") card.classList.add("status-pending");
6711
+ if (status === "done") card.classList.add("status-done");
6466
6712
  card.id = "fs-group-" + groupId;
6467
6713
 
6468
6714
  const tabs = group.urls || group.tabs || [];
@@ -6645,6 +6891,10 @@ async function fsSaveLockedGroups() {
6645
6891
  await chrome.storage.local.set({ lockedGroups: [...fsLockedGroups] });
6646
6892
  }
6647
6893
 
6894
+ function fsWorkflowStatus(group) {
6895
+ return SessionLogic.normalizeArchiveWorkflowStatus(group?.status);
6896
+ }
6897
+
6648
6898
  // ─── Full-Screen Data Loading ───────────────────────────────────────────────
6649
6899
 
6650
6900
  async function fsLoadAndRender() {
@@ -6960,14 +7210,9 @@ async function fsRenderTasksPanel(groups) {
6960
7210
  const agents = await fsFetchAgentRegistryForPanel();
6961
7211
  fsTaskthreadsState.agentRegistry = agents;
6962
7212
 
6963
- const pending = fsSortGroups(
6964
- groups.filter((g) => g.status === "pending"),
6965
- fsTaskthreadsState.sortPending
6966
- );
6967
- const done = fsSortGroups(
6968
- groups.filter((g) => g.status === "done"),
6969
- fsTaskthreadsState.sortDone
6970
- );
7213
+ const taskGroups = SessionLogic.fsPartitionTaskthreadGroups(groups);
7214
+ const pending = fsSortGroups(taskGroups.pending, fsTaskthreadsState.sortPending);
7215
+ const done = fsSortGroups(taskGroups.done, fsTaskthreadsState.sortDone);
6971
7216
 
6972
7217
  if (countEl) countEl.textContent = String(pending.length + done.length);
6973
7218
  if (pendingCountEl) pendingCountEl.textContent = String(pending.length);
@@ -7263,7 +7508,8 @@ function fsUpdateBulkBar() {
7263
7508
  );
7264
7509
  fsAddBulkButton("Restore", () => fsRestoreGroup(groupId));
7265
7510
  fsAddBulkButton("Copy URLs", () => fsShareAsUrl(group));
7266
- fsAddBulkButton("Mark as archived", () => fsUpdateStatus(groupId, "archived"));
7511
+ fsAddBulkButton("Mark as pending", () => fsUpdateStatus(groupId, "pending"));
7512
+ fsAddBulkButton("Mark as done", () => fsUpdateStatus(groupId, "done"));
7267
7513
  fsAddBulkButton("Move to trash", () => fsDeleteGroup(groupId), { danger: true });
7268
7514
  } else if (tabCount === 1 && groupCount === 0 && selectedTabs[0]) {
7269
7515
  const selected = selectedTabs[0];
@@ -7278,13 +7524,13 @@ function fsUpdateBulkBar() {
7278
7524
  if (groupCount) {
7279
7525
  fsAddBulkButton("Restore groups", fsBulkRestoreGroups);
7280
7526
  fsAddBulkButton("Copy URLs", fsBulkCopyGroups);
7281
- fsAddBulkButton("Mark as archived", () => fsBulkUpdateGroupStatus("archived"));
7527
+ fsAddBulkButton("Mark as pending", () => fsBulkUpdateGroupStatus("pending"));
7528
+ fsAddBulkButton("Mark as done", () => fsBulkUpdateGroupStatus("done"));
7282
7529
  fsAddBulkButton("Move groups to trash", fsBulkTrashGroups, { danger: true });
7283
7530
  }
7284
7531
  if (tabCount) {
7285
7532
  fsAddBulkButton("Restore tabs", fsBulkRestore);
7286
7533
  fsAddBulkButton("Copy URLs", fsBulkCopy);
7287
- fsAddBulkButton("Mark groups archived", fsBulkArchive);
7288
7534
  fsAddBulkButton("Move tabs to trash", fsBulkTrash, { danger: true });
7289
7535
  }
7290
7536
  }
@@ -7358,6 +7604,301 @@ function fsResolveSelectedTabs() {
7358
7604
  return SessionLogic.fsResolveSelectedTabs(fsSelectedTabs, fsCachedGroups);
7359
7605
  }
7360
7606
 
7607
+ function getPrimeSelectedItemCatalog() {
7608
+ const groups = fsResolveSelectedGroups().map((group) => ({
7609
+ id: String(group.taskThreadId || group.id),
7610
+ entityType: "archive",
7611
+ taskThreadId: String(group.taskThreadId || group.id),
7612
+ title: group.sessionName || group.title || "Untitled",
7613
+ description: normalizeRecordDescription(group),
7614
+ status: group.status || "unknown",
7615
+ sourceFamily: "selected",
7616
+ extensionOwned: true,
7617
+ }));
7618
+ const tabs = fsResolveSelectedTabs().map((selected) => ({
7619
+ id: `${selected.groupId}-${selected.tabIdx}`,
7620
+ entityType: "archive-tab",
7621
+ taskThreadId: String(selected.groupId),
7622
+ tabIndex: selected.tabIdx,
7623
+ title: selected.tab?.title || selected.tab?.url || "Untitled",
7624
+ url: selected.tab?.url || "",
7625
+ description: normalizeRecordDescription(selected.tab),
7626
+ sourceFamily: "selected",
7627
+ extensionOwned: true,
7628
+ }));
7629
+ return [...groups, ...tabs];
7630
+ }
7631
+
7632
+ function primeCommandText(command, ...keys) {
7633
+ for (const key of keys) {
7634
+ if (command?.[key] !== undefined && command?.[key] !== null) {
7635
+ return normalizeDescriptionText(command[key]);
7636
+ }
7637
+ }
7638
+ return "";
7639
+ }
7640
+
7641
+ function primeTargetTaskThreadId(target) {
7642
+ return String(target?.taskThreadId || target?.threadId || target?.entityId || target?.id || "");
7643
+ }
7644
+
7645
+ function primeTargetTabIndex(target) {
7646
+ const index = Number(target?.tabIndex ?? target?.tabIdx ?? target?.index);
7647
+ return Number.isInteger(index) && index >= 0 ? index : -1;
7648
+ }
7649
+
7650
+ function buildPrimeValidationScope(command) {
7651
+ if (command?.activeWindowScope) return command.activeWindowScope;
7652
+ const windowScope = {
7653
+ windowId: state.activeWindowId || command?.target?.windowId || "unknown",
7654
+ };
7655
+ return buildActiveWindowRouterScope(
7656
+ windowScope,
7657
+ getWindowEquanautInstructions(windowScope.windowId)
7658
+ );
7659
+ }
7660
+
7661
+ function validatePrimeCommand(command) {
7662
+ const payload = {
7663
+ ...command,
7664
+ actorRole: command?.actorRole || "equanaut_prime",
7665
+ activeWindowScope: buildPrimeValidationScope(command),
7666
+ };
7667
+ return SessionLogic.validateRouterIntent(payload);
7668
+ }
7669
+
7670
+ async function loadPrimeArchiveGroup(taskThreadId) {
7671
+ const groups = await sendMessage("getArchivedGroups");
7672
+ return (groups || []).find((group) => String(group.taskThreadId || group.id) === taskThreadId);
7673
+ }
7674
+
7675
+ async function updatePrimeArchiveTabMetadata(target, updates) {
7676
+ const taskThreadId = primeTargetTaskThreadId(target);
7677
+ const tabIndex = primeTargetTabIndex(target);
7678
+ if (!taskThreadId) throw new Error("taskThreadId is required for archive tab updates");
7679
+ if (tabIndex < 0) throw new Error("tabIndex is required for archive tab updates");
7680
+ const group = await loadPrimeArchiveGroup(taskThreadId);
7681
+ if (!group) throw new Error("Archive group not found: " + taskThreadId);
7682
+ const urls = [...(group.urls || group.tabs || [])];
7683
+ if (!urls[tabIndex]) throw new Error("Archive tab not found: " + tabIndex);
7684
+ urls[tabIndex] = { ...urls[tabIndex], ...updates };
7685
+ await sendMessage("updateArchiveEntry", { taskThreadId, updates: { urls } });
7686
+ return { taskThreadId, tabIndex, updates };
7687
+ }
7688
+
7689
+ async function executePrimeDescriptionUpdate(command) {
7690
+ const target = command.target || {};
7691
+ const entityType = String(target.entityType || target.type || "");
7692
+ const description = primeCommandText(command, "description", "value", "text");
7693
+ if (entityType === "archive-tab" || target.tabIndex !== undefined) {
7694
+ return await updatePrimeArchiveTabMetadata(target, { description });
7695
+ }
7696
+ if (entityType === "archive" || target.sourceFamily === "archived") {
7697
+ const taskThreadId = primeTargetTaskThreadId(target);
7698
+ if (!taskThreadId) throw new Error("taskThreadId is required for archive descriptions");
7699
+ await sendMessage("updateArchiveDescription", { taskThreadId, description });
7700
+ return { taskThreadId, description };
7701
+ }
7702
+ const entityId =
7703
+ target.entityId || target.id || target.windowId || target.groupId || target.tabId;
7704
+ if (!entityType || entityId === undefined || entityId === null) {
7705
+ throw new Error("entityType and entityId are required for description updates");
7706
+ }
7707
+ await persistEntityDescription(entityType, entityId, description);
7708
+ return { entityType, entityId: String(entityId), description };
7709
+ }
7710
+
7711
+ async function executePrimeRename(command) {
7712
+ const target = command.target || {};
7713
+ const intent = String(command.intent || "");
7714
+ const entityType = String(target.entityType || target.type || "");
7715
+ const title = primeCommandText(command, "newTitle", "title", "label", "name");
7716
+ if (!title.trim()) throw new Error("New title or label is required");
7717
+
7718
+ if (intent === "rename_window" || entityType === "window" || entityType === "window-label") {
7719
+ const windowId = target.windowId || target.entityId || target.id;
7720
+ if (windowId === undefined || windowId === null) throw new Error("windowId is required");
7721
+ await persistWindowLabel(windowId, title, "manual");
7722
+ return { windowId: String(windowId), label: title };
7723
+ }
7724
+
7725
+ if (intent === "rename_archive" || entityType === "archive") {
7726
+ const taskThreadId = primeTargetTaskThreadId(target);
7727
+ if (!taskThreadId) throw new Error("taskThreadId is required for archive rename");
7728
+ await sendMessage("renameArchived", { taskThreadId, newTitle: title });
7729
+ return { taskThreadId, title };
7730
+ }
7731
+
7732
+ if (intent === "rename_tab" || entityType === "archive-tab") {
7733
+ if (target.sourceFamily === "live" && entityType !== "archive-tab") {
7734
+ throw new Error("Live tab titles are browser-owned; update the tab description instead");
7735
+ }
7736
+ return await updatePrimeArchiveTabMetadata(target, { title });
7737
+ }
7738
+
7739
+ const groupId = target.groupId || target.entityId || target.id;
7740
+ if (groupId === undefined || groupId === null) throw new Error("groupId is required");
7741
+ if (!chrome.tabGroups?.update) throw new Error("tabGroups API unavailable");
7742
+ await chrome.tabGroups.update(Number(groupId), { title });
7743
+ return { groupId: String(groupId), title };
7744
+ }
7745
+
7746
+ async function executePrimeTaskStatus(command) {
7747
+ const target = command.target || {};
7748
+ const taskThreadId = primeTargetTaskThreadId(target);
7749
+ const status = primeCommandText(command, "status", "taskStatus", "value");
7750
+ if (!taskThreadId) throw new Error("taskThreadId is required for status update");
7751
+ if (!status) throw new Error("status is required");
7752
+ await sendMessage("updateArchiveStatus", { taskThreadId, status });
7753
+ return { taskThreadId, status };
7754
+ }
7755
+
7756
+ async function executePrimeSaveSnapshot(command) {
7757
+ const target = command.target || {};
7758
+ const groupId = target.groupId || target.entityId || target.id;
7759
+ if (groupId === undefined || groupId === null) throw new Error("groupId is required");
7760
+ await saveGroupSnapshotAction(Number(groupId), target.title || command.title || "");
7761
+ return { groupId: String(groupId), saved: true };
7762
+ }
7763
+
7764
+ async function executePrimeSelectedItemCommand(command) {
7765
+ const action = String(command.action || command.selectedAction || "list").toLowerCase();
7766
+ const selected = getPrimeSelectedItemCatalog();
7767
+ if (action === "clear_selection" || action === "clear") {
7768
+ fsClearSelections();
7769
+ return { selectedItems: [], cleared: true };
7770
+ }
7771
+ if (action === "list" || selected.length === 0) {
7772
+ return { selectedItems: selected };
7773
+ }
7774
+
7775
+ const results = [];
7776
+ for (const item of selected) {
7777
+ const target = { ...item, sourceFamily: "selected", extensionOwned: true };
7778
+ if (action === "update_description" || action === "describe") {
7779
+ results.push(
7780
+ await executePrimeRouterIntent({
7781
+ ...command,
7782
+ intent: "update_description",
7783
+ target,
7784
+ description: command.description,
7785
+ })
7786
+ );
7787
+ } else if (action === "rename") {
7788
+ results.push(
7789
+ await executePrimeRouterIntent({
7790
+ ...command,
7791
+ intent: item.entityType === "archive-tab" ? "rename_tab" : "rename_archive",
7792
+ target,
7793
+ })
7794
+ );
7795
+ } else if (action === "update_task_status" || action === "status") {
7796
+ if (item.entityType === "archive") {
7797
+ results.push(
7798
+ await executePrimeRouterIntent({
7799
+ ...command,
7800
+ intent: "update_task_status",
7801
+ target,
7802
+ })
7803
+ );
7804
+ }
7805
+ } else {
7806
+ throw new Error("Unsupported selected-item action: " + action);
7807
+ }
7808
+ }
7809
+ return { selectedItems: selected, results };
7810
+ }
7811
+
7812
+ async function executePrimeRouterIntent(command = {}) {
7813
+ if (command.intent === "bulk_update_descriptions") {
7814
+ const targets = Array.isArray(command.targets) ? command.targets : [];
7815
+ const results = [];
7816
+ for (const item of targets) {
7817
+ const target = item.target || item;
7818
+ results.push(
7819
+ await executePrimeRouterIntent({
7820
+ ...command,
7821
+ intent: "update_description",
7822
+ target,
7823
+ description: item.description !== undefined ? item.description : command.description,
7824
+ })
7825
+ );
7826
+ }
7827
+ return {
7828
+ intent: "bulk_update_descriptions",
7829
+ success: results.every((result) => result.success),
7830
+ results,
7831
+ audit: {
7832
+ policyTier: "prime",
7833
+ activeWindowId: state.activeWindowId || "unknown",
7834
+ createdAt: new Date().toISOString(),
7835
+ },
7836
+ };
7837
+ }
7838
+
7839
+ const validation = validatePrimeCommand(command);
7840
+ if (!validation.allowed) {
7841
+ return {
7842
+ intent: command.intent || "unknown",
7843
+ target: command.target || {},
7844
+ success: false,
7845
+ allowed: false,
7846
+ denial: validation.denial,
7847
+ audit: validation.audit,
7848
+ };
7849
+ }
7850
+
7851
+ try {
7852
+ let result;
7853
+ switch (command.intent) {
7854
+ case "update_description":
7855
+ result = await executePrimeDescriptionUpdate(command);
7856
+ break;
7857
+ case "rename_window":
7858
+ case "rename_group":
7859
+ case "rename_tab":
7860
+ case "rename_archive":
7861
+ result = await executePrimeRename(command);
7862
+ break;
7863
+ case "update_task_status":
7864
+ result = await executePrimeTaskStatus(command);
7865
+ break;
7866
+ case "save_snapshot":
7867
+ result = await executePrimeSaveSnapshot(command);
7868
+ break;
7869
+ case "manage_selected_item":
7870
+ result = await executePrimeSelectedItemCommand(command);
7871
+ break;
7872
+ default:
7873
+ throw new Error("Unsupported Prime command intent: " + command.intent);
7874
+ }
7875
+ await Promise.allSettled([
7876
+ fetchAndRenderLiveTree(),
7877
+ fetchAndRenderArchived(),
7878
+ fetchAndRenderRecent(),
7879
+ ]);
7880
+ return {
7881
+ intent: command.intent,
7882
+ target: command.target || {},
7883
+ scope: validation.scope,
7884
+ success: true,
7885
+ allowed: true,
7886
+ result,
7887
+ audit: validation.audit,
7888
+ };
7889
+ } catch (err) {
7890
+ return {
7891
+ intent: command.intent || "unknown",
7892
+ target: command.target || {},
7893
+ scope: validation.scope,
7894
+ success: false,
7895
+ allowed: true,
7896
+ error: err?.message || String(err),
7897
+ audit: validation.audit,
7898
+ };
7899
+ }
7900
+ }
7901
+
7361
7902
  // T038: Bulk restore — open selected tabs in a new window
7362
7903
  async function fsBulkRestore() {
7363
7904
  const selected = fsResolveSelectedTabs();
@@ -7394,23 +7935,6 @@ async function fsBulkCopy() {
7394
7935
  }
7395
7936
  }
7396
7937
 
7397
- // T038: Bulk archive — mark selected tabs' groups as archived
7398
- async function fsBulkArchive() {
7399
- const selected = fsResolveSelectedTabs();
7400
- if (!selected.length) return;
7401
- const groupIds = [...new Set(selected.map((s) => s.groupId))];
7402
- for (const gid of groupIds) {
7403
- try {
7404
- await sendMessage("updateArchiveStatus", { taskThreadId: gid, status: "archived" });
7405
- } catch {
7406
- // continue
7407
- }
7408
- }
7409
- showToast(groupIds.length + " group" + (groupIds.length !== 1 ? "s" : "") + " archived");
7410
- fsSelectedTabs.clear();
7411
- await fsLoadAndRender();
7412
- }
7413
-
7414
7938
  // T038: Bulk trash — remove selected tabs from their groups
7415
7939
  async function fsBulkTrash() {
7416
7940
  const selected = fsResolveSelectedTabs();
@@ -7594,7 +8118,7 @@ function fsShowGroupContextMenu(trigger, groupId, group) {
7594
8118
  const isLocked = fsLockedGroups.has(groupId);
7595
8119
  const isStarred = group.starred;
7596
8120
  const isPinned = group.pinned;
7597
- const status = group.status || "archived";
8121
+ const status = fsWorkflowStatus(group);
7598
8122
  const color = group.color || "grey";
7599
8123
 
7600
8124
  const COLORS = ["grey", "blue", "red", "yellow", "green", "pink", "purple", "cyan", "orange"];
@@ -7664,12 +8188,6 @@ function fsShowGroupContextMenu(trigger, groupId, group) {
7664
8188
  action: () => fsUpdateStatus(groupId, "done"),
7665
8189
  active: status === "done",
7666
8190
  },
7667
- {
7668
- icon: "📦",
7669
- label: "Mark as archived",
7670
- action: () => fsUpdateStatus(groupId, "archived"),
7671
- active: status === "archived",
7672
- },
7673
8191
  { divider: true },
7674
8192
  // Clipboard / sharing
7675
8193
  { icon: "🔗", label: "Copy URLs to clipboard", action: () => fsShareAsUrl(group) },
@@ -7863,7 +8381,7 @@ let fsHideArchived = false;
7863
8381
 
7864
8382
  // Advanced filter bar state
7865
8383
  const fsAdvancedFilter = {
7866
- status: "all", // "all" | "archived" | "pending" | "done"
8384
+ status: "all", // "all" | "pending" | "done"
7867
8385
  colors: [], // [] = all, ["blue","red"] = only those
7868
8386
  starred: false,
7869
8387
  locked: false,
@@ -7886,6 +8404,11 @@ async function fsLoadFilterState() {
7886
8404
  if (stored.advancedFilter) {
7887
8405
  Object.assign(fsAdvancedFilter, stored.advancedFilter);
7888
8406
  if (!Array.isArray(fsAdvancedFilter.colors)) fsAdvancedFilter.colors = [];
8407
+ if (fsAdvancedFilter.status === "archived" || fsAdvancedFilter.status === "saved") {
8408
+ fsAdvancedFilter.status = "pending";
8409
+ } else if (!["all", "pending", "done"].includes(fsAdvancedFilter.status)) {
8410
+ fsAdvancedFilter.status = "all";
8411
+ }
7889
8412
  }
7890
8413
  } catch {
7891
8414
  // ignore
@@ -8419,7 +8942,6 @@ function fsGetViewLabel() {
8419
8942
  if (fsAdvancedFilter.stale) return "Stale";
8420
8943
  if (fsAdvancedFilter.status === "pending") return "Pending Tasks";
8421
8944
  if (fsAdvancedFilter.status === "done") return "Completed";
8422
- if (fsAdvancedFilter.status === "archived") return "Archived";
8423
8945
  if (fsAdvancedFilter.colors.length === 1) {
8424
8946
  const c = fsAdvancedFilter.colors[0];
8425
8947
  return c.charAt(0).toUpperCase() + c.slice(1) + " Groups";
@@ -8655,8 +9177,9 @@ function fsCreateGroupCard(group) {
8655
9177
  card.className = "fs-group-card";
8656
9178
  if (fsSelectedGroups.has(String(groupId))) card.classList.add("selected");
8657
9179
  if (typeof fsIsGroupStale === "function" && fsIsGroupStale(group)) card.classList.add("stale");
8658
- if (group.status === "pending") card.classList.add("status-pending");
8659
- if (group.status === "done") card.classList.add("status-done");
9180
+ const status = fsWorkflowStatus(group);
9181
+ if (status === "pending") card.classList.add("status-pending");
9182
+ if (status === "done") card.classList.add("status-done");
8660
9183
  card.id = "fs-group-" + groupId;
8661
9184
 
8662
9185
  const tabs = group.urls || group.tabs || [];
@@ -8983,7 +9506,7 @@ async function fsImportFromText(text) {
8983
9506
  urls: urls.map((u) => ({ url: u, title: u })),
8984
9507
  archivedAt: new Date().toISOString(),
8985
9508
  restoredAt: null,
8986
- status: "archived",
9509
+ status: "pending",
8987
9510
  };
8988
9511
  // Save via the archive system — prepend to existing
8989
9512
  const current = Array.isArray(archive) ? archive : [];
@@ -9091,7 +9614,7 @@ async function fsImportBookmarkFolder(folderNode) {
9091
9614
  urls,
9092
9615
  archivedAt: new Date().toISOString(),
9093
9616
  restoredAt: null,
9094
- status: "archived",
9617
+ status: "pending",
9095
9618
  };
9096
9619
  const current = Array.isArray(archive) ? archive : [];
9097
9620
  current.unshift(entry);
@@ -9672,7 +10195,7 @@ function fsIsGroupStale(group) {
9672
10195
  return Date.now() - new Date(ts).getTime() > thresholdMs;
9673
10196
  }
9674
10197
 
9675
- async function fsArchiveAllStale() {
10198
+ async function fsMarkAllStaleDone() {
9676
10199
  const staleGroups = fsCachedGroups.filter(fsIsGroupStale);
9677
10200
  if (!staleGroups.length) {
9678
10201
  showToast("No stale sessions found");
@@ -9681,13 +10204,13 @@ async function fsArchiveAllStale() {
9681
10204
  for (const group of staleGroups) {
9682
10205
  const gid = group.taskThreadId || group.id;
9683
10206
  try {
9684
- await sendMessage("updateArchiveStatus", { taskThreadId: gid, status: "archived" });
10207
+ await sendMessage("updateArchiveStatus", { taskThreadId: gid, status: "done" });
9685
10208
  } catch {
9686
10209
  // continue
9687
10210
  }
9688
10211
  }
9689
10212
  showToast(
9690
- staleGroups.length + " stale session" + (staleGroups.length !== 1 ? "s" : "") + " archived"
10213
+ staleGroups.length + " stale session" + (staleGroups.length !== 1 ? "s" : "") + " marked done"
9691
10214
  );
9692
10215
  await fsLoadAndRender();
9693
10216
  }
@@ -9977,11 +10500,8 @@ function fsInitTopBarActions() {
9977
10500
  { label: "Tab Group Templates", action: fsShowTemplatesModal },
9978
10501
  { label: "Rename current window", action: fsRenameCurrentWindowLabel },
9979
10502
  { divider: true },
10503
+ { label: "Mark stale sessions done", action: fsMarkAllStaleDone },
9980
10504
  { label: "Export workspace (JSON)", action: fsExportWorkspace },
9981
- {
9982
- label: "Archive stale sessions",
9983
- action: fsArchiveAllStale,
9984
- },
9985
10505
  ]);
9986
10506
  });
9987
10507
  }
@@ -10027,8 +10547,10 @@ async function initFullscreenUI() {
10027
10547
  fsInitCommandPalette();
10028
10548
  fsInitKeyboardNav();
10029
10549
  fsInitDragAndDrop();
10550
+ fsInitNavigationMenu();
10030
10551
  fsInitTopBarActions();
10031
10552
  await fsLoadAndRender();
10553
+ fsApplyFullscreenRoute();
10032
10554
  fsStartAgentPoll();
10033
10555
  await fsRenderTimeline();
10034
10556
  }
@@ -10306,5 +10828,13 @@ async function init() {
10306
10828
  }
10307
10829
  }
10308
10830
 
10831
+ if (typeof globalThis !== "undefined") {
10832
+ globalThis.EquanautPrimeControlPlane = {
10833
+ executePrimeRouterIntent,
10834
+ getPrimeSelectedItemCatalog,
10835
+ buildSidepanelGlobalContextCatalog,
10836
+ };
10837
+ }
10838
+
10309
10839
  // Start when DOM is ready
10310
10840
  document.addEventListener("DOMContentLoaded", init);