@shawnowen/comet-mcp 2.3.0 → 2.4.1

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 (85) hide show
  1. package/README.md +86 -19
  2. package/dist/alert-dispatcher.d.ts +23 -0
  3. package/dist/alert-dispatcher.js +101 -0
  4. package/dist/bound-session.d.ts +23 -0
  5. package/dist/bound-session.js +119 -0
  6. package/dist/bridge-config.d.ts +6 -0
  7. package/dist/bridge-config.js +78 -0
  8. package/dist/cdp-client.d.ts +40 -4
  9. package/dist/cdp-client.js +502 -155
  10. package/dist/comet-ai.d.ts +15 -0
  11. package/dist/comet-ai.js +114 -38
  12. package/dist/delegate-binding.d.ts +19 -0
  13. package/dist/delegate-binding.js +73 -0
  14. package/dist/discovery/capability-entry.d.ts +215 -0
  15. package/dist/discovery/capability-entry.js +13 -0
  16. package/dist/discovery/description-template.d.ts +40 -0
  17. package/dist/discovery/description-template.js +61 -0
  18. package/dist/discovery/golden-queries.fixture.d.ts +22 -0
  19. package/dist/discovery/golden-queries.fixture.js +137 -0
  20. package/dist/discovery/mcp-source.d.ts +38 -0
  21. package/dist/discovery/mcp-source.js +70 -0
  22. package/dist/discovery/metadata-completeness.d.ts +48 -0
  23. package/dist/discovery/metadata-completeness.js +83 -0
  24. package/dist/discovery/registry.d.ts +35 -0
  25. package/dist/discovery/registry.js +35 -0
  26. package/dist/discovery/safety.d.ts +44 -0
  27. package/dist/discovery/safety.js +59 -0
  28. package/dist/discovery/schema-validator.d.ts +36 -0
  29. package/dist/discovery/schema-validator.js +257 -0
  30. package/dist/discovery/source-error.d.ts +47 -0
  31. package/dist/discovery/source-error.js +95 -0
  32. package/dist/discovery/tool-meta.d.ts +41 -0
  33. package/dist/discovery/tool-meta.js +229 -0
  34. package/dist/discovery/virtual-tools.d.ts +20 -0
  35. package/dist/discovery/virtual-tools.js +69 -0
  36. package/dist/http-server.js +2067 -47
  37. package/dist/index.js +3163 -710
  38. package/dist/observer.d.ts +47 -0
  39. package/dist/observer.js +516 -0
  40. package/dist/session-registry.d.ts +57 -0
  41. package/dist/session-registry.js +500 -0
  42. package/dist/sidecar-artifacts.d.ts +49 -0
  43. package/dist/sidecar-artifacts.js +146 -0
  44. package/dist/snapshot-capture.d.ts +3 -0
  45. package/dist/snapshot-capture.js +91 -0
  46. package/dist/tab-group-archive.js +3 -1
  47. package/dist/tab-groups.d.ts +7 -0
  48. package/dist/tab-groups.js +21 -3
  49. package/dist/task-thread-aggregator.d.ts +34 -0
  50. package/dist/task-thread-aggregator.js +480 -0
  51. package/dist/task-thread-canonical.d.ts +142 -0
  52. package/dist/task-thread-canonical.js +116 -0
  53. package/dist/types.d.ts +237 -0
  54. package/dist/window-bindings.d.ts +112 -0
  55. package/dist/window-bindings.js +476 -0
  56. package/extension/background.js +1556 -300
  57. package/extension/icons/icon.svg +9 -0
  58. package/extension/icons/icon128.png +0 -0
  59. package/extension/icons/icon16.png +0 -0
  60. package/extension/icons/icon48.png +0 -0
  61. package/extension/manifest.json +19 -4
  62. package/extension/session-logic.js +2383 -0
  63. package/extension/session-manager.html +299 -0
  64. package/extension/sidepanel.css +5323 -528
  65. package/extension/sidepanel.html +282 -2
  66. package/extension/sidepanel.js +10075 -951
  67. package/extension/window-policy.js +162 -0
  68. package/package.json +10 -7
  69. package/vendor/lifecycle-mcp-adapter.mjs +103 -0
  70. package/vendor/lifecycle-metadata.mjs +252 -0
  71. package/vendor/readiness-report.mjs +742 -0
  72. package/dist/cdp-client.d.ts.map +0 -1
  73. package/dist/cdp-client.js.map +0 -1
  74. package/dist/comet-ai.d.ts.map +0 -1
  75. package/dist/comet-ai.js.map +0 -1
  76. package/dist/http-server.d.ts.map +0 -1
  77. package/dist/http-server.js.map +0 -1
  78. package/dist/index.d.ts.map +0 -1
  79. package/dist/index.js.map +0 -1
  80. package/dist/tab-group-archive.d.ts.map +0 -1
  81. package/dist/tab-group-archive.js.map +0 -1
  82. package/dist/tab-groups.d.ts.map +0 -1
  83. package/dist/tab-groups.js.map +0 -1
  84. package/dist/types.d.ts.map +0 -1
  85. package/dist/types.js.map +0 -1
