@shawnowen/comet-mcp 2.3.1 → 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.
- package/README.md +86 -19
- package/dist/alert-dispatcher.d.ts +23 -0
- package/dist/alert-dispatcher.js +101 -0
- package/dist/bound-session.d.ts +23 -0
- package/dist/bound-session.js +119 -0
- package/dist/bridge-config.d.ts +6 -0
- package/dist/bridge-config.js +78 -0
- package/dist/cdp-client.d.ts +40 -4
- package/dist/cdp-client.js +502 -155
- package/dist/comet-ai.d.ts +15 -0
- package/dist/comet-ai.js +114 -38
- package/dist/delegate-binding.d.ts +19 -0
- package/dist/delegate-binding.js +73 -0
- package/dist/discovery/capability-entry.d.ts +215 -0
- package/dist/discovery/capability-entry.js +13 -0
- package/dist/discovery/description-template.d.ts +40 -0
- package/dist/discovery/description-template.js +61 -0
- package/dist/discovery/golden-queries.fixture.d.ts +22 -0
- package/dist/discovery/golden-queries.fixture.js +137 -0
- package/dist/discovery/mcp-source.d.ts +38 -0
- package/dist/discovery/mcp-source.js +70 -0
- package/dist/discovery/metadata-completeness.d.ts +48 -0
- package/dist/discovery/metadata-completeness.js +83 -0
- package/dist/discovery/registry.d.ts +35 -0
- package/dist/discovery/registry.js +35 -0
- package/dist/discovery/safety.d.ts +44 -0
- package/dist/discovery/safety.js +59 -0
- package/dist/discovery/schema-validator.d.ts +36 -0
- package/dist/discovery/schema-validator.js +257 -0
- package/dist/discovery/source-error.d.ts +47 -0
- package/dist/discovery/source-error.js +95 -0
- package/dist/discovery/tool-meta.d.ts +41 -0
- package/dist/discovery/tool-meta.js +229 -0
- package/dist/discovery/virtual-tools.d.ts +20 -0
- package/dist/discovery/virtual-tools.js +69 -0
- package/dist/http-server.js +2067 -47
- package/dist/index.js +3163 -710
- package/dist/observer.d.ts +47 -0
- package/dist/observer.js +516 -0
- package/dist/session-registry.d.ts +57 -0
- package/dist/session-registry.js +500 -0
- package/dist/sidecar-artifacts.d.ts +49 -0
- package/dist/sidecar-artifacts.js +146 -0
- package/dist/snapshot-capture.d.ts +3 -0
- package/dist/snapshot-capture.js +91 -0
- package/dist/tab-group-archive.js +3 -1
- package/dist/tab-groups.d.ts +7 -0
- package/dist/tab-groups.js +21 -3
- package/dist/task-thread-aggregator.d.ts +34 -0
- package/dist/task-thread-aggregator.js +480 -0
- package/dist/task-thread-canonical.d.ts +142 -0
- package/dist/task-thread-canonical.js +116 -0
- package/dist/types.d.ts +237 -0
- package/dist/window-bindings.d.ts +112 -0
- package/dist/window-bindings.js +476 -0
- package/extension/background.js +1556 -300
- package/extension/icons/icon.svg +9 -0
- package/extension/icons/icon128.png +0 -0
- package/extension/icons/icon16.png +0 -0
- package/extension/icons/icon48.png +0 -0
- package/extension/manifest.json +19 -4
- package/extension/session-logic.js +2383 -0
- package/extension/session-manager.html +299 -0
- package/extension/sidepanel.css +5323 -528
- package/extension/sidepanel.html +282 -2
- package/extension/sidepanel.js +10075 -951
- package/extension/window-policy.js +162 -0
- package/package.json +10 -7
- package/vendor/lifecycle-mcp-adapter.mjs +103 -0
- package/vendor/lifecycle-metadata.mjs +252 -0
- package/vendor/readiness-report.mjs +742 -0
- package/dist/cdp-client.d.ts.map +0 -1
- package/dist/cdp-client.js.map +0 -1
- package/dist/comet-ai.d.ts.map +0 -1
- package/dist/comet-ai.js.map +0 -1
- package/dist/http-server.d.ts.map +0 -1
- package/dist/http-server.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/tab-group-archive.d.ts.map +0 -1
- package/dist/tab-group-archive.js.map +0 -1
- package/dist/tab-groups.d.ts.map +0 -1
- package/dist/tab-groups.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
# comet-mcp
|
|
2
2
|
|
|
3
|
-
[](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
|
+
[](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:
|
|
26
|
-
**Comet MCP
|
|
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
|
-
|
|
29
|
-
-
|
|
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
|
-
| `
|
|
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
|
-
|
|
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
|
|
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/
|
|
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,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
|
package/dist/cdp-client.d.ts
CHANGED
|
@@ -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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
*/
|