@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/dist/comet-ai.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { CometCDPClient } from "./cdp-client.js";
|
|
1
2
|
export declare class CometAI {
|
|
3
|
+
private cdp;
|
|
4
|
+
constructor(cdpClient?: CometCDPClient);
|
|
2
5
|
/**
|
|
3
6
|
* Find the first matching element from a list of selectors
|
|
4
7
|
*/
|
|
@@ -26,6 +29,18 @@ export declare class CometAI {
|
|
|
26
29
|
hasStopButton: boolean;
|
|
27
30
|
agentBrowsingUrl: string;
|
|
28
31
|
}>;
|
|
32
|
+
/**
|
|
33
|
+
* Detect voice/listening mode — Perplexity entered microphone mode instead of processing text.
|
|
34
|
+
*/
|
|
35
|
+
detectVoiceMode(): Promise<boolean>;
|
|
36
|
+
/**
|
|
37
|
+
* Detect IDLE without navigation — task returned IDLE but never left the Perplexity home page.
|
|
38
|
+
*/
|
|
39
|
+
detectIdleNoNavigation(initialUrl?: string): Promise<boolean>;
|
|
40
|
+
/**
|
|
41
|
+
* Detect context bleed — response content URL doesn't match the requested URL.
|
|
42
|
+
*/
|
|
43
|
+
detectContextBleed(requestedUrl?: string): Promise<boolean>;
|
|
29
44
|
/**
|
|
30
45
|
* Stop the current agent task
|
|
31
46
|
*/
|
package/dist/comet-ai.js
CHANGED
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
// Comet AI interaction module
|
|
2
2
|
// Handles sending prompts to Comet's AI assistant and reading responses
|
|
3
|
-
import { cometClient } from "./cdp-client.js";
|
|
3
|
+
import { cometClient as globalCometClient } from "./cdp-client.js";
|
|
4
4
|
// Input selectors - contenteditable div is primary for Perplexity
|
|
5
5
|
const INPUT_SELECTORS = [
|
|
6
6
|
'[contenteditable="true"]',
|
|
7
7
|
'textarea[placeholder*="Ask"]',
|
|
8
8
|
'textarea[placeholder*="Search"]',
|
|
9
|
-
|
|
9
|
+
"textarea",
|
|
10
10
|
'input[type="text"]',
|
|
11
11
|
];
|
|
12
12
|
export class CometAI {
|
|
13
|
+
cdp;
|
|
14
|
+
constructor(cdpClient) {
|
|
15
|
+
this.cdp = cdpClient || globalCometClient;
|
|
16
|
+
}
|
|
13
17
|
/**
|
|
14
18
|
* Find the first matching element from a list of selectors
|
|
15
19
|
*/
|
|
16
20
|
async findInputElement() {
|
|
17
21
|
for (const selector of INPUT_SELECTORS) {
|
|
18
|
-
const result = await
|
|
22
|
+
const result = await this.cdp.evaluate(`
|
|
19
23
|
document.querySelector(${JSON.stringify(selector)}) !== null
|
|
20
24
|
`);
|
|
21
25
|
if (result.result.value === true) {
|
|
@@ -33,7 +37,7 @@ export class CometAI {
|
|
|
33
37
|
throw new Error("Could not find input element. Navigate to Perplexity first.");
|
|
34
38
|
}
|
|
35
39
|
// Use execCommand for contenteditable elements (works with React/Vue)
|
|
36
|
-
const result = await
|
|
40
|
+
const result = await this.cdp.evaluate(`
|
|
37
41
|
(() => {
|
|
38
42
|
const el = document.querySelector('[contenteditable="true"]');
|
|
39
43
|
if (el) {
|
|
@@ -59,16 +63,16 @@ export class CometAI {
|
|
|
59
63
|
}
|
|
60
64
|
// Submit the prompt
|
|
61
65
|
await this.submitPrompt();
|
|
62
|
-
return `Prompt sent: "${prompt.substring(0, 50)}${prompt.length > 50 ?
|
|
66
|
+
return `Prompt sent: "${prompt.substring(0, 50)}${prompt.length > 50 ? "..." : ""}"`;
|
|
63
67
|
}
|
|
64
68
|
/**
|
|
65
69
|
* Submit the current prompt
|
|
66
70
|
*/
|
|
67
71
|
async submitPrompt() {
|
|
68
72
|
// Wait for React to process the typed content
|
|
69
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
73
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
70
74
|
// Verify text was typed before attempting submit
|
|
71
|
-
const hasContent = await
|
|
75
|
+
const hasContent = await this.cdp.evaluate(`
|
|
72
76
|
(() => {
|
|
73
77
|
const el = document.querySelector('[contenteditable="true"]');
|
|
74
78
|
if (el && el.innerText.trim().length > 0) return true;
|
|
@@ -81,17 +85,17 @@ export class CometAI {
|
|
|
81
85
|
throw new Error("Prompt text not found in input - typing may have failed");
|
|
82
86
|
}
|
|
83
87
|
// Strategy 1: Use Enter key (most reliable for Perplexity)
|
|
84
|
-
await
|
|
88
|
+
await this.cdp.evaluate(`
|
|
85
89
|
(() => {
|
|
86
90
|
const el = document.querySelector('[contenteditable="true"]') ||
|
|
87
91
|
document.querySelector('textarea');
|
|
88
92
|
if (el) el.focus();
|
|
89
93
|
})()
|
|
90
94
|
`);
|
|
91
|
-
await
|
|
92
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
95
|
+
await this.cdp.pressKey("Enter");
|
|
96
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
93
97
|
// Check if submission worked
|
|
94
|
-
const submitted = await
|
|
98
|
+
const submitted = await this.cdp.evaluate(`
|
|
95
99
|
(() => {
|
|
96
100
|
const el = document.querySelector('[contenteditable="true"]');
|
|
97
101
|
if (el && el.innerText.trim().length < 5) return true;
|
|
@@ -102,7 +106,7 @@ export class CometAI {
|
|
|
102
106
|
if (submitted.result.value)
|
|
103
107
|
return;
|
|
104
108
|
// Strategy 2: Click submit button
|
|
105
|
-
await
|
|
109
|
+
await this.cdp.evaluate(`
|
|
106
110
|
(() => {
|
|
107
111
|
const selectors = [
|
|
108
112
|
'button[aria-label*="Submit"]',
|
|
@@ -156,8 +160,8 @@ export class CometAI {
|
|
|
156
160
|
})()
|
|
157
161
|
`);
|
|
158
162
|
// Final check and retry with Enter if still not submitted
|
|
159
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
160
|
-
const finalCheck = await
|
|
163
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
164
|
+
const finalCheck = await this.cdp.evaluate(`
|
|
161
165
|
(() => {
|
|
162
166
|
const el = document.querySelector('[contenteditable="true"]');
|
|
163
167
|
if (el && el.innerText.trim().length < 5) return true;
|
|
@@ -168,7 +172,7 @@ export class CometAI {
|
|
|
168
172
|
`);
|
|
169
173
|
if (!finalCheck.result.value) {
|
|
170
174
|
// Last resort: try Enter one more time
|
|
171
|
-
await
|
|
175
|
+
await this.cdp.pressKey("Enter");
|
|
172
176
|
}
|
|
173
177
|
}
|
|
174
178
|
/**
|
|
@@ -180,7 +184,7 @@ export class CometAI {
|
|
|
180
184
|
throw new Error("Could not find input element. Navigate to Perplexity first.");
|
|
181
185
|
}
|
|
182
186
|
// Focus and clear the input
|
|
183
|
-
await
|
|
187
|
+
await this.cdp.evaluate(`
|
|
184
188
|
(() => {
|
|
185
189
|
const el = document.querySelector('[contenteditable="true"]');
|
|
186
190
|
if (el) {
|
|
@@ -199,7 +203,7 @@ export class CometAI {
|
|
|
199
203
|
`);
|
|
200
204
|
// Type the shortcut command
|
|
201
205
|
const shortcutCmd = `/${shortcut}`;
|
|
202
|
-
await
|
|
206
|
+
await this.cdp.evaluate(`
|
|
203
207
|
(() => {
|
|
204
208
|
const el = document.querySelector('[contenteditable="true"]');
|
|
205
209
|
if (el) {
|
|
@@ -218,13 +222,13 @@ export class CometAI {
|
|
|
218
222
|
})()
|
|
219
223
|
`);
|
|
220
224
|
// Wait for shortcut dropdown/autocomplete to appear
|
|
221
|
-
await new Promise(resolve => setTimeout(resolve, 800));
|
|
225
|
+
await new Promise((resolve) => setTimeout(resolve, 800));
|
|
222
226
|
// Press Enter to select the shortcut
|
|
223
|
-
await
|
|
227
|
+
await this.cdp.pressKey("Enter");
|
|
224
228
|
// If context is provided, type it after a brief pause
|
|
225
229
|
if (context) {
|
|
226
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
227
|
-
await
|
|
230
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
231
|
+
await this.cdp.evaluate(`
|
|
228
232
|
(() => {
|
|
229
233
|
const el = document.querySelector('[contenteditable="true"]');
|
|
230
234
|
if (el) {
|
|
@@ -250,26 +254,25 @@ export class CometAI {
|
|
|
250
254
|
`);
|
|
251
255
|
}
|
|
252
256
|
// Submit
|
|
253
|
-
await new Promise(resolve => setTimeout(resolve, 300));
|
|
254
|
-
await
|
|
255
|
-
return `Shortcut /${shortcut} sent${context ? ` with context: "${context.substring(0, 50)}${context.length > 50 ?
|
|
257
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
258
|
+
await this.cdp.pressKey("Enter");
|
|
259
|
+
return `Shortcut /${shortcut} sent${context ? ` with context: "${context.substring(0, 50)}${context.length > 50 ? "..." : ""}"` : ""}`;
|
|
256
260
|
}
|
|
257
261
|
/**
|
|
258
262
|
* Get current agent status and progress (for polling)
|
|
259
263
|
*/
|
|
260
264
|
async getAgentStatus() {
|
|
261
|
-
//
|
|
262
|
-
|
|
265
|
+
// Report the bound tab URL only. Global target scans can leak another
|
|
266
|
+
// agent's authenticated workflow into this session's status.
|
|
267
|
+
let agentBrowsingUrl = "";
|
|
263
268
|
try {
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
agentBrowsingUrl = tabs.agentBrowsing.url;
|
|
267
|
-
}
|
|
269
|
+
const urlResult = await this.cdp.safeEvaluate("window.location.href");
|
|
270
|
+
agentBrowsingUrl = urlResult.result.value || "";
|
|
268
271
|
}
|
|
269
272
|
catch {
|
|
270
273
|
// Continue without URL
|
|
271
274
|
}
|
|
272
|
-
const result = await
|
|
275
|
+
const result = await this.cdp.safeEvaluate(`
|
|
273
276
|
(() => {
|
|
274
277
|
const body = document.body.innerText;
|
|
275
278
|
|
|
@@ -285,7 +288,9 @@ export class CometAI {
|
|
|
285
288
|
}
|
|
286
289
|
}
|
|
287
290
|
|
|
288
|
-
|
|
291
|
+
// Scope spinner detection to response content area only (not page-wide UI)
|
|
292
|
+
const responseArea = document.querySelector('main') || document.body;
|
|
293
|
+
const hasLoadingSpinner = responseArea.querySelector('[class*="animate-spin"], [class*="animate-pulse"]') !== null;
|
|
289
294
|
const hasStepsCompleted = /\\d+ steps? completed/i.test(body);
|
|
290
295
|
const hasFinishedMarker = body.includes('Finished') && !hasActiveStopButton;
|
|
291
296
|
const hasReviewedSources = /Reviewed \\d+ sources?/i.test(body);
|
|
@@ -300,18 +305,18 @@ export class CometAI {
|
|
|
300
305
|
];
|
|
301
306
|
const hasWorkingText = workingPatterns.some(p => body.includes(p));
|
|
302
307
|
|
|
303
|
-
// Determine status
|
|
308
|
+
// Determine status — prioritize positive completion signals
|
|
304
309
|
let status = 'idle';
|
|
305
|
-
if (
|
|
306
|
-
status = '
|
|
310
|
+
if (hasAskFollowUp && hasProseContent && !hasActiveStopButton) {
|
|
311
|
+
status = 'completed';
|
|
307
312
|
} else if (hasStepsCompleted || hasFinishedMarker) {
|
|
308
313
|
status = 'completed';
|
|
309
314
|
} else if (hasReviewedSources && !hasWorkingText) {
|
|
310
315
|
status = 'completed';
|
|
316
|
+
} else if (hasActiveStopButton || hasLoadingSpinner) {
|
|
317
|
+
status = 'working';
|
|
311
318
|
} else if (hasWorkingText) {
|
|
312
319
|
status = 'working';
|
|
313
|
-
} else if (hasAskFollowUp && hasProseContent && !hasActiveStopButton) {
|
|
314
|
-
status = 'completed';
|
|
315
320
|
}
|
|
316
321
|
|
|
317
322
|
// Extract steps
|
|
@@ -368,11 +373,82 @@ export class CometAI {
|
|
|
368
373
|
agentBrowsingUrl,
|
|
369
374
|
};
|
|
370
375
|
}
|
|
376
|
+
// ---- Failure Detection Heuristics (Spec 016, FR-002, T014-T016) ----
|
|
377
|
+
/**
|
|
378
|
+
* Detect voice/listening mode — Perplexity entered microphone mode instead of processing text.
|
|
379
|
+
*/
|
|
380
|
+
async detectVoiceMode() {
|
|
381
|
+
try {
|
|
382
|
+
const result = await this.cdp.safeEvaluate(`
|
|
383
|
+
(() => {
|
|
384
|
+
const body = document.body.innerText;
|
|
385
|
+
// Check for "Listening..." text which appears in voice mode
|
|
386
|
+
if (body.includes('Listening...') || body.includes('Listening…')) return true;
|
|
387
|
+
// Check for active microphone UI elements
|
|
388
|
+
const micButtons = document.querySelectorAll('button[aria-label*="microphone" i], button[aria-label*="voice" i], button[aria-label*="listen" i]');
|
|
389
|
+
for (const btn of micButtons) {
|
|
390
|
+
if (btn.classList.contains('active') || btn.getAttribute('aria-pressed') === 'true') return true;
|
|
391
|
+
}
|
|
392
|
+
return false;
|
|
393
|
+
})()
|
|
394
|
+
`);
|
|
395
|
+
return result.result.value === true;
|
|
396
|
+
}
|
|
397
|
+
catch (err) {
|
|
398
|
+
console.warn(`[comet-bridge] Voice mode detection failed: ${err instanceof Error ? err.message : err}`);
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Detect IDLE without navigation — task returned IDLE but never left the Perplexity home page.
|
|
404
|
+
*/
|
|
405
|
+
async detectIdleNoNavigation(initialUrl) {
|
|
406
|
+
try {
|
|
407
|
+
const result = await this.cdp.safeEvaluate(`window.location.href`);
|
|
408
|
+
const currentUrl = result.result.value;
|
|
409
|
+
// If still on Perplexity home/search and status is idle, no navigation occurred
|
|
410
|
+
const isPerplexityHome = currentUrl === "https://www.perplexity.ai/" ||
|
|
411
|
+
currentUrl === "https://www.perplexity.ai" ||
|
|
412
|
+
currentUrl?.endsWith("/home");
|
|
413
|
+
if (isPerplexityHome)
|
|
414
|
+
return true;
|
|
415
|
+
// If we have an initial URL and it hasn't changed, no navigation occurred
|
|
416
|
+
if (initialUrl && currentUrl === initialUrl)
|
|
417
|
+
return true;
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
catch (err) {
|
|
421
|
+
console.warn(`[comet-bridge] Idle/no-navigation detection failed: ${err instanceof Error ? err.message : err}`);
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Detect context bleed — response content URL doesn't match the requested URL.
|
|
427
|
+
*/
|
|
428
|
+
async detectContextBleed(requestedUrl) {
|
|
429
|
+
if (!requestedUrl)
|
|
430
|
+
return false;
|
|
431
|
+
try {
|
|
432
|
+
const status = await this.getAgentStatus();
|
|
433
|
+
// If the response mentions browsing a different URL than requested, it's context bleed
|
|
434
|
+
if (status.agentBrowsingUrl && requestedUrl) {
|
|
435
|
+
const requested = new URL(requestedUrl).hostname;
|
|
436
|
+
const actual = new URL(status.agentBrowsingUrl).hostname;
|
|
437
|
+
if (requested !== actual)
|
|
438
|
+
return true;
|
|
439
|
+
}
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
catch (err) {
|
|
443
|
+
console.warn(`[comet-bridge] Context bleed detection failed: ${err instanceof Error ? err.message : err}`);
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
371
447
|
/**
|
|
372
448
|
* Stop the current agent task
|
|
373
449
|
*/
|
|
374
450
|
async stopAgent() {
|
|
375
|
-
const result = await
|
|
451
|
+
const result = await this.cdp.evaluate(`
|
|
376
452
|
(() => {
|
|
377
453
|
// Try aria-label buttons first
|
|
378
454
|
for (const btn of document.querySelectorAll('button[aria-label*="Stop"], button[aria-label*="Cancel"]')) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type CodexIdentityInput, type CodexWindowBinding, type CodexWindowBindingStore } from "./window-bindings.js";
|
|
2
|
+
export interface DelegateBindingInput extends CodexIdentityInput {
|
|
3
|
+
taskId: string;
|
|
4
|
+
threadId: string;
|
|
5
|
+
agentId: string;
|
|
6
|
+
currentBinding?: CodexWindowBinding | null;
|
|
7
|
+
bindingId?: string;
|
|
8
|
+
windowId?: number;
|
|
9
|
+
tabGroupId?: number | null;
|
|
10
|
+
targetId?: string | null;
|
|
11
|
+
}
|
|
12
|
+
export interface DelegateBindingDispatch {
|
|
13
|
+
bindingId: string | null;
|
|
14
|
+
windowId: number | null;
|
|
15
|
+
tabGroupId: number | null;
|
|
16
|
+
dispatchStatus: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function createOrReuseDelegateBinding(input: DelegateBindingInput, store?: CodexWindowBindingStore): Promise<DelegateBindingDispatch>;
|
|
19
|
+
//# sourceMappingURL=delegate-binding.d.ts.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { deriveCodexSessionIdentity, windowBindingStore, } from "./window-bindings.js";
|
|
2
|
+
function numericValue(value) {
|
|
3
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
4
|
+
return value;
|
|
5
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
6
|
+
const parsed = Number(value);
|
|
7
|
+
if (Number.isFinite(parsed))
|
|
8
|
+
return parsed;
|
|
9
|
+
}
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
export async function createOrReuseDelegateBinding(input, store = windowBindingStore) {
|
|
13
|
+
const currentBinding = input.currentBinding ?? null;
|
|
14
|
+
if (input.bindingId) {
|
|
15
|
+
const binding = await store.addRunId(input.bindingId, input.taskId);
|
|
16
|
+
return {
|
|
17
|
+
bindingId: binding.bindingId,
|
|
18
|
+
windowId: binding.windowId,
|
|
19
|
+
tabGroupId: binding.tabGroupId,
|
|
20
|
+
dispatchStatus: "binding-reused",
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if (currentBinding &&
|
|
24
|
+
input.windowId === undefined &&
|
|
25
|
+
input.tabGroupId === undefined &&
|
|
26
|
+
input.targetId === undefined) {
|
|
27
|
+
const persisted = await store.get(currentBinding.bindingId);
|
|
28
|
+
if (persisted) {
|
|
29
|
+
const binding = await store.addRunId(currentBinding.bindingId, input.taskId);
|
|
30
|
+
return {
|
|
31
|
+
bindingId: binding.bindingId,
|
|
32
|
+
windowId: binding.windowId,
|
|
33
|
+
tabGroupId: binding.tabGroupId,
|
|
34
|
+
dispatchStatus: "binding-reused",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const windowId = numericValue(input.windowId) ?? currentBinding?.windowId;
|
|
39
|
+
if (windowId === undefined) {
|
|
40
|
+
return {
|
|
41
|
+
bindingId: null,
|
|
42
|
+
windowId: null,
|
|
43
|
+
tabGroupId: null,
|
|
44
|
+
dispatchStatus: "binding-unavailable: windowId required",
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const identity = deriveCodexSessionIdentity({
|
|
48
|
+
...input,
|
|
49
|
+
strict: input.strict === true,
|
|
50
|
+
fallbackAgentId: input.agentId,
|
|
51
|
+
fallbackTaskThreadId: input.threadId,
|
|
52
|
+
});
|
|
53
|
+
const bindingResult = await store.createOrReuse({
|
|
54
|
+
...identity,
|
|
55
|
+
windowId,
|
|
56
|
+
tabGroupId: numericValue(input.tabGroupId) ??
|
|
57
|
+
currentBinding?.tabGroupId ??
|
|
58
|
+
(typeof input.tabGroupId === "number" ? input.tabGroupId : null),
|
|
59
|
+
targetId: input.targetId ?? currentBinding?.targetId ?? null,
|
|
60
|
+
runIds: [input.taskId],
|
|
61
|
+
profileId: currentBinding?.profileId ?? "agent",
|
|
62
|
+
profileAlias: currentBinding?.profileAlias ?? "oe",
|
|
63
|
+
profileOwner: currentBinding?.profileOwner ?? "agent",
|
|
64
|
+
sidecarContextKey: currentBinding?.sidecarContextKey,
|
|
65
|
+
});
|
|
66
|
+
return {
|
|
67
|
+
bindingId: bindingResult.binding.bindingId,
|
|
68
|
+
windowId: bindingResult.binding.windowId,
|
|
69
|
+
tabGroupId: bindingResult.binding.tabGroupId,
|
|
70
|
+
dispatchStatus: `binding-${bindingResult.action}`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=delegate-binding.js.map
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* capability-entry.ts
|
|
3
|
+
* Core type definitions for the Comet Discovery Layer (Spec 042).
|
|
4
|
+
* All types are derived from data-model.md and capability-entry.schema.json.
|
|
5
|
+
* No runtime logic — pure type declarations + tagged unions.
|
|
6
|
+
*/
|
|
7
|
+
/** Which canonical source owns a capability entry. */
|
|
8
|
+
export type SourceLayer = "mcp" | "plugin" | "extension";
|
|
9
|
+
/** Rank order for dedup: plugin (3) > extension (2) > mcp (1). */
|
|
10
|
+
export declare const SOURCE_LAYER_RANK: Record<SourceLayer, number>;
|
|
11
|
+
/**
|
|
12
|
+
* Safety classification — reused verbatim from docs/TOOL-SAFETY-REFERENCE.md.
|
|
13
|
+
* No default, no UNKNOWN class. Every capability must have exactly one.
|
|
14
|
+
*
|
|
15
|
+
* SAFE — Read-only, zero side effects, no session required.
|
|
16
|
+
* SESSION — Requires an active Comet CDP session; scoped to the calling agent's tab group.
|
|
17
|
+
* CAUTION — Cross-session side effects possible; requires explicit multi-agent awareness.
|
|
18
|
+
*/
|
|
19
|
+
export type SafetyClass = "SAFE" | "SESSION" | "CAUTION";
|
|
20
|
+
export type Precondition = {
|
|
21
|
+
kind: "cdp_session";
|
|
22
|
+
note: string;
|
|
23
|
+
} | {
|
|
24
|
+
kind: "profile";
|
|
25
|
+
value: "oe" | "moon";
|
|
26
|
+
} | {
|
|
27
|
+
kind: "task_group";
|
|
28
|
+
note: string;
|
|
29
|
+
} | {
|
|
30
|
+
kind: "task_thread";
|
|
31
|
+
note: string;
|
|
32
|
+
} | {
|
|
33
|
+
kind: "free";
|
|
34
|
+
note: string;
|
|
35
|
+
};
|
|
36
|
+
export type Invocation = {
|
|
37
|
+
kind: "native";
|
|
38
|
+
/** Name of the existing MCP tool — pass-through, no behavior change (FR-009). */
|
|
39
|
+
toolName: string;
|
|
40
|
+
} | {
|
|
41
|
+
kind: "guide";
|
|
42
|
+
/** Claude Code Skill name (e.g. "comet-browse"). */
|
|
43
|
+
skill: string;
|
|
44
|
+
/** Args to pass to Skill(). */
|
|
45
|
+
normalizedArgs: Record<string, unknown>;
|
|
46
|
+
/** Human hint: "Invoke via Skill('comet-browse') with these args." */
|
|
47
|
+
nextStepHint: string;
|
|
48
|
+
} | {
|
|
49
|
+
kind: "cdp";
|
|
50
|
+
/** Chrome DevTools Protocol method string. */
|
|
51
|
+
method: string;
|
|
52
|
+
params?: Record<string, unknown>;
|
|
53
|
+
} | {
|
|
54
|
+
kind: "filesystem";
|
|
55
|
+
path: string;
|
|
56
|
+
mode: "read" | "signal";
|
|
57
|
+
} | {
|
|
58
|
+
kind: "cli";
|
|
59
|
+
command: string;
|
|
60
|
+
args: string[];
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* One CapabilityEntry per source-side definition.
|
|
64
|
+
* A plugin command that wraps a native MCP tool produces TWO entries sharing a canonicalId.
|
|
65
|
+
*/
|
|
66
|
+
export interface CapabilityEntry {
|
|
67
|
+
/**
|
|
68
|
+
* Globally unique tool-facing identifier.
|
|
69
|
+
* Native: existing tool name (e.g. "comet_screenshot").
|
|
70
|
+
* Plugin: "plugin_<slug>".
|
|
71
|
+
* Extension: "ext_<slug>".
|
|
72
|
+
* Regex: /^[a-z][a-z0-9_]{2,63}$/
|
|
73
|
+
*/
|
|
74
|
+
name: string;
|
|
75
|
+
/** Which canonical source owns this entry. */
|
|
76
|
+
sourceLayer: SourceLayer;
|
|
77
|
+
/**
|
|
78
|
+
* Repo-relative path + optional #fragment pointing to the canonical definition.
|
|
79
|
+
* e.g. "comet-mcp/src/index.ts#tool_comet_screenshot"
|
|
80
|
+
*/
|
|
81
|
+
sourcePath: string;
|
|
82
|
+
/**
|
|
83
|
+
* Shared identity key for dedup grouping across layers.
|
|
84
|
+
* Entries sharing a canonicalId form a CapabilityGroup.
|
|
85
|
+
* Regex: /^[a-z][a-z0-9_-]{2,63}$/
|
|
86
|
+
*/
|
|
87
|
+
canonicalId: string;
|
|
88
|
+
/**
|
|
89
|
+
* Human-readable verb-phrase description.
|
|
90
|
+
* Template: "<verb> <object> [<modifier>]. Source: <layer>. Safety: <class>."
|
|
91
|
+
* Min 24 chars, max 400 chars.
|
|
92
|
+
*/
|
|
93
|
+
description: string;
|
|
94
|
+
/**
|
|
95
|
+
* At least 3 lowercase keywords capturing common search intents.
|
|
96
|
+
* Drives natural-language ToolSearch ranking (FR-006, SC-002).
|
|
97
|
+
*/
|
|
98
|
+
intentKeywords: string[];
|
|
99
|
+
/**
|
|
100
|
+
* Safety class — must be set; no default.
|
|
101
|
+
* Reused verbatim from docs/TOOL-SAFETY-REFERENCE.md.
|
|
102
|
+
*/
|
|
103
|
+
safety: SafetyClass;
|
|
104
|
+
/**
|
|
105
|
+
* Prerequisites the agent must satisfy before calling this tool.
|
|
106
|
+
* May be empty array, never missing.
|
|
107
|
+
*/
|
|
108
|
+
preconditions: Precondition[];
|
|
109
|
+
/**
|
|
110
|
+
* JSON Schema draft 2020-12 fragment for the tool's arguments.
|
|
111
|
+
* Empty schema `{ type: "object", properties: {} }` is valid.
|
|
112
|
+
*/
|
|
113
|
+
argsSchema: Record<string, unknown>;
|
|
114
|
+
/**
|
|
115
|
+
* True iff this capability has no agent-reachable programmatic entry point
|
|
116
|
+
* and is deliberately excluded from virtual-tool registration.
|
|
117
|
+
* MUST be accompanied by a matching HumanOnlyException.
|
|
118
|
+
*/
|
|
119
|
+
humanOnly: boolean;
|
|
120
|
+
/** Determines what the virtual MCP tool body returns or dispatches. */
|
|
121
|
+
invocation: Invocation;
|
|
122
|
+
/**
|
|
123
|
+
* The `name` of the paired entry in this entry's CapabilityGroup, or null for singletons.
|
|
124
|
+
*/
|
|
125
|
+
crossRef: string | null;
|
|
126
|
+
/**
|
|
127
|
+
* True for the highest-layer entry in a group (plugin > extension > mcp).
|
|
128
|
+
* Singletons are always recommended: true.
|
|
129
|
+
*/
|
|
130
|
+
recommended: boolean;
|
|
131
|
+
/** For non-recommended entries: "lower-level". For recommended: null. */
|
|
132
|
+
alternativeLabel: "lower-level" | null;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Transient collection of CapabilityEntry instances sharing a canonicalId.
|
|
136
|
+
* Emits exactly one "recommended" virtual tool and up to one "alternative (lower-level)".
|
|
137
|
+
*/
|
|
138
|
+
export interface CapabilityGroup {
|
|
139
|
+
canonicalId: string;
|
|
140
|
+
/** Highest-layer member (plugin > extension > mcp). */
|
|
141
|
+
recommendedEntry: CapabilityEntry;
|
|
142
|
+
/** Lowest-layer member when distinct from recommendedEntry. null for singletons. */
|
|
143
|
+
alternativeEntry: CapabilityEntry | null;
|
|
144
|
+
/**
|
|
145
|
+
* Intermediate members NOT registered as virtual tools.
|
|
146
|
+
* Still listed for traceability under the recommended entry's alternates.
|
|
147
|
+
*/
|
|
148
|
+
suppressedEntries: CapabilityEntry[];
|
|
149
|
+
}
|
|
150
|
+
export interface HumanOnlyException {
|
|
151
|
+
/** Must equal the canonicalId of a capability with humanOnly: true. */
|
|
152
|
+
name: string;
|
|
153
|
+
/** Why no programmatic entry point exists. Min 24 chars. */
|
|
154
|
+
reason: string;
|
|
155
|
+
/** GitHub handle (@username) or team (@org/team). */
|
|
156
|
+
owner: string;
|
|
157
|
+
/** Plan to make this capability agent-accessible, or null if genuinely blocked. */
|
|
158
|
+
enhancementPath: string | null;
|
|
159
|
+
/** ISO 8601 date string. Staleness warning fires if older than 90 days. */
|
|
160
|
+
lastReviewed: string;
|
|
161
|
+
}
|
|
162
|
+
export interface SourceError {
|
|
163
|
+
/** Which source layer failed. */
|
|
164
|
+
source: SourceLayer;
|
|
165
|
+
/** Human-readable reason. */
|
|
166
|
+
reason: string;
|
|
167
|
+
/** Original error, if any. */
|
|
168
|
+
originalError?: unknown;
|
|
169
|
+
}
|
|
170
|
+
export type DriftStatus = "pass" | "warn" | "fail";
|
|
171
|
+
export interface DriftReport {
|
|
172
|
+
generatedAt: string;
|
|
173
|
+
emittedBy: "ci" | "session-start";
|
|
174
|
+
available: SourceLayer[];
|
|
175
|
+
unavailable: Array<{
|
|
176
|
+
source: SourceLayer;
|
|
177
|
+
reason: string;
|
|
178
|
+
}>;
|
|
179
|
+
capabilityCount: number;
|
|
180
|
+
virtualToolCount: number;
|
|
181
|
+
/** canonicalIds in canonical source but missing from registry → hard failure. */
|
|
182
|
+
missingFromRegistry: string[];
|
|
183
|
+
/** Virtual tool names in registry with no canonical source → hard failure. */
|
|
184
|
+
extrasInRegistry: string[];
|
|
185
|
+
/** Entry names missing safety field → hard failure. */
|
|
186
|
+
safetyGaps: string[];
|
|
187
|
+
/** humanOnly: true capabilities lacking a matching exception → hard failure. */
|
|
188
|
+
humanOnlyDangling: string[];
|
|
189
|
+
/** Exception names that don't resolve to any humanOnly: true capability → hard failure. */
|
|
190
|
+
exceptionsDangling: string[];
|
|
191
|
+
/** Exceptions with lastReviewed older than review window → warning only. */
|
|
192
|
+
exceptionsStale: string[];
|
|
193
|
+
status: DriftStatus;
|
|
194
|
+
}
|
|
195
|
+
export interface GoldenQueryResult {
|
|
196
|
+
query: string;
|
|
197
|
+
expectedCanonicalId: string;
|
|
198
|
+
top5Names: string[];
|
|
199
|
+
hit: boolean;
|
|
200
|
+
}
|
|
201
|
+
export interface CoverageReport {
|
|
202
|
+
sources: Record<SourceLayer, number>;
|
|
203
|
+
totalCapabilities: number;
|
|
204
|
+
coverageRatio: number;
|
|
205
|
+
goldenQueries: GoldenQueryResult[];
|
|
206
|
+
top5HitRate: number;
|
|
207
|
+
metadataCompleteness: number;
|
|
208
|
+
}
|
|
209
|
+
/** Minimal MCP tool definition shape accepted by the comet-mcp Server. */
|
|
210
|
+
export interface McpToolDefinition {
|
|
211
|
+
name: string;
|
|
212
|
+
description: string;
|
|
213
|
+
inputSchema: Record<string, unknown>;
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=capability-entry.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* capability-entry.ts
|
|
3
|
+
* Core type definitions for the Comet Discovery Layer (Spec 042).
|
|
4
|
+
* All types are derived from data-model.md and capability-entry.schema.json.
|
|
5
|
+
* No runtime logic — pure type declarations + tagged unions.
|
|
6
|
+
*/
|
|
7
|
+
/** Rank order for dedup: plugin (3) > extension (2) > mcp (1). */
|
|
8
|
+
export const SOURCE_LAYER_RANK = {
|
|
9
|
+
plugin: 3,
|
|
10
|
+
extension: 2,
|
|
11
|
+
mcp: 1,
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=capability-entry.js.map
|