@hover-dev/core 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/claude.d.ts.map +1 -1
- package/dist/agents/claude.js +23 -0
- package/dist/agents/types.d.ts +5 -0
- package/dist/agents/types.d.ts.map +1 -1
- package/dist/playwright/raiseWindow.js +20 -5
- package/dist/service/cdpHandlers.d.ts +12 -3
- package/dist/service/cdpHandlers.d.ts.map +1 -1
- package/dist/service/cdpHandlers.js +24 -9
- package/dist/service/saveHandlers.d.ts.map +1 -1
- package/dist/service/saveHandlers.js +15 -4
- package/dist/service/types.d.ts +10 -1
- package/dist/service/types.d.ts.map +1 -1
- package/dist/service/types.js +12 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +122 -55
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/agents/claude.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAA2C,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/agents/claude.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAA2C,MAAM,YAAY,CAAC;AA6G3F,eAAO,MAAM,WAAW,EAAE,eA0HzB,CAAC"}
|
package/dist/agents/claude.js
CHANGED
|
@@ -39,12 +39,35 @@ function claudeState(state) {
|
|
|
39
39
|
}
|
|
40
40
|
return state;
|
|
41
41
|
}
|
|
42
|
+
/** Every built-in claude-code tool that has nothing to do with driving a
|
|
43
|
+
* browser. Combined with `--strict-mcp-config` + an allow-list of mcp__*
|
|
44
|
+
* ids, this leaves Claude with only the Playwright MCP (plus any
|
|
45
|
+
* plugin-contributed MCPs) as a usable tool surface. */
|
|
46
|
+
const CLAUDE_DEFAULT_DISALLOWED_TOOLS = [
|
|
47
|
+
// file / shell / data access — never appropriate for browser driving
|
|
48
|
+
'Bash', 'BashOutput', 'KillBash',
|
|
49
|
+
'Edit', 'MultiEdit', 'Write', 'Read', 'NotebookEdit',
|
|
50
|
+
'Grep', 'Glob', 'Task', 'TodoWrite',
|
|
51
|
+
'WebFetch', 'WebSearch',
|
|
52
|
+
// plan / worktree / cron / notification — irrelevant in -p mode
|
|
53
|
+
'EnterPlanMode', 'ExitPlanMode',
|
|
54
|
+
'EnterWorktree', 'ExitWorktree',
|
|
55
|
+
'CronCreate', 'CronDelete', 'CronList',
|
|
56
|
+
'PushNotification', 'RemoteTrigger',
|
|
57
|
+
// task & tool introspection added in claude 2.1.x — let through and
|
|
58
|
+
// the agent will burn turns exploring instead of executing
|
|
59
|
+
'ToolSearch',
|
|
60
|
+
'Monitor', 'TaskOutput', 'TaskStop',
|
|
61
|
+
'AskUserQuestion',
|
|
62
|
+
'ShareOnboardingGuide',
|
|
63
|
+
];
|
|
42
64
|
export const claudeAgent = {
|
|
43
65
|
id: 'claude',
|
|
44
66
|
binName: 'claude',
|
|
45
67
|
protocol: 'argv',
|
|
46
68
|
streamFormat: 'stream-json',
|
|
47
69
|
sandboxStrength: 'hard',
|
|
70
|
+
defaultDisallowedTools: CLAUDE_DEFAULT_DISALLOWED_TOOLS,
|
|
48
71
|
display: {
|
|
49
72
|
label: 'Claude Code',
|
|
50
73
|
tagline: 'Anthropic — best-in-class browser driving, hard tool sandbox',
|
package/dist/agents/types.d.ts
CHANGED
|
@@ -146,6 +146,11 @@ export interface AgentDescriptor {
|
|
|
146
146
|
streamFormat: StreamFormat;
|
|
147
147
|
sandboxStrength: SandboxStrength;
|
|
148
148
|
display: AgentDisplay;
|
|
149
|
+
/** Hard-sandbox agents pass this list to `disallowedTools` when the
|
|
150
|
+
* service-level allow/deny config isn't explicitly overridden. Lets the
|
|
151
|
+
* per-CLI deny list live alongside its descriptor instead of as a magic
|
|
152
|
+
* array in the service. Soft-sandbox agents leave this undefined. */
|
|
153
|
+
defaultDisallowedTools?: readonly string[];
|
|
149
154
|
buildArgs(opts: InvokeOptions): string[];
|
|
150
155
|
/**
|
|
151
156
|
* Parse a single line of agent stdout into normalised InvokeEvents.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/agents/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/agents/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -103,22 +103,37 @@ export async function raiseChromeWindow(pid) {
|
|
|
103
103
|
function runCapture(cmd, args) {
|
|
104
104
|
return new Promise(resolve => {
|
|
105
105
|
let out = '';
|
|
106
|
+
let settled = false;
|
|
107
|
+
const finish = (v) => {
|
|
108
|
+
if (settled)
|
|
109
|
+
return;
|
|
110
|
+
settled = true;
|
|
111
|
+
resolve(v);
|
|
112
|
+
};
|
|
106
113
|
const child = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
107
114
|
child.stdout.on('data', chunk => {
|
|
108
115
|
out += chunk.toString();
|
|
109
116
|
});
|
|
110
|
-
child.on('error', () =>
|
|
111
|
-
child.on('close', code =>
|
|
117
|
+
child.on('error', () => finish(null));
|
|
118
|
+
child.on('close', code => finish(code === 0 ? out : null));
|
|
112
119
|
});
|
|
113
120
|
}
|
|
114
121
|
function runDetached(cmd, args) {
|
|
115
122
|
return new Promise((resolve, reject) => {
|
|
123
|
+
let settled = false;
|
|
116
124
|
const child = spawn(cmd, args, { stdio: 'ignore' });
|
|
117
|
-
child.on('error',
|
|
118
|
-
|
|
125
|
+
child.on('error', err => {
|
|
126
|
+
if (settled)
|
|
127
|
+
return;
|
|
128
|
+
settled = true;
|
|
129
|
+
reject(err);
|
|
130
|
+
});
|
|
131
|
+
child.on('close', () => {
|
|
132
|
+
if (settled)
|
|
133
|
+
return;
|
|
134
|
+
settled = true;
|
|
119
135
|
// Don't treat non-zero as fatal — caller already wraps in try/catch.
|
|
120
136
|
resolve();
|
|
121
|
-
void code;
|
|
122
137
|
});
|
|
123
138
|
});
|
|
124
139
|
}
|
|
@@ -11,7 +11,16 @@
|
|
|
11
11
|
* file can be a thin orchestrator.
|
|
12
12
|
*/
|
|
13
13
|
import type { WebSocket } from 'ws';
|
|
14
|
+
import { type LaunchOptions } from '../playwright/launchChrome.js';
|
|
14
15
|
import { type ClientMessage } from './types.js';
|
|
16
|
+
/** Extra launch options surfaced from the active mode (security plugin
|
|
17
|
+
* needs proxy + spki + separate profile + non-default CDP port). When
|
|
18
|
+
* none are set, behaviour is identical to pre-v0.7 normal-mode launch. */
|
|
19
|
+
export type LaunchExtras = Pick<LaunchOptions, 'userDataDir' | 'proxy'> & {
|
|
20
|
+
/** Override CDP port (mode-specific, e.g. 9333 for security). When set,
|
|
21
|
+
* this also wins over the `port` parsed from cdpUrl. */
|
|
22
|
+
cdpPort?: number;
|
|
23
|
+
};
|
|
15
24
|
/**
|
|
16
25
|
* "Is this widget running inside the debug Chrome?" The widget asks this on
|
|
17
26
|
* connect (and after every status-changing event) so it can render itself as
|
|
@@ -20,14 +29,14 @@ import { type ClientMessage } from './types.js';
|
|
|
20
29
|
* - wrong-window → disabled, with a "use the other window" notice
|
|
21
30
|
* - no-cdp → enabled but click triggers launch-chrome instead
|
|
22
31
|
*/
|
|
23
|
-
export declare function handleCheckCdp(ws: WebSocket, msg: ClientMessage, cdpUrl: string): Promise<void>;
|
|
32
|
+
export declare function handleCheckCdp(ws: WebSocket, msg: ClientMessage, cdpUrl: string, extras?: LaunchExtras): Promise<void>;
|
|
24
33
|
/**
|
|
25
34
|
* Launch a debug Chrome navigated to `pageUrl`, then re-check status. The
|
|
26
35
|
* re-check usually returns 'wrong-window' (because the widget asking is in
|
|
27
36
|
* the user's regular Chrome, not the freshly-launched one) — the widget then
|
|
28
37
|
* displays the "use the other window" state.
|
|
29
38
|
*/
|
|
30
|
-
export declare function handleLaunchChrome(ws: WebSocket, msg: ClientMessage, cdpUrl: string): Promise<void>;
|
|
39
|
+
export declare function handleLaunchChrome(ws: WebSocket, msg: ClientMessage, cdpUrl: string, extras?: LaunchExtras): Promise<void>;
|
|
31
40
|
/**
|
|
32
41
|
* bringToFront the debug-Chrome tab matching `pageUrl`'s origin (or open one
|
|
33
42
|
* if none exists). Used by the wrong-window UI's "switch to debug Chrome"
|
|
@@ -35,5 +44,5 @@ export declare function handleLaunchChrome(ws: WebSocket, msg: ClientMessage, cd
|
|
|
35
44
|
* the widget cares about, and the widget the user is about to focus is a
|
|
36
45
|
* different page (and will run its own check-cdp on its own ws connection).
|
|
37
46
|
*/
|
|
38
|
-
export declare function handleFocusDebug(ws: WebSocket, msg: ClientMessage, cdpUrl: string): Promise<void>;
|
|
47
|
+
export declare function handleFocusDebug(ws: WebSocket, msg: ClientMessage, cdpUrl: string, extras?: LaunchExtras): Promise<void>;
|
|
39
48
|
//# sourceMappingURL=cdpHandlers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cdpHandlers.d.ts","sourceRoot":"","sources":["../../src/service/cdpHandlers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"cdpHandlers.d.ts","sourceRoot":"","sources":["../../src/service/cdpHandlers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAEpC,OAAO,EAAqB,KAAK,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACtF,OAAO,EAAQ,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAEtD;;2EAE2E;AAC3E,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,EAAE,aAAa,GAAG,OAAO,CAAC,GAAG;IACxE;6DACyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,YAAY,GACpB,OAAO,CAAC,IAAI,CAAC,CAWf;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,YAAY,GACpB,OAAO,CAAC,IAAI,CAAC,CAkCf;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,YAAY,GACpB,OAAO,CAAC,IAAI,CAAC,CAaf"}
|
|
@@ -21,13 +21,16 @@ import { send } from './types.js';
|
|
|
21
21
|
* - wrong-window → disabled, with a "use the other window" notice
|
|
22
22
|
* - no-cdp → enabled but click triggers launch-chrome instead
|
|
23
23
|
*/
|
|
24
|
-
export async function handleCheckCdp(ws, msg, cdpUrl) {
|
|
24
|
+
export async function handleCheckCdp(ws, msg, cdpUrl, extras) {
|
|
25
25
|
const pageUrl = msg.payload?.pageUrl;
|
|
26
26
|
if (typeof pageUrl !== 'string' || !pageUrl) {
|
|
27
27
|
send(ws, { type: 'error', payload: { message: 'check-cdp: pageUrl is required' } });
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
30
|
-
const
|
|
30
|
+
const effectiveCdpUrl = extras?.cdpPort
|
|
31
|
+
? `http://localhost:${extras.cdpPort}`
|
|
32
|
+
: cdpUrl;
|
|
33
|
+
const status = await checkCdpStatus(effectiveCdpUrl, pageUrl);
|
|
31
34
|
send(ws, { type: 'cdp-status', payload: status });
|
|
32
35
|
}
|
|
33
36
|
/**
|
|
@@ -36,7 +39,7 @@ export async function handleCheckCdp(ws, msg, cdpUrl) {
|
|
|
36
39
|
* the user's regular Chrome, not the freshly-launched one) — the widget then
|
|
37
40
|
* displays the "use the other window" state.
|
|
38
41
|
*/
|
|
39
|
-
export async function handleLaunchChrome(ws, msg, cdpUrl) {
|
|
42
|
+
export async function handleLaunchChrome(ws, msg, cdpUrl, extras) {
|
|
40
43
|
const pageUrl = msg.payload?.pageUrl;
|
|
41
44
|
if (typeof pageUrl !== 'string' || !pageUrl) {
|
|
42
45
|
send(ws, { type: 'error', payload: { message: 'launch-chrome: pageUrl is required' } });
|
|
@@ -45,7 +48,7 @@ export async function handleLaunchChrome(ws, msg, cdpUrl) {
|
|
|
45
48
|
// Tell the widget we're launching so it can render a spinner immediately —
|
|
46
49
|
// findChromeBinary + spawn + ready-poll can take a few seconds.
|
|
47
50
|
send(ws, { type: 'cdp-status', payload: { state: 'no-cdp', launching: true } });
|
|
48
|
-
const port = (() => {
|
|
51
|
+
const port = extras?.cdpPort ?? (() => {
|
|
49
52
|
try {
|
|
50
53
|
return Number(new URL(cdpUrl).port) || 9222;
|
|
51
54
|
}
|
|
@@ -53,13 +56,22 @@ export async function handleLaunchChrome(ws, msg, cdpUrl) {
|
|
|
53
56
|
return 9222;
|
|
54
57
|
}
|
|
55
58
|
})();
|
|
56
|
-
const result = await launchDebugChrome({
|
|
59
|
+
const result = await launchDebugChrome({
|
|
60
|
+
url: pageUrl,
|
|
61
|
+
port,
|
|
62
|
+
userDataDir: extras?.userDataDir,
|
|
63
|
+
proxy: extras?.proxy,
|
|
64
|
+
});
|
|
57
65
|
if (!result.ok) {
|
|
58
66
|
send(ws, { type: 'cdp-status', payload: { state: 'no-cdp', reason: result.reason } });
|
|
59
67
|
return;
|
|
60
68
|
}
|
|
61
|
-
// Re-check
|
|
62
|
-
|
|
69
|
+
// Re-check status against the port we actually launched on, so a mode-
|
|
70
|
+
// specific port (9333 for security) doesn't get probed at 9222.
|
|
71
|
+
const effectiveCdpUrl = extras?.cdpPort
|
|
72
|
+
? `http://localhost:${extras.cdpPort}`
|
|
73
|
+
: cdpUrl;
|
|
74
|
+
const status = await checkCdpStatus(effectiveCdpUrl, pageUrl);
|
|
63
75
|
send(ws, { type: 'cdp-status', payload: status });
|
|
64
76
|
}
|
|
65
77
|
/**
|
|
@@ -69,13 +81,16 @@ export async function handleLaunchChrome(ws, msg, cdpUrl) {
|
|
|
69
81
|
* the widget cares about, and the widget the user is about to focus is a
|
|
70
82
|
* different page (and will run its own check-cdp on its own ws connection).
|
|
71
83
|
*/
|
|
72
|
-
export async function handleFocusDebug(ws, msg, cdpUrl) {
|
|
84
|
+
export async function handleFocusDebug(ws, msg, cdpUrl, extras) {
|
|
73
85
|
const pageUrl = msg.payload?.pageUrl;
|
|
74
86
|
if (typeof pageUrl !== 'string' || !pageUrl) {
|
|
75
87
|
send(ws, { type: 'error', payload: { message: 'focus-debug: pageUrl is required' } });
|
|
76
88
|
return;
|
|
77
89
|
}
|
|
78
|
-
const
|
|
90
|
+
const effectiveCdpUrl = extras?.cdpPort
|
|
91
|
+
? `http://localhost:${extras.cdpPort}`
|
|
92
|
+
: cdpUrl;
|
|
93
|
+
const result = await focusDebugTab(effectiveCdpUrl, pageUrl);
|
|
79
94
|
if (!result.ok) {
|
|
80
95
|
send(ws, { type: 'error', payload: { message: `focus-debug: ${result.reason}` } });
|
|
81
96
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"saveHandlers.d.ts","sourceRoot":"","sources":["../../src/service/saveHandlers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,EAAE,UAAU,EAAgC,KAAK,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACnG,OAAO,EAAE,SAAS,EAAmB,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACvF,OAAO,EAAE,YAAY,EAAsB,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EAAQ,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAEtD,UAAU,kBAAkB,CAAC,YAAY,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;IAC9E,qEAAqE;IACrE,WAAW,EAAE,MAAM,CAAC;IACpB,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB;wDACoD;IACpD,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,0EAA0E;IAC1E,WAAW,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,KAAK,CAAC;IAC9E,wEAAwE;IACxE,KAAK,EAAE,CAAC,IAAI,EAAE;QACZ,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,SAAS,EAAE,CAAC;QACnB,UAAU,EAAE,aAAa,EAAE,CAAC;QAC5B,OAAO,EAAE,WAAW,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC;QAC/C,SAAS,EAAE,OAAO,CAAC;KACpB,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;CAC7B;AAED,wBAAsB,kBAAkB,CAAC,YAAY,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAC1F,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,kBAAkB,CAAC,YAAY,CAAC,GACpC,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"saveHandlers.d.ts","sourceRoot":"","sources":["../../src/service/saveHandlers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,EAAE,UAAU,EAAgC,KAAK,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACnG,OAAO,EAAE,SAAS,EAAmB,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACvF,OAAO,EAAE,YAAY,EAAsB,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EAAQ,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAEtD,UAAU,kBAAkB,CAAC,YAAY,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;IAC9E,qEAAqE;IACrE,WAAW,EAAE,MAAM,CAAC;IACpB,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB;wDACoD;IACpD,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,0EAA0E;IAC1E,WAAW,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,KAAK,CAAC;IAC9E,wEAAwE;IACxE,KAAK,EAAE,CAAC,IAAI,EAAE;QACZ,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,SAAS,EAAE,CAAC;QACnB,UAAU,EAAE,aAAa,EAAE,CAAC;QAC5B,OAAO,EAAE,WAAW,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC;QAC/C,SAAS,EAAE,OAAO,CAAC;KACpB,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;CAC7B;AAED,wBAAsB,kBAAkB,CAAC,YAAY,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAC1F,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,kBAAkB,CAAC,YAAY,CAAC,GACpC,OAAO,CAAC,IAAI,CAAC,CA0Cf;AAED,eAAO,MAAM,YAAY,EAAE,kBAAkB,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC,CAanF,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,kBAAkB,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC,CAOjF,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,kBAAkB,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAYxF,CAAC"}
|
|
@@ -28,14 +28,12 @@ export async function handleSaveArtifact(ws, msg, devRoot, cfg) {
|
|
|
28
28
|
send(ws, { type: 'error', payload: { message: `${cfg.requestName}: no steps to save` } });
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
|
+
let result;
|
|
31
32
|
try {
|
|
32
|
-
|
|
33
|
+
result = await cfg.write({
|
|
33
34
|
devRoot, name, description, steps, assertions,
|
|
34
35
|
payload: msg.payload, overwrite,
|
|
35
36
|
});
|
|
36
|
-
send(ws, { type: cfg.savedType, payload: { name: result.slug, path: result.path } });
|
|
37
|
-
if (cfg.onSaved)
|
|
38
|
-
await cfg.onSaved(ws, devRoot);
|
|
39
37
|
}
|
|
40
38
|
catch (err) {
|
|
41
39
|
if (err instanceof cfg.ExistsError) {
|
|
@@ -44,6 +42,19 @@ export async function handleSaveArtifact(ws, msg, devRoot, cfg) {
|
|
|
44
42
|
}
|
|
45
43
|
const message = err instanceof Error ? err.message : String(err);
|
|
46
44
|
send(ws, { type: 'error', payload: { message: `${cfg.requestName} failed: ${message}` } });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
send(ws, { type: cfg.savedType, payload: { name: result.slug, path: result.path } });
|
|
48
|
+
// The artifact is already on disk; an onSaved failure (e.g. listSkills
|
|
49
|
+
// re-scan) shouldn't surface as if the save itself failed — log and move on.
|
|
50
|
+
if (cfg.onSaved) {
|
|
51
|
+
try {
|
|
52
|
+
await cfg.onSaved(ws, devRoot);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
56
|
+
console.warn(`[hover] ${cfg.requestName} onSaved failed: ${message}`);
|
|
57
|
+
}
|
|
47
58
|
}
|
|
48
59
|
}
|
|
49
60
|
export const SKILL_CONFIG = {
|
package/dist/service/types.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* back to the widget. Centralised so the JSON.stringify happens in exactly
|
|
10
10
|
* one place.
|
|
11
11
|
*/
|
|
12
|
-
import
|
|
12
|
+
import { WebSocket } from 'ws';
|
|
13
13
|
import type { SkillStep } from '../skills/writeSkill.js';
|
|
14
14
|
import type { SpecAssertion } from '../specs/writeSpec.js';
|
|
15
15
|
export interface ClientMessage {
|
|
@@ -41,4 +41,13 @@ export declare function send(ws: WebSocket, message: {
|
|
|
41
41
|
type: string;
|
|
42
42
|
payload?: unknown;
|
|
43
43
|
}): void;
|
|
44
|
+
/** Send a message only if the socket is still open. Use this from delayed
|
|
45
|
+
* callbacks (promise `.then`, timers) where the client may have disconnected
|
|
46
|
+
* between scheduling and firing — calling `ws.send` on a closed socket
|
|
47
|
+
* is a silent no-op for some states and throws for others, so a single
|
|
48
|
+
* guarded helper makes the intent obvious and prevents surprises. */
|
|
49
|
+
export declare function sendIfOpen(ws: WebSocket, message: {
|
|
50
|
+
type: string;
|
|
51
|
+
payload?: unknown;
|
|
52
|
+
}): boolean;
|
|
44
53
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/service/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/service/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC/B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC;QACpB,UAAU,CAAC,EAAE,aAAa,EAAE,CAAC;QAC7B,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB;uDAC+C;QAC/C,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB;;2DAEmD;QACnD,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,oEAAoE;QACpE,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB;+DACuD;QACvD,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACxB,CAAC;CACH;AAED,wBAAgB,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAEtF;AAED;;;;sEAIsE;AACtE,wBAAgB,UAAU,CACxB,EAAE,EAAE,SAAS,EACb,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAC3C,OAAO,CAIT"}
|
package/dist/service/types.js
CHANGED
|
@@ -9,6 +9,18 @@
|
|
|
9
9
|
* back to the widget. Centralised so the JSON.stringify happens in exactly
|
|
10
10
|
* one place.
|
|
11
11
|
*/
|
|
12
|
+
import { WebSocket } from 'ws';
|
|
12
13
|
export function send(ws, message) {
|
|
13
14
|
ws.send(JSON.stringify(message));
|
|
14
15
|
}
|
|
16
|
+
/** Send a message only if the socket is still open. Use this from delayed
|
|
17
|
+
* callbacks (promise `.then`, timers) where the client may have disconnected
|
|
18
|
+
* between scheduling and firing — calling `ws.send` on a closed socket
|
|
19
|
+
* is a silent no-op for some states and throws for others, so a single
|
|
20
|
+
* guarded helper makes the intent obvious and prevents surprises. */
|
|
21
|
+
export function sendIfOpen(ws, message) {
|
|
22
|
+
if (ws.readyState !== WebSocket.OPEN)
|
|
23
|
+
return false;
|
|
24
|
+
ws.send(JSON.stringify(message));
|
|
25
|
+
return true;
|
|
26
|
+
}
|
package/dist/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAqEA,OAAO,EAEL,KAAK,mBAAmB,EAEzB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;6EAGyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;uBAImB;IACnB,OAAO,CAAC,EAAE,mBAAmB,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B;4EACwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAiDD,wBAAsB,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAmrB/E"}
|
package/dist/service.js
CHANGED
|
@@ -48,7 +48,7 @@ import { getAgent } from './agents/registry.js';
|
|
|
48
48
|
import { getPreflight, invalidatePreflight } from './playwright/preflightCache.js';
|
|
49
49
|
import { resolveMcpConfig } from './playwright/resolveMcpConfig.js';
|
|
50
50
|
import { listSkills } from './skills/writeSkill.js';
|
|
51
|
-
import { send } from './service/types.js';
|
|
51
|
+
import { send, sendIfOpen } from './service/types.js';
|
|
52
52
|
import { buildCdpHint, buildCdpHintResume } from './service/cdpHint.js';
|
|
53
53
|
import { handleCheckCdp, handleLaunchChrome, handleFocusDebug, } from './service/cdpHandlers.js';
|
|
54
54
|
import { handleSaveArtifact, SKILL_CONFIG, SPEC_CONFIG, CASE_CSV_CONFIG, } from './service/saveHandlers.js';
|
|
@@ -167,8 +167,15 @@ export async function startService(opts) {
|
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
|
+
// In an active mode, the Playwright MCP must point at THAT mode's
|
|
171
|
+
// Chrome (e.g. security mode's 9333), not the default 9222.
|
|
172
|
+
// effectiveLaunchExtras().cdpPort is the source of truth.
|
|
173
|
+
const extras = effectiveLaunchExtras();
|
|
174
|
+
const effectiveCdpUrl = extras?.cdpPort
|
|
175
|
+
? `http://localhost:${extras.cdpPort}`
|
|
176
|
+
: cdpUrl;
|
|
170
177
|
return resolveMcpConfig({
|
|
171
|
-
cdpUrl,
|
|
178
|
+
cdpUrl: effectiveCdpUrl,
|
|
172
179
|
port,
|
|
173
180
|
extra,
|
|
174
181
|
// Suffix the filename by the mode so different mode toggles within
|
|
@@ -209,14 +216,44 @@ export async function startService(opts) {
|
|
|
209
216
|
/** id of the currently-active mode, or null for normal (unmoded) mode. */
|
|
210
217
|
let currentModeId = null;
|
|
211
218
|
/** Chrome-proxy settings the active mode's activate hook set on us.
|
|
212
|
-
*
|
|
213
|
-
* launch
|
|
219
|
+
* Read by `effectiveLaunchExtras()` and threaded into the cdp handlers
|
|
220
|
+
* (check-cdp / launch-chrome / focus-debug) so the secured Chrome on
|
|
221
|
+
* 9333 actually gets `--proxy-server` + SPKI pin when the user clicks
|
|
222
|
+
* Launch from the widget. */
|
|
214
223
|
let modeChromeProxy = null;
|
|
215
224
|
/** Runtime env overrides keyed by mcpServer id, set by plugin
|
|
216
225
|
* activate hooks (via ctx.setMcpServerEnv). Cleared on mode change.
|
|
217
226
|
* Merged with the manifest-declared env when the agent's spawn-time
|
|
218
227
|
* MCP config is built. */
|
|
219
228
|
const mcpEnvOverrides = new Map();
|
|
229
|
+
/** The cdp-handler extras (port, userDataDir, proxy) for the active
|
|
230
|
+
* mode's chromeFlags manifest field, or undefined when no mode is
|
|
231
|
+
* active. The widget's launch-chrome / check-cdp / focus-debug paths
|
|
232
|
+
* all consume these so a Chrome relaunch obeys the mode's needs. */
|
|
233
|
+
const effectiveLaunchExtras = () => {
|
|
234
|
+
if (!currentModeId)
|
|
235
|
+
return undefined;
|
|
236
|
+
const plugin = pluginsByModeId.get(currentModeId);
|
|
237
|
+
const flags = plugin?.chromeFlags;
|
|
238
|
+
if (!flags && !modeChromeProxy)
|
|
239
|
+
return undefined;
|
|
240
|
+
// Belt + suspenders — flags.activeInModes is honoured if set, but
|
|
241
|
+
// since chromeFlags lives on the plugin that contributed this mode,
|
|
242
|
+
// the default of "applies in own mode" matches what we want.
|
|
243
|
+
if (flags?.activeInModes && !flags.activeInModes.includes('*') && !flags.activeInModes.includes(currentModeId)) {
|
|
244
|
+
// Plugin explicitly restricted its chromeFlags to a different mode.
|
|
245
|
+
// Honour that and only carry modeChromeProxy (set by setChromeProxy).
|
|
246
|
+
return modeChromeProxy ? { proxy: modeChromeProxy } : undefined;
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
cdpPort: flags?.cdpPort,
|
|
250
|
+
userDataDir: flags?.userDataDir,
|
|
251
|
+
// modeChromeProxy wins over flags.proxy because it's the runtime
|
|
252
|
+
// value the activate hook computed (after starting mockttp);
|
|
253
|
+
// flags.proxy is only ever set by tests stubbing the manifest.
|
|
254
|
+
proxy: modeChromeProxy ?? flags?.proxy,
|
|
255
|
+
};
|
|
256
|
+
};
|
|
220
257
|
/** Send the current mode catalogue to one ws (or all if undefined). */
|
|
221
258
|
const broadcastModes = (target) => {
|
|
222
259
|
const available = plugins
|
|
@@ -286,7 +323,21 @@ export async function startService(opts) {
|
|
|
286
323
|
mcpEnvOverrides.set(id, env);
|
|
287
324
|
},
|
|
288
325
|
};
|
|
289
|
-
|
|
326
|
+
try {
|
|
327
|
+
await next.hooks['hover:mode:activate'](ctx);
|
|
328
|
+
}
|
|
329
|
+
catch (err) {
|
|
330
|
+
// Activate failed half-way — roll back state so we don't
|
|
331
|
+
// pretend to be in `newModeId` with no sidecars running.
|
|
332
|
+
// Widget still trusts the broadcast below to learn we're back
|
|
333
|
+
// to default. The error is rethrown so the caller can surface
|
|
334
|
+
// it to the user.
|
|
335
|
+
modeChromeProxy = null;
|
|
336
|
+
mcpEnvOverrides.clear();
|
|
337
|
+
currentModeId = null;
|
|
338
|
+
broadcastModes();
|
|
339
|
+
throw err;
|
|
340
|
+
}
|
|
290
341
|
}
|
|
291
342
|
}
|
|
292
343
|
broadcastModes();
|
|
@@ -321,9 +372,19 @@ export async function startService(opts) {
|
|
|
321
372
|
payload: { agentId: currentAgentId, model, version: PROTOCOL_VERSION },
|
|
322
373
|
});
|
|
323
374
|
// Send the agent list as a follow-up event so the widget can render the
|
|
324
|
-
// dropdown immediately on connect / reconnect (e.g. after HMR).
|
|
325
|
-
|
|
326
|
-
|
|
375
|
+
// dropdown immediately on connect / reconnect (e.g. after HMR). The
|
|
376
|
+
// socket may have closed between scheduling and firing, so guard the
|
|
377
|
+
// send and catch any availability-probe rejection — otherwise it
|
|
378
|
+
// surfaces as an unhandled rejection in strict-mode Node.
|
|
379
|
+
void getAvailability(false)
|
|
380
|
+
.then(available => {
|
|
381
|
+
sendIfOpen(ws, {
|
|
382
|
+
type: 'agents',
|
|
383
|
+
payload: { current: currentAgentId, available },
|
|
384
|
+
});
|
|
385
|
+
})
|
|
386
|
+
.catch(err => {
|
|
387
|
+
console.warn('[hover] agents broadcast failed:', err);
|
|
327
388
|
});
|
|
328
389
|
// Send the mode catalogue too, so the widget can render the mode
|
|
329
390
|
// toggle immediately. Empty list when no plugins are loaded.
|
|
@@ -473,15 +534,15 @@ export async function startService(opts) {
|
|
|
473
534
|
return;
|
|
474
535
|
}
|
|
475
536
|
if (msg.type === 'check-cdp') {
|
|
476
|
-
await handleCheckCdp(ws, msg, cdpUrl);
|
|
537
|
+
await handleCheckCdp(ws, msg, cdpUrl, effectiveLaunchExtras());
|
|
477
538
|
return;
|
|
478
539
|
}
|
|
479
540
|
if (msg.type === 'launch-chrome') {
|
|
480
|
-
await handleLaunchChrome(ws, msg, cdpUrl);
|
|
541
|
+
await handleLaunchChrome(ws, msg, cdpUrl, effectiveLaunchExtras());
|
|
481
542
|
return;
|
|
482
543
|
}
|
|
483
544
|
if (msg.type === 'focus-debug') {
|
|
484
|
-
await handleFocusDebug(ws, msg, cdpUrl);
|
|
545
|
+
await handleFocusDebug(ws, msg, cdpUrl, effectiveLaunchExtras());
|
|
485
546
|
return;
|
|
486
547
|
}
|
|
487
548
|
if (msg.type !== 'command')
|
|
@@ -512,7 +573,13 @@ export async function startService(opts) {
|
|
|
512
573
|
// Playwright MCP server would silently launch its own Chromium —
|
|
513
574
|
// and Hover's premise is to drive the user's existing Chrome (with
|
|
514
575
|
// their dev state, cookies, devtools open), never spawn a fresh one.
|
|
515
|
-
|
|
576
|
+
// In an active mode, the relevant CDP endpoint may be the mode's
|
|
577
|
+
// own port (e.g. 9333 for security), not the default cdpUrl.
|
|
578
|
+
const preflightExtras = effectiveLaunchExtras();
|
|
579
|
+
const preflightCdpUrl = preflightExtras?.cdpPort
|
|
580
|
+
? `http://localhost:${preflightExtras.cdpPort}`
|
|
581
|
+
: cdpUrl;
|
|
582
|
+
const cdp = await getPreflight(preflightCdpUrl);
|
|
516
583
|
if (!cdp.ok) {
|
|
517
584
|
send(ws, {
|
|
518
585
|
type: 'event',
|
|
@@ -539,18 +606,21 @@ export async function startService(opts) {
|
|
|
539
606
|
let appendSystemPrompt = resumeSessionId
|
|
540
607
|
? buildCdpHintResume(cdp.tabs)
|
|
541
608
|
: buildCdpHint(cdp.tabs);
|
|
542
|
-
// Add
|
|
543
|
-
//
|
|
544
|
-
//
|
|
545
|
-
//
|
|
546
|
-
//
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
for (const add of
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
609
|
+
// Add plugin-contributed prompt additions whose scope includes the
|
|
610
|
+
// current mode (or '*' for always-on). Walks ALL loaded plugins,
|
|
611
|
+
// not just the active-mode plugin — a plugin that contributes
|
|
612
|
+
// an always-on prompt without contributing a mode is a valid
|
|
613
|
+
// shape (e.g. a future "always remind the agent of these
|
|
614
|
+
// project conventions" plugin).
|
|
615
|
+
for (const p of plugins) {
|
|
616
|
+
for (const add of p.systemPromptAdditions ?? []) {
|
|
617
|
+
// Default scope: if the plugin has a mode, the prompt is
|
|
618
|
+
// gated to that mode; if it doesn't have a mode, the prompt
|
|
619
|
+
// is always-on (treated as if activeInModes was '*').
|
|
620
|
+
const scope = add.activeInModes ?? (p.mode ? [p.mode.id] : ['*']);
|
|
621
|
+
const inScope = scope.includes('*') ||
|
|
622
|
+
(currentModeId !== null && scope.includes(currentModeId));
|
|
623
|
+
if (inScope) {
|
|
554
624
|
appendSystemPrompt = `${appendSystemPrompt}\n\n${add.text}`;
|
|
555
625
|
}
|
|
556
626
|
}
|
|
@@ -603,24 +673,9 @@ export async function startService(opts) {
|
|
|
603
673
|
? ['mcp__playwright', 'Skill', ...activePluginMcpIds]
|
|
604
674
|
: undefined,
|
|
605
675
|
disallowedTools: isHardSandbox
|
|
606
|
-
?
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
'Edit', 'MultiEdit', 'Write', 'Read', 'NotebookEdit',
|
|
610
|
-
'Grep', 'Glob', 'Task', 'TodoWrite',
|
|
611
|
-
'WebFetch', 'WebSearch',
|
|
612
|
-
// plan / worktree / cron / notification — irrelevant in -p mode
|
|
613
|
-
'EnterPlanMode', 'ExitPlanMode',
|
|
614
|
-
'EnterWorktree', 'ExitWorktree',
|
|
615
|
-
'CronCreate', 'CronDelete', 'CronList',
|
|
616
|
-
'PushNotification', 'RemoteTrigger',
|
|
617
|
-
// task & tool introspection added in claude 2.1.x — let through and
|
|
618
|
-
// the agent will burn turns exploring instead of executing
|
|
619
|
-
'ToolSearch',
|
|
620
|
-
'Monitor', 'TaskOutput', 'TaskStop',
|
|
621
|
-
'AskUserQuestion',
|
|
622
|
-
'ShareOnboardingGuide',
|
|
623
|
-
]
|
|
676
|
+
? (invokedDescriptor?.defaultDisallowedTools
|
|
677
|
+
? [...invokedDescriptor.defaultDisallowedTools]
|
|
678
|
+
: undefined)
|
|
624
679
|
: undefined,
|
|
625
680
|
maxBudgetUsd,
|
|
626
681
|
model,
|
|
@@ -632,20 +687,32 @@ export async function startService(opts) {
|
|
|
632
687
|
}
|
|
633
688
|
}
|
|
634
689
|
catch (err) {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
if (
|
|
642
|
-
|
|
690
|
+
// A user-initiated cancel() already sent a synthetic session_end
|
|
691
|
+
// {cancelled:true}. The subsequent AbortError surfacing here would
|
|
692
|
+
// otherwise produce a second session_end{isError:true}, leaving the
|
|
693
|
+
// widget to reconcile two terminal events for one run. CDP isn't
|
|
694
|
+
// suspect either — the user just stopped — so skip preflight
|
|
695
|
+
// invalidation too.
|
|
696
|
+
if (!cancelled) {
|
|
697
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
698
|
+
const errorEvent = {
|
|
699
|
+
kind: 'session_end',
|
|
700
|
+
isError: true,
|
|
701
|
+
summary: message,
|
|
702
|
+
};
|
|
703
|
+
sendIfOpen(ws, { type: 'event', payload: errorEvent });
|
|
704
|
+
// Force the next command to re-probe CDP. The error could be from
|
|
705
|
+
// Chrome dying, MCP spawning a stray Chromium, the user closing
|
|
706
|
+
// their debug window — anything that would make a cached "all
|
|
707
|
+
// healthy" result lie. Invalidate the mode-effective URL (see
|
|
708
|
+
// preflightCdpUrl above) — not the static cdpUrl — so security
|
|
709
|
+
// mode invalidations don't no-op against the default port.
|
|
710
|
+
const invalExtras = effectiveLaunchExtras();
|
|
711
|
+
const invalCdpUrl = invalExtras?.cdpPort
|
|
712
|
+
? `http://localhost:${invalExtras.cdpPort}`
|
|
713
|
+
: cdpUrl;
|
|
714
|
+
invalidatePreflight(invalCdpUrl);
|
|
643
715
|
}
|
|
644
|
-
// Force the next command to re-probe CDP. The error could be from
|
|
645
|
-
// Chrome dying, MCP spawning a stray Chromium, the user closing
|
|
646
|
-
// their debug window — anything that would make a cached "all
|
|
647
|
-
// healthy" result lie.
|
|
648
|
-
invalidatePreflight(cdpUrl);
|
|
649
716
|
}
|
|
650
717
|
finally {
|
|
651
718
|
busy = false;
|