@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
@@ -799,6 +799,28 @@
799
799
  return filtered;
800
800
  }
801
801
 
802
+ function normalizeArchiveWorkflowStatus(status) {
803
+ const raw = String(status || "")
804
+ .trim()
805
+ .toLowerCase();
806
+ if (raw === "done") return "done";
807
+ if (raw === "trashed") return "trashed";
808
+ return "pending";
809
+ }
810
+
811
+ function fsPartitionTaskthreadGroups(groups) {
812
+ const pending = [];
813
+ const done = [];
814
+
815
+ for (const group of Array.isArray(groups) ? groups : []) {
816
+ const status = normalizeArchiveWorkflowStatus(group?.status);
817
+ if (status === "pending") pending.push(group);
818
+ if (status === "done") done.push(group);
819
+ }
820
+
821
+ return { pending, done };
822
+ }
823
+
802
824
  // Extended filter engine for the full-screen filter bar
803
825
  function fsApplyAdvancedFilters(groups, opts) {
804
826
  let filtered = groups;
@@ -814,7 +836,10 @@
814
836
 
815
837
  // Status filter
816
838
  if (opts.status && opts.status !== "all") {
817
- filtered = filtered.filter((g) => (g.status || "archived") === opts.status);
839
+ const expectedStatus = normalizeArchiveWorkflowStatus(opts.status);
840
+ filtered = filtered.filter(
841
+ (g) => normalizeArchiveWorkflowStatus(g.status) === expectedStatus
842
+ );
818
843
  }
819
844
 
820
845
  // Color filter (array of active colors)
@@ -927,8 +952,8 @@
927
952
  break;
928
953
  }
929
954
  case "status": {
930
- sectionKey = group.status || "archived";
931
- const statusLabels = { pending: "Pending", done: "Done", archived: "Archived" };
955
+ sectionKey = normalizeArchiveWorkflowStatus(group.status);
956
+ const statusLabels = { pending: "Pending", done: "Done", trashed: "Trashed" };
932
957
  sectionLabel = statusLabels[sectionKey] || sectionKey;
933
958
  break;
934
959
  }
@@ -1913,15 +1938,107 @@
1913
1938
  "restore",
1914
1939
  "archive",
1915
1940
  "rename",
1941
+ "update_description",
1942
+ "bulk_update_descriptions",
1943
+ "rename_window",
1944
+ "rename_group",
1945
+ "rename_tab",
1946
+ "rename_archive",
1947
+ "update_task_status",
1948
+ "save_snapshot",
1949
+ "manage_selected_item",
1916
1950
  "mark_pending",
1917
1951
  "mark_done",
1918
1952
  "dispatch_to_fleet",
1919
1953
  "get_fleet_status",
1920
1954
  "open_orchestrator_url",
1921
1955
  "invoke_slash_command",
1956
+ "bind_bwoa_session",
1957
+ "snapshot_bwoa_window",
1958
+ "delegate_bwoa_spoke",
1959
+ "complete_bwoa_spoke",
1922
1960
  ]);
1923
1961
 
1924
1962
  const READ_ONLY_ROUTER_INTENTS = new Set(["inspect", "search", "get_fleet_status"]);
1963
+ const BWOA_DEFAULT_SPACE_URL =
1964
+ "https://www.perplexity.ai/spaces/hub-browser-agent-window-all-t-dcF8yEn2RFejcQJxYBf3Zg";
1965
+
1966
+ const BWOA_MUTATING_INTENTS = new Set([
1967
+ "bind_bwoa_session",
1968
+ "snapshot_bwoa_window",
1969
+ "delegate_bwoa_spoke",
1970
+ "complete_bwoa_spoke",
1971
+ ]);
1972
+
1973
+ function normalizeRole(value) {
1974
+ return String(value || "")
1975
+ .trim()
1976
+ .toLowerCase()
1977
+ .replace(/[\s-]+/g, "_");
1978
+ }
1979
+
1980
+ function isPrimeRouterActor(data) {
1981
+ const principal = data?.principal || {};
1982
+ const activeWindowScope = data?.activeWindowScope || {};
1983
+ const roles = [
1984
+ data?.actorRole,
1985
+ data?.role,
1986
+ data?.actor,
1987
+ principal.role,
1988
+ principal.actorRole,
1989
+ activeWindowScope.actorRole,
1990
+ ].map(normalizeRole);
1991
+ return (
1992
+ data?.prime === true ||
1993
+ principal.prime === true ||
1994
+ roles.includes("prime") ||
1995
+ roles.includes("equanaut_prime") ||
1996
+ roles.includes("equanautprime")
1997
+ );
1998
+ }
1999
+
2000
+ function isExtensionOwnedMetadataTarget(target) {
2001
+ const source = String(target?.sourceFamily || target?.source || "").toLowerCase();
2002
+ const entityType = String(target?.entityType || target?.type || "").toLowerCase();
2003
+ return !!(
2004
+ target?.extensionOwned === true ||
2005
+ target?.extensionManaged === true ||
2006
+ target?.managedByExtension === true ||
2007
+ [
2008
+ "archived",
2009
+ "archive",
2010
+ "recent",
2011
+ "saved",
2012
+ "unified",
2013
+ "selected",
2014
+ "selected_item",
2015
+ "task-thread",
2016
+ "task_thread",
2017
+ "gateway",
2018
+ ].includes(source) ||
2019
+ [
2020
+ "archive",
2021
+ "archive-tab",
2022
+ "recent-window",
2023
+ "recent-tab",
2024
+ "selected-item",
2025
+ "task-thread",
2026
+ "window-label",
2027
+ ].includes(entityType)
2028
+ );
2029
+ }
2030
+
2031
+ function isPrimeLiveTargetVerified(target) {
2032
+ const owner = normalizeRole(target?.profileOwner || target?.owner);
2033
+ const displayRole = normalizeRole(target?.displayRole);
2034
+ if (owner !== "agent") return false;
2035
+ return !!(
2036
+ displayRole === "top_agents" ||
2037
+ target?.extensionOwned === true ||
2038
+ target?.extensionManaged === true ||
2039
+ target?.managedByExtension === true
2040
+ );
2041
+ }
1925
2042
 