package/README.md CHANGED
@@ -1,10 +1,6 @@
1
1
  # comet-mcp
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/comet-mcp.svg)](https://www.npmjs.com/package/comet-mcp)
4
-
5
- <a href="https://glama.ai/mcp/servers/@hanzili/comet-mcp">
6
- <img width="380" height="200" src="https://glama.ai/mcp/servers/@hanzili/comet-mcp/badge" />
7
- </a>
3
+ [![npm version](https://img.shields.io/npm/v/@shawnowen/comet-mcp.svg)](https://www.npmjs.com/package/@shawnowen/comet-mcp)
8
4
 
9
5
  **Give Claude Code a browser that thinks.**
10
6
 
@@ -22,25 +18,29 @@ Return static text. No interaction, no login, no dynamic content. Great for quic
22
18
  ### 2. Browser Automation (browser-use, Puppeteer MCP, Playwright MCP)
23
19
  Can interact with pages, but use a **one-agent-do-all** approach: the same reasoning model that's writing your code is also deciding where to click, what to type, and how to navigate. This overwhelms the context window and fragments focus.
24
20
 
25
- ### 3. Comet MCP: Multi-Agent Delegation
26
- **Comet MCP takes a different approach.** Instead of Claude controlling a browser directly, it delegates to [Perplexity Comet](https://www.perplexity.ai/comet) - an AI purpose-built for web research and browsing.
21
+ ### 3. Comet MCP: Direct Control + AI Delegation
22
+ **Comet MCP gives you both.** Direct DOM interaction for deterministic tasks (clicking, typing, form filling) AND delegation to [Perplexity Comet](https://www.perplexity.ai/comet) for AI-powered research and complex browsing.
23
+
24
+ - **Direct mode** — `comet_interact` and `comet_navigate` give Claude instant, precise control over any web page via CDP. No AI middleman. Click buttons, fill forms, read content in milliseconds
25
+ - **AI mode** — `comet_ask` delegates to Perplexity for research, summarization, and complex browsing decisions
26
+ - **Sidecar mode** — `comet_ask` with `sidecar: true` keeps the current page visible and uses the Perplexity Assistant sidebar for page-aware Q&A
27
+ - **Result**: Deterministic precision when you need it, AI intelligence when you want it
27
28
 
28
- - **Claude** stays focused on your coding task
29
- - **Comet** handles the browsing: navigation, login walls, dynamic content, deep research
30
- - **Result**: Claude's coding intelligence + Perplexity's web intelligence, working together
29
+ > **For full toolkit installation, see the [canonical install guide](../docs/INSTALLATION.md).**
30
+ > Run `npx comet-browser-automation install` from any compatible repo to set up all configuration automatically.
31
31
 
32
32
  ## Quick Start
33
33
 
34
34
  ### 1. Configure Claude Code
35
35
 
36
- Add to `~/.claude.json` or `.mcp.json`:
36
+ Add to `~/.claude/settings.json` or `.mcp.json`:
37
37
 
38
38
  ```json
39
39
  {
40
40
  "mcpServers": {
41
41
  "comet-bridge": {
42
42
  "command": "npx",
43
- "args": ["-y", "comet-mcp"]
43
+ "args": ["-y", "@shawnowen/comet-mcp"]
44
44
  }
45
45
  }
46
46
  }
@@ -62,17 +62,79 @@ You: "Log into my GitHub and check my notifications"
62
62
  Claude: [Comet handles the login flow and navigation]
63
63
  ```
64
64
 
65
- ## Tools
65
+ ## Tools (25)
66
+
67
+ ### Direct Interaction (2) — No AI, pure CDP
68
+
69
+ | Tool | Description |
70
+ |------|-------------|
71
+ | `comet_interact` | Click, type, fill, check/uncheck, select, scroll, extract text, or run JS on the active page. Supports chaining multiple actions in one call |
72
+ | `comet_navigate` | Navigate the active tab to any URL (QBO, Mercury, GitHub, etc.) without opening Perplexity |
73
+
74
+ **`comet_interact` actions:** `click`, `type`, `fill`, `press`, `check`, `uncheck`, `select`, `scroll`, `wait`, `extract`, `evaluate`
75
+
76
+ ### AI-Assisted Browsing (9)
66
77
 
67
78
  | Tool | Description |
68
79
  |------|-------------|
69
80
  | `comet_connect` | Connect to Comet (auto-starts if needed) |
70
- | `comet_ask` | Send a task and wait for response |
81
+ | `comet_ask` | Send a task to Perplexity and wait for response. Use `sidecar: true` to keep current page and ask via the assistant sidebar |
71
82
  | `comet_poll` | Check progress on long-running tasks |
72
83
  | `comet_stop` | Stop current task |
73
84
  | `comet_screenshot` | Capture current page |
74
85
  | `comet_mode` | Switch modes: search, research, labs, learn |
75
- | `comet_tab_groups` | Manage Chrome tab groups (list, create, update, delete) |
86
+ | `comet_shortcut` | Trigger a Comet Query Shortcut |
87
+ | `comet_read_page` | Extract page as accessibility tree + clean text |
88
+ | `comet_wait_for_idle` | Wait for network activity to settle |
89
+
90
+ ### Tab Groups (1)
91
+
92
+ | Tool | Description |
93
+ |------|-------------|
94
+ | `comet_tab_groups` | Manage Chrome tab groups (list, create, update, delete, archive, restore) |
95
+
96
+ ### Lifecycle (4)
97
+
98
+ | Tool | Description |
99
+ |------|-------------|
100
+ | `comet_lifecycle_start` | Register a new lifecycle run |
101
+ | `comet_lifecycle_complete` | Mark a run as completed |
102
+ | `comet_lifecycle_abort` | Abort a run with optional reason |
103
+ | `comet_lifecycle_update` | Update run metadata |
104
+
105
+ Lifecycle tools are binding-aware: `start` attaches the run ID to the active or supplied Codex window binding, `complete` transitions the binding to `completed`, and `abort` transitions it to `stale`.
106
+
107
+ ### Orchestration (3)
108
+
109
+ | Tool | Description |
110
+ |------|-------------|
111
+ | `comet_task_status` | Get unified status (session manifest + extension events + lifecycle) |
112
+ | `comet_delegate` | High-level task dispatch with direct Codex binding creation/reuse, URL, and lifecycle setup |
113
+ | `comet_observe` | Passively observe browser state, binding owners, and stale/conflict/unbound windows without disrupting active agents |
114
+
115
+ ## Codex Window Bindings
116
+
117
+ Comet MCP routes multi-agent browser work through Codex window bindings. Each binding records the owning Codex session, repo/worktree, branch, `windowId`, `tabGroupId`, `targetId`, sidecar context key, lifecycle run IDs, and status.
118
+
119
+ Binding rules:
120
+
121
+ - Normal session agents can operate only on their own binding.
122
+ - Worktree and fleet orchestrators can observe wider scopes, but cross-session mutation requires explicit target binding, reason, and audit.
123
+ - `comet_task_status`, `comet_peek`, sidecar artifacts, lifecycle transitions, and delegation all resolve through the binding index instead of the currently focused window.
124
+ - `comet_delegate` no longer shells out to the legacy session controller. It creates or reuses a binding directly and returns `bindingId`, `windowId`, `tabGroupId`, and `dispatchStatus`.
125
+
126
+ Focus-independent routing means browser actions use the resolved binding's `targetId`, `windowId`, and `tabGroupId`, not whatever Comet window happens to be frontmost. Orchestrator-only cross-session actions must carry the target binding and audit reason so ordinary agents cannot accidentally mutate another agent's window.
127
+
128
+ ### Choosing the Right Tool
129
+
130
+ | Task | Use | Why |
131
+ |------|-----|-----|
132
+ | Click buttons, fill forms | `comet_interact` | Direct CDP, milliseconds |
133
+ | Navigate to a URL | `comet_navigate` | Immediate, no AI |
134
+ | Read page content | `comet_read_page` | Structured a11y tree |
135
+ | Research a topic | `comet_ask` | Perplexity's strength |
136
+ | "Summarize this page" | `comet_ask(sidecar: true)` | Sidecar sees active tab |
137
+ | Take a screenshot | `comet_screenshot` | Direct CDP capture |
76
138
 
77
139
  ## Tab Groups
78
140
 
@@ -110,7 +172,8 @@ Claude Code → MCP Server → CDP → Extension Service Worker → chrome.tabGr
110
172
  For environments that can't use MCP directly (e.g., sandboxed VMs), an HTTP server exposes all tools as REST endpoints:
111
173
 
112
174
  ```bash
113
- npm run build && npm run http
175
+ npx @shawnowen/comet-mcp --http
176
+ # Or if installed locally: npm run http
114
177
  # Starts on http://localhost:3456
115
178
  ```
116
179
 
@@ -123,7 +186,11 @@ Claude Code → MCP Server → CDP → Comet Browser → Perplexity AI
123
186
  (reasoning) (bridge) (web browsing)
124
187
  ```
125
188
 
126
- Claude sends high-level goals ("research X", "log into Y"). Comet figures out the clicks, scrolls, and searches. Results flow back to Claude.
189
+ **Direct mode**: Claude sends DOM actions (`comet_interact`, `comet_navigate`) CDP executes them instantly on the active page.
190
+
191
+ **AI mode**: Claude sends high-level goals (`comet_ask`) → Comet/Perplexity figures out the clicks, scrolls, and searches → results flow back.
192
+
193
+ **Sidecar mode**: Claude asks about the current page (`comet_ask` with `sidecar: true`) → Perplexity Assistant sees and interacts with the active tab → results flow back.
127
194
 
128
195
  ## Requirements
129
196
 
@@ -164,7 +231,7 @@ If Comet is installed in a non-standard location:
164
231
  "mcpServers": {
165
232
  "comet-bridge": {
166
233
  "command": "npx",
167
- "args": ["-y", "comet-mcp"],
234
+ "args": ["-y", "@shawnowen/comet-mcp"],
168
235
  "env": {
169
236
  "COMET_PATH": "/path/to/your/Comet"
170
237
  }
@@ -193,4 +260,4 @@ MIT
193
260
 
194
261
  ---
195
262
 
196
- [Report Issues](https://github.com/hanzili/comet-mcp/issues) · [Contribute](https://github.com/hanzili/comet-mcp)
263
+ [Report Issues](https://github.com/EQUAStart/equa-comet-browser-control/issues) · [Contribute](https://github.com/EQUAStart/equa-comet-browser-control)
@@ -0,0 +1,23 @@
1
+ import type { ErrorEvent, ErrorEventType, ErrorSeverity } from "./types.js";
2
+ declare const mcpErrorQueue: ErrorEvent[];
3
+ export interface DispatchOptions {
4
+ type: ErrorEventType;
5
+ message: string;
6
+ consumerId?: string | null;
7
+ sessionKey?: string | null;
8
+ context?: Record<string, unknown>;
9
+ suggestedAction?: string;
10
+ severity?: ErrorSeverity;
11
+ }
12
+ export declare function dispatchAlert(options: DispatchOptions): ErrorEvent;
13
+ /**
14
+ * Drain the MCP error queue — call on each tool response to append alerts.
15
+ * Returns queued alerts and clears the queue.
16
+ */
17
+ export declare function drainMcpAlertQueue(): ErrorEvent[];
18
+ /**
19
+ * Format drained alerts as text to append to MCP tool responses.
20
+ */
21
+ export declare function formatAlertsForResponse(events: ErrorEvent[]): string;
22
+ export { mcpErrorQueue };
23
+ //# sourceMappingURL=alert-dispatcher.d.ts.map
@@ -0,0 +1,101 @@
1
+ // Alert Dispatcher — three-channel alert system (Spec 016, FR-011)
2
+ // Channels: macOS notification, JSONL log file, in-memory MCP error queue
3
+ // Routes alerts by severity and consumer role per BridgeConfig.alertRouting
4
+ import { appendFileSync, mkdirSync } from "fs";
5
+ import { execFile } from "child_process";
6
+ import { dirname } from "path";
7
+ import { randomUUID } from "crypto";
8
+ import { loadBridgeConfig } from "./bridge-config.js";
9
+ // In-memory MCP error queue — drained on next tool response
10
+ const mcpErrorQueue = [];
11
+ // Suggested actions by error type
12
+ const SUGGESTED_ACTIONS = {
13
+ PERPLEXITY_VOICE_MODE: "Start a fresh Perplexity session by calling comet_connect with a new taskThreadId",
14
+ PERPLEXITY_IDLE_NO_NAV: "Retry the task. If persistent, use comet_stop then start a fresh session via comet_connect",
15
+ PERPLEXITY_CONTEXT_BLEED: "Start a fresh Perplexity session to prevent context from prior conversation bleeding into this task",
16
+ BROWSER_CRASH: "Browser is restarting automatically. Reconnect with comet_connect after restart completes",
17
+ CRASH_LOOP_CAP: "Automatic restart halted after 3 attempts in 10 minutes. Manual intervention required",
18
+ CDP_DISCONNECT: "Connection lost. Call comet_connect to re-establish",
19
+ ORPHAN_DETECTED: "Task Thread will be snapshot-closed at threshold",
20
+ ORPHAN_REAPED: "Task Thread snapshot-closed. Check snapshots directory for recovery",
21
+ DUPLICATE_PROCESS: "Multiple comet-mcp processes detected. Review and terminate extras",
22
+ PROTOCOL_VIOLATION: "Missing prerequisite step. Check error message for required sequence",
23
+ };
24
+ // Default severity by error type
25
+ const DEFAULT_SEVERITY = {
26
+ BROWSER_CRASH: "critical",
27
+ CRASH_LOOP_CAP: "critical",
28
+ DUPLICATE_PROCESS: "operational",
29
+ ORPHAN_DETECTED: "operational",
30
+ ORPHAN_REAPED: "operational",
31
+ PERPLEXITY_VOICE_MODE: "warning",
32
+ PERPLEXITY_IDLE_NO_NAV: "warning",
33
+ PERPLEXITY_CONTEXT_BLEED: "warning",
34
+ CDP_DISCONNECT: "warning",
35
+ PROTOCOL_VIOLATION: "warning",
36
+ };
37
+ export function dispatchAlert(options) {
38
+ const config = loadBridgeConfig();
39
+ const severity = options.severity || DEFAULT_SEVERITY[options.type] || "warning";
40
+ const event = {
41
+ id: randomUUID(),
42
+ timestamp: new Date().toISOString(),
43
+ type: options.type,
44
+ severity,
45
+ consumerId: options.consumerId ?? null,
46
+ sessionKey: options.sessionKey ?? null,
47
+ message: options.message,
48
+ context: options.context || {},
49
+ suggestedAction: options.suggestedAction || SUGGESTED_ACTIONS[options.type] || "",
50
+ delivered: { macosNotification: false, logFile: false, mcpQueue: false },
51
+ };
52
+ // Channel 1: JSONL log (always — durable record)
53
+ try {
54
+ mkdirSync(dirname(config.alerts.logPath), { recursive: true });
55
+ appendFileSync(config.alerts.logPath, JSON.stringify(event) + "\n");
56
+ event.delivered.logFile = true;
57
+ }
58
+ catch (logErr) {
59
+ console.error(`[comet-bridge] ALERT LOG FAILED: Could not write to ${config.alerts.logPath}: ${logErr instanceof Error ? logErr.message : logErr}`);
60
+ }
61
+ // Channel 2: macOS notification (if enabled and severity warrants)
62
+ if (config.alerts.macosNotifications && severity !== "warning") {
63
+ const title = `Comet Bridge [${severity.toUpperCase()}]`;
64
+ const msg = event.message.substring(0, 200);
65
+ // execFile is safe — no shell injection (arguments are array, not interpolated)
66
+ execFile("osascript", ["-e", `display notification "${msg.replace(/"/g, '\\"')}" with title "${title}"`], (err) => {
67
+ if (!err) {
68
+ event.delivered.macosNotification = true;
69
+ }
70
+ else if (severity === "critical") {
71
+ console.error(`[comet-bridge] macOS notification failed for ${severity} alert: ${err.message}`);
72
+ }
73
+ });
74
+ }
75
+ // Channel 3: MCP error queue (if enabled)
76
+ if (config.alerts.mcpErrorQueue) {
77
+ mcpErrorQueue.push(event);
78
+ event.delivered.mcpQueue = true;
79
+ }
80
+ return event;
81
+ }
82
+ /**
83
+ * Drain the MCP error queue — call on each tool response to append alerts.
84
+ * Returns queued alerts and clears the queue.
85
+ */
86
+ export function drainMcpAlertQueue() {
87
+ const events = [...mcpErrorQueue];
88
+ mcpErrorQueue.length = 0;
89
+ return events;
90
+ }
91
+ /**
92
+ * Format drained alerts as text to append to MCP tool responses.
93
+ */
94
+ export function formatAlertsForResponse(events) {
95
+ if (events.length === 0)
96
+ return "";
97
+ const lines = events.map((e) => `⚠️ [${e.severity.toUpperCase()}] ${e.type}: ${e.message}`);
98
+ return "\n\n--- Queued Alerts ---\n" + lines.join("\n");
99
+ }
100
+ export { mcpErrorQueue };
101
+ //# sourceMappingURL=alert-dispatcher.js.map
@@ -0,0 +1,23 @@
1
+ import type { AgentSession } from "./types.js";
2
+ import { type CodexIdentityInput, type CodexSessionIdentity, type CodexWindowBinding, type CodexWindowBindingStore } from "./window-bindings.js";
3
+ export type BoundSessionErrorCode = "MISSING_SESSION" | "MISSING_IDENTITY" | "MISSING_BINDING" | "STALE_BINDING" | "CONFLICT_BINDING" | "OWNERSHIP_VIOLATION";
4
+ export declare class BoundSessionError extends Error {
5
+ readonly code: BoundSessionErrorCode;
6
+ readonly repairAction: string;
7
+ readonly status: number;
8
+ constructor(code: BoundSessionErrorCode, message: string, repairAction: string, status?: number);
9
+ toMcpText(toolName: string): string;
10
+ }
11
+ export interface BoundSessionResolution {
12
+ session: AgentSession;
13
+ identity: CodexSessionIdentity;
14
+ binding: CodexWindowBinding;
15
+ }
16
+ export interface BoundHttpResolution {
17
+ identity: CodexSessionIdentity;
18
+ binding: CodexWindowBinding;
19
+ }
20
+ export declare function validateBoundTargetHints(binding: CodexWindowBinding, args?: Record<string, unknown>): void;
21
+ export declare function resolveBoundSession(session: AgentSession | undefined, args?: Record<string, unknown>, store?: CodexWindowBindingStore): Promise<BoundSessionResolution>;
22
+ export declare function resolveHttpBoundSession(input: CodexIdentityInput, args?: Record<string, unknown>, store?: CodexWindowBindingStore): Promise<BoundHttpResolution>;
23
+ //# sourceMappingURL=bound-session.d.ts.map
@@ -0,0 +1,119 @@
1
+ import { assertBindingProfileAllowed, deriveCodexSessionIdentity, isBindingInWorktreeScope, windowBindingStore, } from "./window-bindings.js";
2
+ export class BoundSessionError extends Error {
3
+ code;
4
+ repairAction;
5
+ status;
6
+ constructor(code, message, repairAction, status = 409) {
7
+ super(message);
8
+ this.code = code;
9
+ this.repairAction = repairAction;
10
+ this.status = status;
11
+ this.name = "BoundSessionError";
12
+ }
13
+ toMcpText(toolName) {
14
+ return [
15
+ `Binding error (${this.code}) before ${toolName}: ${this.message}`,
16
+ `Safe repair action: ${this.repairAction}`,
17
+ ].join("\n\n");
18
+ }
19
+ }
20
+ const TARGET_HINT_KEYS = ["targetId"];
21
+ const WINDOW_HINT_KEYS = ["windowId"];
22
+ const TAB_GROUP_HINT_KEYS = ["tabGroupId", "groupId"];
23
+ function stringValue(value) {
24
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
25
+ }
26
+ function numberValue(value) {
27
+ if (typeof value === "number" && Number.isFinite(value))
28
+ return value;
29
+ if (typeof value === "string" && value.trim().length > 0) {
30
+ const parsed = Number(value);
31
+ if (Number.isFinite(parsed))
32
+ return parsed;
33
+ }
34
+ return undefined;
35
+ }
36
+ function getHintArgs(args) {
37
+ return args ?? {};
38
+ }
39
+ export function validateBoundTargetHints(binding, args) {
40
+ const hints = getHintArgs(args);
41
+ for (const key of TARGET_HINT_KEYS) {
42
+ const targetId = stringValue(hints[key]);
43
+ if (targetId && targetId !== binding.targetId) {
44
+ throw new BoundSessionError("OWNERSHIP_VIOLATION", `${key}=${targetId} is outside binding ${binding.bindingId}`, binding.targetId
45
+ ? `Use the bound targetId ${binding.targetId} or reconnect with comet_connect.`
46
+ : "Reconnect with comet_connect so the binding has a targetId before targeting a page.");
47
+ }
48
+ }
49
+ for (const key of WINDOW_HINT_KEYS) {
50
+ const windowId = numberValue(hints[key]);
51
+ if (windowId !== undefined && windowId !== binding.windowId) {
52
+ throw new BoundSessionError("OWNERSHIP_VIOLATION", `${key}=${windowId} is outside binding ${binding.bindingId}`, `Use the bound windowId ${binding.windowId} or reconnect with comet_connect.`);
53
+ }
54
+ }
55
+ for (const key of TAB_GROUP_HINT_KEYS) {
56
+ const tabGroupId = numberValue(hints[key]);
57
+ if (tabGroupId !== undefined && tabGroupId !== binding.tabGroupId) {
58
+ throw new BoundSessionError("OWNERSHIP_VIOLATION", `${key}=${tabGroupId} is outside binding ${binding.bindingId}`, binding.tabGroupId !== null
59
+ ? `Use the bound tabGroupId ${binding.tabGroupId} or reconnect with comet_connect.`
60
+ : "Reconnect with comet_connect so the binding has a tabGroupId before targeting a group.");
61
+ }
62
+ }
63
+ const sessionKey = stringValue(hints.sessionKey);
64
+ if (sessionKey && sessionKey !== binding.sessionKey) {
65
+ throw new BoundSessionError("OWNERSHIP_VIOLATION", `sessionKey=${sessionKey} is outside binding ${binding.bindingId}`, `Use the bound sessionKey ${binding.sessionKey}.`);
66
+ }
67
+ const projectThreadId = stringValue(hints.projectThreadId) ?? stringValue(hints.threadId);
68
+ if (projectThreadId && projectThreadId !== binding.projectThreadId) {
69
+ throw new BoundSessionError("OWNERSHIP_VIOLATION", `projectThreadId=${projectThreadId} is outside binding ${binding.bindingId}`, `Use the bound projectThreadId ${binding.projectThreadId}.`);
70
+ }
71
+ }
72
+ async function resolveBindingByArgs(store, identity, args) {
73
+ const bindingId = stringValue(args?.bindingId);
74
+ if (bindingId)
75
+ return store.get(bindingId);
76
+ const runId = stringValue(args?.runId);
77
+ if (runId)
78
+ return store.findByRunId(runId);
79
+ return store.findActiveByIdentity(identity);
80
+ }
81
+ function assertBindingUsable(identity, binding) {
82
+ if (!binding) {
83
+ throw new BoundSessionError("MISSING_BINDING", "No active Codex window binding exists for this caller.", "Call comet_connect with Codex identity fields to create or repair the binding.");
84
+ }
85
+ if (binding.status === "stale" || binding.status === "reaped" || binding.status === "completed") {
86
+ throw new BoundSessionError("STALE_BINDING", `Binding ${binding.bindingId} is ${binding.status}.`, "Call comet_connect to repair or replace the stale binding.");
87
+ }
88
+ if (binding.status === "conflict") {
89
+ throw new BoundSessionError("CONFLICT_BINDING", `Binding ${binding.bindingId} is in conflict.`, "Resolve the conflicting window ownership before using browser tools.");
90
+ }
91
+ if (!isBindingInWorktreeScope(identity, binding)) {
92
+ throw new BoundSessionError("OWNERSHIP_VIOLATION", `Caller ${identity.sessionKey} is not authorized for binding ${binding.bindingId}.`, "Use the caller's own binding or an authorized orchestrator identity.");
93
+ }
94
+ try {
95
+ assertBindingProfileAllowed(identity, binding);
96
+ }
97
+ catch (err) {
98
+ throw new BoundSessionError("OWNERSHIP_VIOLATION", err instanceof Error ? err.message : String(err), "Use an agent-owned Comet runtime profile or reconnect with comet_connect.", 403);
99
+ }
100
+ return binding;
101
+ }
102
+ export async function resolveBoundSession(session, args, store = windowBindingStore) {
103
+ if (!session) {
104
+ throw new BoundSessionError("MISSING_SESSION", "No active session is registered.", "Call comet_connect before using bound browser tools.");
105
+ }
106
+ if (!session.codexIdentity) {
107
+ throw new BoundSessionError("MISSING_IDENTITY", `Session ${session.sessionKey} has no Codex identity metadata.`, "Reconnect with comet_connect so Codex identity can be derived and persisted.");
108
+ }
109
+ const binding = assertBindingUsable(session.codexIdentity, await resolveBindingByArgs(store, session.codexIdentity, args));
110
+ validateBoundTargetHints(binding, args);
111
+ return { session, identity: session.codexIdentity, binding };
112
+ }
113
+ export async function resolveHttpBoundSession(input, args, store = windowBindingStore) {
114
+ const identity = deriveCodexSessionIdentity({ ...input, strict: true });
115
+ const binding = assertBindingUsable(identity, await resolveBindingByArgs(store, identity, args));
116
+ validateBoundTargetHints(binding, args);
117
+ return { identity, binding };
118
+ }
119
+ //# sourceMappingURL=bound-session.js.map
@@ -0,0 +1,6 @@
1
+ import type { BridgeConfig } from "./types.js";
2
+ declare const CONFIG_PATH: string;
3
+ declare const DEFAULTS: BridgeConfig;
4
+ export declare function loadBridgeConfig(): BridgeConfig;
5
+ export { CONFIG_PATH, DEFAULTS };
6
+ //# sourceMappingURL=bridge-config.d.ts.map
@@ -0,0 +1,78 @@
1
+ // Bridge Configuration — unified config for MCP server, extension, and scripts (Spec 016, FR-013)
2
+ // Reads from ~/.claude/comet-browser/bridge-config.json with typed defaults.
3
+ // Re-reads on each access — no caching, so config changes take effect on next tool call.
4
+ import { readFileSync } from "fs";
5
+ import { homedir } from "os";
6
+ import { join } from "path";
7
+ const CONFIG_PATH = join(homedir(), ".claude", "comet-browser", "bridge-config.json");
8
+ const DEFAULTS = {
9
+ version: "1.0.0",
10
+ cleanup: {
11
+ orphanThresholdMinutes: 120,
12
+ snapshotBeforeClose: true,
13
+ snapshotDir: join(homedir(), ".claude", "comet-browser", "snapshots"),
14
+ },
15
+ crashRecovery: {
16
+ maxRestarts: 3,
17
+ windowMinutes: 10,
18
+ autoRestart: true,
19
+ crashHistoryPath: join(homedir(), ".claude", "comet-browser", "crash-history.json"),
20
+ },
21
+ alerts: {
22
+ logPath: join(homedir(), ".local", "log", "comet-alerts.log"),
23
+ macosNotifications: true,
24
+ mcpErrorQueue: true,
25
+ routing: {
26
+ critical: "operator-direct",
27
+ operational: "orchestrator-first",
28
+ warning: "orchestrator-only",
29
+ },
30
+ },
31
+ roles: {
32
+ operatorId: "operator",
33
+ orchestratorAgentId: null,
34
+ },
35
+ protocolEnforcement: {
36
+ requireConnectBeforeBrowse: true,
37
+ warnMissingTabGroup: true,
38
+ },
39
+ };
40
+ export function loadBridgeConfig() {
41
+ try {
42
+ const raw = readFileSync(CONFIG_PATH, "utf-8");
43
+ try {
44
+ const parsed = JSON.parse(raw);
45
+ return deepMerge(DEFAULTS, parsed);
46
+ }
47
+ catch (parseErr) {
48
+ console.error(`[comet-bridge] CONFIG PARSE ERROR in ${CONFIG_PATH}: ${parseErr instanceof Error ? parseErr.message : parseErr}. Using defaults.`);
49
+ return { ...DEFAULTS };
50
+ }
51
+ }
52
+ catch (readErr) {
53
+ if (readErr?.code !== "ENOENT") {
54
+ console.error(`[comet-bridge] CONFIG READ ERROR: ${readErr?.message}. Using defaults.`);
55
+ }
56
+ return { ...DEFAULTS };
57
+ }
58
+ }
59
+ function deepMerge(defaults, overrides) {
60
+ const result = { ...defaults };
61
+ for (const key of Object.keys(overrides)) {
62
+ const val = overrides[key];
63
+ if (val !== undefined && val !== null) {
64
+ if (typeof val === "object" &&
65
+ !Array.isArray(val) &&
66
+ typeof result[key] === "object" &&
67
+ result[key] !== null) {
68
+ result[key] = deepMerge(result[key], val);
69
+ }
70
+ else {
71
+ result[key] = val;
72
+ }
73
+ }
74
+ }
75
+ return result;
76
+ }
77
+ export { CONFIG_PATH, DEFAULTS };
78
+ //# sourceMappingURL=bridge-config.js.map
@@ -1,3 +1,4 @@
1
+ import CDP from "chrome-remote-interface";
1
2
  import type { CDPTarget, CDPVersion, NavigateResult, ScreenshotResult, EvaluateResult, CometState, NetworkIdleResult } from "./types.js";