1926
2043
  function isCapabilityUsable(capability) {
1927
2044
  return !!(
@@ -2027,6 +2144,38 @@
2027
2144
  return record.status || record.taskStatus || record.state || fallback || "unknown";
2028
2145
  }
2029
2146
 
2147
+ function catalogPrimeOperationsForSource(sourceFamily) {
2148
+ const source = String(sourceFamily || "").toLowerCase();
2149
+ if (source === "live") {
2150
+ return [
2151
+ "inspect",
2152
+ "search",
2153
+ "update_description",
2154
+ "bulk_update_descriptions",
2155
+ "rename_window",
2156
+ "rename_group",
2157
+ "rename_tab",
2158
+ "save_snapshot",
2159
+ ];
2160
+ }
2161
+ if (["archived", "archive", "recent", "saved", "unified", "selected"].includes(source)) {
2162
+ return [
2163
+ "inspect",
2164
+ "search",
2165
+ "update_description",
2166
+ "bulk_update_descriptions",
2167
+ "rename_archive",
2168
+ "rename_tab",
2169
+ "update_task_status",
2170
+ "manage_selected_item",
2171
+ ];
2172
+ }
2173
+ if (source === "gateway") {
2174
+ return ["inspect", "search", "update_description"];
2175
+ }
2176
+ return ["inspect", "search"];
2177
+ }
2178
+
2030
2179
  function normalizeCatalogRecord(record, sourceFamily, index) {
2031
2180
  const source = sourceFamily || "unknown";
2032
2181
  const nativeId =
@@ -2060,10 +2209,461 @@
2060
2209
  record.closedAt ||
2061
2210
  null,
2062
2211
  allowedOperations: ["inspect", "search"],
2212
+ primeAllowedOperations: catalogPrimeOperationsForSource(source),
2213
+ extensionOwned: record.extensionOwned !== false,
2063
2214
  readOnly: true,
2064
2215
  };
2065
2216
  }
2066
2217
 