2
3
  export declare class CometCDPClient {
3
4
  private client;
@@ -8,6 +9,12 @@ export declare class CometCDPClient {
8
9
  private maxReconnectAttempts;
9
10
  private isReconnecting;
10
11
  get isConnected(): boolean;
12
+ /**
13
+ * Access the raw CDP protocol client for direct domain calls
14
+ * (e.g., Page.printToPDF, Network.enable, Input.dispatchKeyEvent).
15
+ * Throws if not connected — always check isConnected or call connect() first.
16
+ */
17
+ get protocol(): CDP.Client;
11
18
  /**
12
19
  * Health check - verify connection is actually alive (not just "connected" in state)
13
20
  * This catches cases where WebSocket died silently
@@ -41,15 +48,26 @@ export declare class CometCDPClient {
41
48
  * Check if Comet process is running
42
49
  */
43
50
  private isCometProcessRunning;
44
- /**
45
- * Kill any running Comet process
46
- */
47
- private killComet;
51
+ private readCrashHistory;
52
+ private writeCrashHistory;
53
+ recordCrash(): void;
54
+ checkCrashLoopCap(): {
55
+ blocked: boolean;
56
+ count: number;
57
+ windowMinutes: number;
58
+ };
48
59
  /**
49
60
  * Start Comet browser with remote debugging enabled
50
61
  * Handles macOS, Windows, and WSL environments
51
62
  */
52
63
  startComet(port?: number): Promise<string>;
64
+ /**
65
+ * Position the Comet browser window on the top display using full-screen bounds.
66
+ * Top display staging bounds: origin (0, -1440), size 2560x1440.
67
+ * Keep the native window state normal so browser tabs and toolbar remain visible.
68
+ * Uses CDP Browser.getWindowForTarget + Browser.setWindowBounds.
69
+ */
70
+ positionOnTopDisplay(targetId?: string): Promise<void>;
53
71
  /**
54
72
  * Get CDP version info
55
73
  */
@@ -58,6 +76,9 @@ export declare class CometCDPClient {
58
76
  * List all available tabs/targets
59
77
  */
60
78
  listTargets(): Promise<CDPTarget[]>;
79
+ waitForTargetUrl(url: string, timeoutMs?: number): Promise<CDPTarget>;
80
+ private getWindowIdForTarget;
81
+ private findSidecarTargetForWindow;
61
82
  /**
62
83
  * Connect to a specific tab
63
84
  */
@@ -89,6 +110,21 @@ export declare class CometCDPClient {
89
110
  * Press a key
90
111
  */
91
112
  pressKey(key: string): Promise<void>;
113
+ /**
114
+ * Return the existing Perplexity sidecar CDP target without activating Comet
115
+ * or sending OS-level keyboard shortcuts.
116
+ */
117
+ ensureSidecarOpen(options?: {
118
+ windowId?: number;
119
+ targetId?: string;
120
+ }): Promise<CDPTarget | null>;
121
+ /**
122
+ * Connect a separate CDP client to the sidecar target for isolated interaction.
123
+ */
124
+ connectToSidecar(options?: {
125
+ windowId?: number;
126
+ targetId?: string;
127
+ }): Promise<CometCDPClient | null>;
92
128
  /**
93
129
  * Create a new tab
94
130
  */