2218
+ function normalizeBwoaStatus(value) {
2219
+ const status = normalizeRole(value || "active");
2220
+ if (
2221
+ ["active", "pending", "running", "paused", "completed", "stale", "failed"].includes(status)
2222
+ ) {
2223
+ return status;
2224
+ }
2225
+ return "active";
2226
+ }
2227
+
2228
+ function normalizeTrackedUrl(record, index) {
2229
+ const url = String(record?.url || record?.href || "").trim();
2230
+ return {
2231
+ id: String(record?.id || record?.bookmarkId || record?.tabId || `url-${index + 1}`),
2232
+ title: String(record?.title || record?.name || url || "Untitled"),
2233
+ url,
2234
+ source: String(record?.source || record?.sourceFamily || "tracked"),
2235
+ taskThreadId: stringOrNull(record?.taskThreadId || record?.threadId),
2236
+ description: normalizeEntityDescription(record),
2237
+ };
2238
+ }
2239
+
2240
+ function normalizeBwoaSpoke(record, index) {
2241
+ return {
2242
+ id: String(record?.id || record?.agentId || `spoke-${index + 1}`),
2243
+ agentId: stringOrNull(record?.agentId || record?.id),
2244
+ taskThreadId: stringOrNull(record?.taskThreadId || record?.threadId),
2245
+ threadUrl: stringOrNull(record?.threadUrl || record?.url || record?.orchestratorUrl),
2246
+ goal: String(record?.goal || record?.taskGoal || record?.title || ""),
2247
+ successBenchmark: String(record?.successBenchmark || record?.benchmark || ""),
2248
+ status: normalizeBwoaStatus(record?.status || "pending"),
2249
+ successScore:
2250
+ record?.successScore === undefined || record?.successScore === null
2251
+ ? null
2252
+ : Number(record.successScore),
2253
+ finalSummary: String(record?.finalSummary || record?.summary || ""),
2254
+ logsReference: stringOrNull(record?.logsReference || record?.logRef || record?.threadUrl),
2255
+ };
2256
+ }
2257
+
2258
+ function normalizeBwoaTab(record, index) {
2259
+ return {
2260
+ id: String(record?.id || record?.tabId || `tab-${index + 1}`),
2261
+ tabId: record?.tabId === undefined ? null : Number(record.tabId),
2262
+ groupId: record?.groupId === undefined ? null : Number(record.groupId),
2263
+ windowId: record?.windowId === undefined ? null : Number(record.windowId),
2264
+ title: String(record?.title || record?.url || "Untitled"),
2265
+ url: stringOrNull(record?.url),
2266
+ active: !!record?.active,
2267
+ pinned: !!record?.pinned,
2268
+ description: normalizeEntityDescription(record),
2269
+ taskThreadId: stringOrNull(record?.taskThreadId || record?.threadId),
2270
+ };
2271
+ }
2272
+
2273
+ function normalizeBwoaGroup(record, index) {
2274
+ const tabs = Array.isArray(record?.tabs) ? record.tabs : [];
2275
+ return {
2276
+ id: String(record?.id || record?.groupId || `group-${index + 1}`),
2277
+ groupId: record?.groupId === undefined ? record?.id || null : Number(record.groupId),
2278
+ windowId: record?.windowId === undefined ? null : Number(record.windowId),
2279
+ title: String(record?.title || record?.name || "Untitled Group"),
2280
+ color: String(record?.color || "grey"),
2281
+ status: normalizeBwoaStatus(record?.status || "active"),
2282
+ description: normalizeEntityDescription(record),
2283
+ taskThreadId: stringOrNull(record?.taskThreadId || record?.threadId),
2284
+ tabs: tabs.map((tab, tabIndex) => normalizeBwoaTab(tab, tabIndex)),
2285
+ };
2286
+ }
2287
+
2288
+ function buildBwoaWindowHubSession(input) {
2289
+ const data = input || {};
2290
+ const now = data.updatedAt || data.lastSnapshotAt || data.createdAt || new Date().toISOString();
2291
+ const epoaSessionId = String(data.epoaSessionId || data.primeSessionId || "").trim();
2292
+ const windowId =
2293
+ data.windowId === undefined || data.windowId === null || data.windowId === ""
2294
+ ? null
2295
+ : Number(data.windowId);
2296
+ const taskThreadId = String(data.taskThreadId || data.threadId || "").trim();
2297
+ const groups = (Array.isArray(data.tabGroups) ? data.tabGroups : data.groups || []).map(
2298
+ (group, index) => normalizeBwoaGroup(group, index)
2299
+ );
2300
+ const openTabs = (Array.isArray(data.openTabs) ? data.openTabs : data.tabs || []).map(
2301
+ (tab, index) => normalizeBwoaTab(tab, index)
2302
+ );
2303
+
2304
+ return {
2305
+ schemaVersion: "bwoa.window-hub-session.v1",
2306
+ id: String(
2307
+ data.id ||
2308
+ data.sessionKey ||
2309
+ [
2310
+ "bwoa",
2311
+ epoaSessionId || "unbound-prime",
2312
+ windowId === null ? "window-unknown" : `window-${windowId}`,
2313
+ ].join(":")
2314
+ ),
2315
+ epoaSessionId,
2316
+ bwoaSpaceUrl: String(data.bwoaSpaceUrl || data.spaceUrl || BWOA_DEFAULT_SPACE_URL),
2317
+ windowId,
2318
+ windowOrdinal: Number(data.windowOrdinal || data.windowNumber || 1),
2319
+ tabGroupId:
2320
+ data.tabGroupId === undefined || data.tabGroupId === null || data.tabGroupId === ""
2321
+ ? null
2322
+ : Number(data.tabGroupId),
2323
+ taskThreadId,
2324
+ agentId: String(data.agentId || "bwoa-window-hub"),
2325
+ status: normalizeBwoaStatus(data.status || "active"),
2326
+ name: String(data.name || data.title || "Window Hub Orchestrator"),
2327
+ description: normalizeEntityDescription(data),
2328
+ bookmarkedUrls: (data.bookmarkedUrls || data.bookmarks || []).map(normalizeTrackedUrl),
2329
+ trackedUrls: (data.trackedUrls || []).map(normalizeTrackedUrl),
2330
+ tabGroups: groups,
2331
+ openTabs,
2332
+ spawnedSpokes: (data.spawnedSpokes || data.spokes || []).map(normalizeBwoaSpoke),
2333
+ lastSnapshotAt: now,
2334
+ createdAt: data.createdAt || now,
2335
+ updatedAt: now,
2336
+ auditTrail: Array.isArray(data.auditTrail) ? data.auditTrail : [],
2337
+ };
2338
+ }
2339
+
2340
+ function countBwoaWindowHubSession(session) {
2341
+ const data = session || {};
2342
+ const groups = Array.isArray(data.tabGroups) ? data.tabGroups : [];
2343
+ const openTabs = Array.isArray(data.openTabs) ? data.openTabs : [];
2344
+ const groupTabs = groups.reduce(
2345
+ (sum, group) => sum + (Array.isArray(group.tabs) ? group.tabs.length : 0),
2346
+ 0
2347
+ );
2348
+ return {
2349
+ tabGroups: groups.length,
2350
+ openTabs: openTabs.length || groupTabs,
2351
+ bookmarkedUrls: (data.bookmarkedUrls || []).length,
2352
+ trackedUrls: (data.trackedUrls || []).length,
2353
+ spawnedSpokes: (data.spawnedSpokes || []).length,
2354
+ activeSpokes: (data.spawnedSpokes || []).filter((spoke) =>
2355
+ ["active", "running", "pending"].includes(normalizeBwoaStatus(spoke.status))
2356
+ ).length,
2357
+ };
2358
+ }
2359
+
2360
+ function buildBwoaDelegationEnvelope(input) {
2361
+ const data = input || {};
2362
+ const session = buildBwoaWindowHubSession(data.session || data);
2363
+ const goal = String(data.goal || data.taskGoal || "").trim();
2364
+ const taskThreadId = String(data.taskThreadId || session.taskThreadId || "").trim();
2365
+ return {
2366
+ schemaVersion: "bwoa.delegation-envelope.v1",
2367
+ goal,
2368
+ successBenchmark: String(data.successBenchmark || data.benchmark || ""),
2369
+ windowId: session.windowId,
2370
+ windowOrdinal: session.windowOrdinal,
2371
+ taskThreadId,
2372
+ allowedTargets: Array.isArray(data.allowedTargets)
2373
+ ? data.allowedTargets
2374
+ : [
2375
+ {
2376
+ entityType: "window",
2377
+ windowId: session.windowId,
2378
+ scope: "bwoa_window",
2379
+ },
2380
+ ],
2381
+ requiredReportFormat:
2382
+ data.requiredReportFormat ||
2383
+ "Session Report with deployed spokes, benchmark, final output summary, success score, pass/fail status, and logs reference.",
2384
+ bwoaSpaceUrl: session.bwoaSpaceUrl,
2385
+ createdAt: data.createdAt || new Date().toISOString(),
2386
+ };
2387
+ }
2388
+
2389
+ function normalizeBwoaConfigItem(record, index, fallbackKind) {
2390
+ const value = record || {};
2391
+ const id = value.id || value.name || value.path || value.url || `${fallbackKind}-${index + 1}`;
2392
+ return {
2393
+ id: String(id),
2394
+ kind: String(value.kind || fallbackKind),
2395
+ name: String(value.name || value.title || id),
2396
+ purpose: String(value.purpose || value.description || ""),
2397
+ required: value.required !== false,
2398
+ };
2399
+ }
2400
+
2401
+ function buildBwoaWindowHubConfiguration(input) {
2402
+ const data = input || {};
2403
+ const session = buildBwoaWindowHubSession(data.session || data);
2404
+ const windowOrdinal = Number(data.windowOrdinal || session.windowOrdinal || 1);
2405
+ const spaceUrl = String(data.bwoaSpaceUrl || data.spaceUrl || session.bwoaSpaceUrl);
2406
+ const defaultConnections = [
2407
+ {
2408
+ id: "epoa-control-channel",
2409
+ kind: "conversation",
2410
+ name: "EPOA to BWOA direct channel",
2411
+ purpose:
2412
+ "Codex Prime sends only compact session envelopes, delegation envelopes, and report requests.",
2413
+ },
2414
+ {
2415
+ id: "bwoa-perplexity-space",
2416
+ kind: "space",
2417
+ name: "Hub Browser Agent Window All Tabs",
2418
+ purpose: spaceUrl,
2419
+ },
2420
+ {
2421
+ id: "comet-bridge",
2422
+ kind: "mcp",
2423
+ name: "Comet-Bridge",
2424
+ purpose:
2425
+ "Authoritative browser control, tab group inventory, lifecycle metadata, and top-display window binding.",
2426
+ },
2427
+ {
2428
+ id: "task-thread-registry",
2429
+ kind: "metadata",
2430
+ name: "Task Thread Registry",
2431
+ purpose: "Joins BWOA hub state to spawned spoke work and completion reports.",
2432
+ },
2433
+ ];
2434
+ const defaultSkills = [
2435
+ "bwoa-window-hub",
2436
+ "comet-browser-control-toolkit",
2437
+ "comet-connect",
2438
+ "comet-workspace",
2439
+ "comet-task-threads",
2440
+ "comet-delegate",
2441
+ "comet-session",
2442
+ "comet-skill-inventory",
2443
+ "15-comet-review",
2444
+ ].map((name) => ({
2445
+ id: name,
2446
+ kind: "skill",
2447
+ name,
2448
+ purpose: "BWOA browser orchestration surface",
2449
+ }));
2450
+ const defaultFiles = [
2451
+ "codex-comet-browser-control-toolkit/skills/bwoa-window-hub/SKILL.md",
2452
+ "codex-comet-browser-control-toolkit/commands/bwoa-window-hub.md",
2453
+ "docs/BWOA-WINDOW-HUB-ORCHESTRATOR.md",
2454
+ "comet-mcp/extension/session-logic.js",
2455
+ "test/bwoa-window-hub-session.test.mjs",
2456
+ "specs/086-branch-window-hub/contracts/bwoa-window-hub-session.md",
2457
+ "specs/086-branch-window-hub/quickstart.md",
2458
+ ].map((path) => ({
2459
+ id: path,
2460
+ kind: "file",
2461
+ name: path,
2462
+ purpose: "Source-level BWOA contract evidence",
2463
+ }));
2464
+
2465
+ return {
2466
+ schemaVersion: "bwoa.window-hub-configuration.v1",
2467
+ id: String(data.id || `bwoa-window-${windowOrdinal}-configuration`),
2468
+ role: "browser_window_orchestrator_agent",
2469
+ windowOrdinal,
2470
+ bwoaSpaceUrl: spaceUrl,
2471
+ profileOwner: "agent",
2472
+ displayRole: "top_agents",
2473
+ primaryMode: String(data.primaryMode || "Computer"),
2474
+ planningMode: String(data.planningMode || "Orchestrator"),
2475
+ settings: {
2476
+ oneWindowPerHub: true,
2477
+ oneManagedTabGroupPerWindow: true,
2478
+ preserveOpenTabs: true,
2479
+ preserveTrackedUrls: true,
2480
+ snapshotCadence: ["bind", "pre_delegate", "post_delegate", "complete", "handoff"],
2481
+ authoritativeState: ["extension_storage", "mcp_metadata", "snapshots", "task_threads"],
2482
+ prohibitedAutomation: ["chrome", "puppeteer", "playwright_keyboard", "codex_browser"],
2483
+ ...(data.settings || {}),
2484
+ },
2485
+ connections: (data.connections || defaultConnections).map((item, index) =>
2486
+ normalizeBwoaConfigItem(item, index, "connection")
2487
+ ),
2488
+ requiredSkills: (data.requiredSkills || defaultSkills).map((item, index) =>
2489
+ normalizeBwoaConfigItem(item, index, "skill")
2490
+ ),
2491
+ requiredFiles: (data.requiredFiles || defaultFiles).map((item, index) =>
2492
+ normalizeBwoaConfigItem(item, index, "file")
2493
+ ),
2494
+ instructionBlocks: data.instructionBlocks || [
2495
+ "Bind one Codex/EPOA session to one BWOA Space and one top-display Comet window.",
2496
+ "Snapshot tab groups, open tabs, bookmarked URLs, and tracked URLs before delegating spoke work.",
2497
+ "Delegate only bounded spoke goals with success benchmarks and allowed targets.",
2498
+ "Return the required Session Report upward to EPOA after every spoke completes or blocks.",
2499
+ ],
2500
+ threadProtocol: {
2501
+ epoaToBwoa: "bwoa.delegation-envelope.v1",
2502
+ bwoaState: "bwoa.window-hub-session.v1",
2503
+ bwoaToEpoa: "Session Report",
2504
+ bwoaToSpoke: "bounded task-thread with benchmark, allowed targets, and logs reference",
2505
+ ...(data.threadProtocol || {}),
2506
+ },
2507
+ session,
2508
+ };
2509
+ }
2510
+
2511
+ function safeBwoaJson(value) {
2512
+ try {
2513
+ return JSON.stringify(value, null, 2);
2514
+ } catch (_err) {
2515
+ return "{}";
2516
+ }
2517
+ }
2518
+
2519
+ function buildBwoaEpoaHandoffPrompt(input) {
2520
+ const data = input || {};
2521
+ const session = buildBwoaWindowHubSession(data.session || data);
2522
+ const configuration = buildBwoaWindowHubConfiguration({
2523
+ ...data,
2524
+ session,
2525
+ });
2526
+ const goal = String(
2527
+ data.goal ||
2528
+ "Bind this BWOA Space to the current EPOA session and maintain Window #1 browser task context."
2529
+ ).trim();
2530
+ return [
2531
+ "# EPOA to BWOA Handoff",
2532
+ "",
2533
+ `Goal: ${goal}`,
2534
+ "",
2535
+ "You are BWOA, the Browser Window Orchestrator Agent for Window #1. Treat Codex/EPOA as the only upstream orchestrator for this browser-window hub.",
2536
+ "",
2537
+ "Required operating rules:",
2538
+ "- Keep this Perplexity Space as the conversational surface only.",
2539
+ "- Keep durable state in Comet-Bridge extension storage, MCP metadata, snapshots, and task-thread records.",
2540
+ "- Preserve tab groups, open tabs, bookmarked URLs, and tracked URLs for the full task lifecycle.",
2541
+ "- Delegate bounded browser work to spoke task threads only through the delegation envelope.",
2542
+ "- Return the required Session Report to EPOA after every spoke completes or blocks.",
2543
+ "- Do not mutate human-owned or bottom-display browser targets.",
2544
+ "- Do not replace Comet-Bridge with Chrome, Puppeteer, Playwright-as-keyboard, or Codex Browser.",
2545
+ "",
2546
+ "Configuration:",
2547
+ "```json",
2548
+ safeBwoaJson(configuration),
2549
+ "```",
2550
+ "",
2551
+ "Current BWOA session:",
2552
+ "```json",
2553
+ safeBwoaJson(session),
2554
+ "```",
2555
+ ].join("\n");
2556
+ }
2557
+
2558
+ function buildBwoaSpokePrompt(input) {
2559
+ const data = input || {};
2560
+ const envelope = buildBwoaDelegationEnvelope(data.envelope || data);
2561
+ return [
2562
+ "# BWOA to Spoke Delegation",
2563
+ "",
2564
+ `Goal: ${envelope.goal || "Complete the delegated browser task."}`,
2565
+ `Success Benchmark: ${envelope.successBenchmark || "Meet the delegated success criteria."}`,
2566
+ `Window Scope: Window #${envelope.windowOrdinal} (${envelope.windowId || "unbound"})`,
2567
+ `Task Thread: ${envelope.taskThreadId || "unbound"}`,
2568
+ "",
2569
+ "Allowed targets:",
2570
+ "```json",
2571
+ safeBwoaJson(envelope.allowedTargets),
2572
+ "```",
2573
+ "",
2574
+ "Delegation envelope:",
2575
+ "```json",
2576
+ safeBwoaJson(envelope),
2577
+ "```",
2578
+ "",
2579
+ "Return exactly this report shape to BWOA/EPOA:",
2580
+ "",
2581
+ "### Session Report: <task or goal>",
2582
+ "- **Spoke Agent(s) Deployed:** <ids or none>",
2583
+ "- **Success Benchmark:** <1-2 sentences>",
2584
+ "- **Final Output Summary:** <compressed result>",
2585
+ "- **Success Score:** <0-100>%",
2586
+ "- **Status:** <PASS|FAIL>",
2587
+ "- **Logs Reference:** <thread URL or record id>",
2588
+ ].join("\n");
2589
+ }
2590
+
2591
+ function validateBwoaRouterIntent(input) {
2592
+ const data = input || {};
2593
+ const intent = String(data.intent || "");
2594
+ if (!BWOA_MUTATING_INTENTS.has(intent)) return null;
2595
+
2596
+ const activeWindowScope = data.activeWindowScope || {};
2597
+ const target = data.target || {};
2598
+ const policyTier = isPrimeRouterActor(data) ? "prime" : "normal";
2599
+
2600
+ if (policyTier !== "prime") {
2601
+ return denyRouterIntent(
2602
+ intent,
2603
+ "BWOA_PRIME_REQUIRED",
2604
+ "BWOA session mutation requires Equanaut Prime or EPOA authority.",
2605
+ activeWindowScope,
2606
+ policyTier
2607
+ );
2608
+ }
2609
+
2610
+ if (normalizeRole(target.profileOwner) === "human") {
2611
+ return denyRouterIntent(
2612
+ intent,
2613
+ "HUMAN_PROFILE_DENIED",
2614
+ "BWOA cannot mutate human-owned browser state.",
2615
+ activeWindowScope,
2616
+ policyTier
2617
+ );
2618
+ }
2619
+
2620
+ const targetDisplayRole = normalizeRole(target.displayRole);
2621
+ if (targetDisplayRole === "primary_user" || targetDisplayRole === "bottom_user") {
2622
+ return denyRouterIntent(
2623
+ intent,
2624
+ "BOTTOM_DISPLAY_MUTATION_DENIED",
2625
+ "BWOA targets must remain in the top-display agent workspace.",
2626
+ activeWindowScope,
2627
+ policyTier
2628
+ );
2629
+ }
2630
+
2631
+ const session = buildBwoaWindowHubSession(data.session || target);
2632
+ const activeWindowId = activeWindowScope.windowId ? String(activeWindowScope.windowId) : "";
2633
+ const targetWindowId =
2634
+ session.windowId === null ? String(target.windowId || "") : String(session.windowId);
2635
+ if (targetWindowId && activeWindowId && targetWindowId !== activeWindowId) {
2636
+ if (!isPrimeLiveTargetVerified({ ...target, windowId: targetWindowId })) {
2637
+ return denyRouterIntent(
2638
+ intent,
2639
+ "BWOA_TARGET_UNVERIFIED",
2640
+ "BWOA cross-window mutation requires verified top-display agent ownership.",
2641
+ activeWindowScope,
2642
+ policyTier
2643
+ );
2644
+ }
2645
+ }
2646
+
2647
+ return {
2648
+ intent,
2649
+ target,
2650
+ session,
2651
+ scope: "bwoa_window_hub",
2652
+ mutation: true,
2653
+ requiresConfirmation: false,
2654
+ preview: data.preview || `${intent} for Window #${session.windowOrdinal}`,
2655
+ audit: {
2656
+ requestedBy: "equanaut_prime",
2657
+ activeWindowId: activeWindowId || "unknown",
2658
+ targetWindowId: targetWindowId || "unknown",
2659
+ policyTier,
2660
+ reason: data.reason || "bwoa_orchestration",
2661
+ createdAt: new Date().toISOString(),
2662
+ },
2663
+ allowed: true,
2664
+ };
2665
+ }
2666
+
2067
2667
  function normalizeFleetRecord(record, index) {
2068
2668
  const base = normalizeCatalogRecord(record, "fleet", index);
2069
2669
  return {
@@ -2094,6 +2694,18 @@
2094
2694
  const gatewayTools = (data.gatewayTools || []).map((r, i) =>
2095
2695
  normalizeCatalogRecord(r, "gateway", i)
2096
2696
  );
2697
+ const selectedItems = (data.selectedItems || []).map((r, i) =>
2698
+ normalizeCatalogRecord(r, "selected", i)
2699
+ );
2700
+ const lifecycleBindings = (data.lifecycleBindings || []).map((r, i) =>
2701
+ normalizeCatalogRecord(r, "lifecycle", i)
2702
+ );
2703
+ const windowLabels = (data.windowLabels || []).map((r, i) =>
2704
+ normalizeCatalogRecord(r, "window-label", i)
2705
+ );
2706
+ const descriptions = (data.descriptions || []).map((r, i) =>
2707
+ normalizeCatalogRecord(r, "description", i)
2708
+ );
2097
2709
  const slashCommands = Array.isArray(data.slashCommands)
2098
2710
  ? buildEquanautSlashCommandCatalog(data.slashCommands)
2099
2711
  : buildEquanautSlashCommandCatalog();
@@ -2108,6 +2720,10 @@
2108
2720
  unifiedThreads,
2109
2721
  fleetMembers,
2110
2722
  gatewayTools,
2723
+ selectedItems,
2724
+ lifecycleBindings,
2725
+ windowLabels,
2726
+ descriptions,
2111
2727
  slashCommands,
2112
2728
  plugins,
2113
2729
  freshness: {
@@ -2129,6 +2745,10 @@
2129
2745
  gatewayTools: (c.gatewayTools || []).length,
2130
2746
  slashCommands: (c.slashCommands || []).length,
2131
2747
  plugins: (c.plugins || []).length,
2748
+ selectedItems: (c.selectedItems || []).length,
2749
+ lifecycleBindings: (c.lifecycleBindings || []).length,
2750
+ windowLabels: (c.windowLabels || []).length,
2751
+ descriptions: (c.descriptions || []).length,
2132
2752
  };
2133
2753
  }
2134
2754
 
@@ -2174,7 +2794,7 @@
2174
2794
  };
2175
2795
  }
2176
2796
 
2177
- function denyRouterIntent(intent, code, message, activeWindowScope) {
2797
+ function denyRouterIntent(intent, code, message, activeWindowScope, policyTier = "normal") {
2178
2798
  return {
2179
2799
  intent,
2180
2800
  scope: "denied",
@@ -2185,6 +2805,7 @@
2185
2805
  audit: {
2186
2806
  requestedBy: "sidepanel",
2187
2807
  activeWindowId: activeWindowScope?.windowId || "unknown",
2808
+ policyTier,
2188
2809
  createdAt: new Date().toISOString(),
2189
2810
  },
2190
2811
  allowed: false,
@@ -2197,16 +2818,22 @@
2197
2818
  const target = data.target || {};
2198
2819
  const activeWindowScope = data.activeWindowScope || {};
2199
2820
  const activeWindowId = activeWindowScope.windowId ? String(activeWindowScope.windowId) : "";
2821
+ const isPrime = isPrimeRouterActor(data);
2822
+ const policyTier = isPrime ? "prime" : "normal";
2200
2823
 
2201
2824
  if (!SUPPORTED_ROUTER_INTENTS.includes(intent)) {
2202
2825
  return denyRouterIntent(
2203
2826
  intent || "unknown",
2204
2827
  "UNSUPPORTED_INTENT",
2205
2828
  `Unsupported router intent: ${intent || "unknown"}`,
2206
- activeWindowScope
2829
+ activeWindowScope,
2830
+ policyTier
2207
2831
  );
2208
2832
  }
2209
2833
 
2834
+ const bwoaDecision = validateBwoaRouterIntent(data);
2835
+ if (bwoaDecision) return bwoaDecision;
2836
+
2210
2837
  const mutation = !READ_ONLY_ROUTER_INTENTS.has(intent);
2211
2838
  if (!mutation) {
2212
2839
  return {
@@ -2219,57 +2846,78 @@
2219
2846
  audit: {
2220
2847
  requestedBy: "sidepanel",
2221
2848
  activeWindowId: activeWindowId || "unknown",
2849
+ policyTier,
2222
2850
  createdAt: new Date().toISOString(),
2223
2851
  },
2224
2852
  allowed: true,
2225
2853
  };
2226
2854
  }
2227
2855
 
2228
- if (activeWindowScope.profileOwner === "human" || target.profileOwner === "human") {
2856
+ if (
2857
+ normalizeRole(activeWindowScope.profileOwner) === "human" ||
2858
+ normalizeRole(target.profileOwner) === "human"
2859
+ ) {
2229
2860
  return denyRouterIntent(
2230
2861
  intent,
2231
2862
  "HUMAN_PROFILE_DENIED",
2232
- "Normal agents cannot mutate human-owned browser profile or window state.",
2233
- activeWindowScope
2863
+ "Agents cannot mutate human-owned browser profile or window state.",
2864
+ activeWindowScope,
2865
+ policyTier
2234
2866
  );
2235
2867
  }
2236
2868
 
2237
- if (target.displayRole === "primary_user" || target.displayRole === "bottom_user") {
2869
+ const targetDisplayRole = normalizeRole(target.displayRole);
2870
+ if (targetDisplayRole === "primary_user" || targetDisplayRole === "bottom_user") {
2238
2871
  return denyRouterIntent(
2239
2872
  intent,
2240
2873
  "BOTTOM_DISPLAY_MUTATION_DENIED",
2241
- "Normal agents cannot mutate bottom-display user browser windows.",
2242
- activeWindowScope
2874
+ "Agents cannot mutate primary or bottom-display user browser windows.",
2875
+ activeWindowScope,
2876
+ policyTier
2243
2877
  );
2244
2878
  }
2245
2879
 
2246
2880
  const targetWindowId = target.windowId ? String(target.windowId) : "";
2247
2881
  const allowOtherWindow =
2248
2882
  activeWindowScope.mutationPolicy && activeWindowScope.mutationPolicy.allowOtherWindowMutation;
2249
- if (
2883
+ const crossWindowMutation = !!(
2250
2884
  targetWindowId &&
2251
2885
  activeWindowId &&
2252
- targetWindowId !== activeWindowId &&
2253
- !allowOtherWindow
2254
- ) {
2255
- return denyRouterIntent(
2256
- intent,
2257
- "OTHER_WINDOW_MUTATION_DENIED",
2258
- "Normal sidepanel requests may mutate only the active sidepanel window. Open or rebind the target first.",
2259
- activeWindowScope
2260
- );
2886
+ targetWindowId !== activeWindowId
2887
+ );
2888
+ if (crossWindowMutation && !allowOtherWindow) {
2889
+ if (isPrime && isPrimeLiveTargetVerified(target)) {
2890
+ // Prime may continue after verified top-display or extension-managed ownership.
2891
+ } else if (isPrime) {
2892
+ return denyRouterIntent(
2893
+ intent,
2894
+ "PRIME_TARGET_UNVERIFIED",
2895
+ "Equanaut Prime cross-window live mutation requires top-display agent ownership or extension-managed target proof.",
2896
+ activeWindowScope,
2897
+ policyTier
2898
+ );
2899
+ } else {
2900
+ return denyRouterIntent(
2901
+ intent,
2902
+ "OTHER_WINDOW_MUTATION_DENIED",
2903
+ "Normal sidepanel requests may mutate only the active sidepanel window. Open or rebind the target first.",
2904
+ activeWindowScope,
2905
+ policyTier
2906
+ );
2907
+ }
2261
2908
  }
2262
2909
 
2263
2910
  if (
2264
2911
  ["open", "restore", "open_orchestrator_url"].includes(intent) &&
2265
2912
  activeWindowScope.displayRole &&
2266
- activeWindowScope.displayRole !== "top_agents"
2913
+ normalizeRole(activeWindowScope.displayRole) !== "top_agents"
2267
2914
  ) {
2268
2915
  return denyRouterIntent(
2269
2916
  intent,
2270
2917
  "TOP_DISPLAY_REQUIRED",
2271
2918
  "Open and restore actions must use the top-display agent workspace.",
2272
- activeWindowScope
2919
+ activeWindowScope,
2920
+ policyTier
2273
2921
  );
2274
2922
  }
2275
2923
 
@@ -2280,21 +2928,31 @@
2280
2928
  intent,
2281
2929
  "FLEET_TARGET_UNAVAILABLE",
2282
2930
  "Dispatch requires a live or reachable fleet target.",
2283
- activeWindowScope
2931
+ activeWindowScope,
2932
+ policyTier
2284
2933
  );
2285
2934
  }
2286
2935
  }
2287
2936
 
2937
+ const extensionOwnedMetadata = isExtensionOwnedMetadataTarget(target);
2938
+ const scope =
2939
+ intent === "dispatch_to_fleet"
2940
+ ? "fleet"
2941
+ : isPrime && (extensionOwnedMetadata || crossWindowMutation)
2942
+ ? "prime_global"
2943
+ : "active_window";
2944
+
2288
2945
  return {
2289
2946
  intent,
2290
2947
  target,
2291
- scope: intent === "dispatch_to_fleet" ? "fleet" : "active_window",
2948
+ scope,
2292
2949
  mutation: true,
2293
2950
  requiresConfirmation: !!data.requiresConfirmation,
2294
2951
  preview: data.preview || `${intent} ${target.title || target.id || "target"}`,
2295
2952
  audit: {
2296
2953
  requestedBy: "sidepanel",
2297
2954
  activeWindowId: activeWindowId || "unknown",
2955
+ policyTier,
2298
2956
  reason: data.reason || "user_request",
2299
2957
  createdAt: new Date().toISOString(),
2300
2958
  },
@@ -2307,6 +2965,8 @@
2307
2965
  fsFuzzyMatch,
2308
2966
  fsFormatEventDescription,
2309
2967
  fsApplyFilters,
2968
+ normalizeArchiveWorkflowStatus,
2969
+ fsPartitionTaskthreadGroups,
2310
2970
  fsApplyAdvancedFilters,
2311
2971
  fsSortGroups,
2312
2972
  fsGroupBy,
@@ -2357,13 +3017,24 @@
2357
3017
  groupAndSortForDeletion,
2358
3018
  SUPPORTED_ROUTER_INTENTS,
2359
3019
  selectEquanautRouterRoute,
3020
+ isPrimeRouterActor,
3021
+ isExtensionOwnedMetadataTarget,
3022
+ isPrimeLiveTargetVerified,
2360
3023
  normalizeEntityDescription,
2361
3024
  normalizeCatalogRecord,
2362
3025
  buildGlobalContextCatalog,
2363
3026
  countGlobalContextCatalog,
2364
3027
  buildAgentContextEnvelope,
3028
+ buildBwoaWindowHubSession,
3029
+ countBwoaWindowHubSession,
3030
+ buildBwoaDelegationEnvelope,
3031
+ buildBwoaWindowHubConfiguration,
3032
+ buildBwoaEpoaHandoffPrompt,
3033
+ buildBwoaSpokePrompt,
3034
+ validateBwoaRouterIntent,
2365
3035
  validateRouterIntent,
2366
3036
  DEFAULT_DOMAIN_DISPLAY_MAP,
3037
+ BWOA_DEFAULT_SPACE_URL,
2367
3038
  };
2368
3039
 
2369
3040
  // Browser: register on globalThis