@quanta-intellect/vessel-browser 0.1.145 → 0.1.147
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/out/main/index.js
CHANGED
|
@@ -175,12 +175,32 @@ function sanitizeSidebarDetachedBounds(value) {
|
|
|
175
175
|
height: Math.max(DETACHED_SIDEBAR_MIN_HEIGHT, Math.round(height))
|
|
176
176
|
};
|
|
177
177
|
}
|
|
178
|
+
const DETACHED_DEVTOOLS_DEFAULT_WIDTH = 920;
|
|
179
|
+
const DETACHED_DEVTOOLS_DEFAULT_HEIGHT = 560;
|
|
180
|
+
const DETACHED_DEVTOOLS_MIN_WIDTH = 520;
|
|
181
|
+
const DETACHED_DEVTOOLS_MIN_HEIGHT = 320;
|
|
182
|
+
function sanitizeDevToolsDetachedBounds(value) {
|
|
183
|
+
if (!value || typeof value !== "object") return null;
|
|
184
|
+
const bounds = value;
|
|
185
|
+
const width = Number(bounds.width);
|
|
186
|
+
const height = Number(bounds.height);
|
|
187
|
+
if (!Number.isFinite(width) || !Number.isFinite(height)) return null;
|
|
188
|
+
const x = Number(bounds.x);
|
|
189
|
+
const y = Number(bounds.y);
|
|
190
|
+
return {
|
|
191
|
+
...Number.isFinite(x) ? { x: Math.round(x) } : {},
|
|
192
|
+
...Number.isFinite(y) ? { y: Math.round(y) } : {},
|
|
193
|
+
width: Math.max(DETACHED_DEVTOOLS_MIN_WIDTH, Math.round(width)),
|
|
194
|
+
height: Math.max(DETACHED_DEVTOOLS_MIN_HEIGHT, Math.round(height))
|
|
195
|
+
};
|
|
196
|
+
}
|
|
178
197
|
const defaults = {
|
|
179
198
|
defaultUrl: "https://start.duckduckgo.com",
|
|
180
199
|
theme: "dark",
|
|
181
200
|
sidebarPanelMode: "docked",
|
|
182
201
|
sidebarWidth: 400,
|
|
183
202
|
sidebarDetachedBounds: null,
|
|
203
|
+
devtoolsPanelDetachedBounds: null,
|
|
184
204
|
mcpPort: 3100,
|
|
185
205
|
autoRestoreSession: true,
|
|
186
206
|
clearBookmarksOnLaunch: false,
|
|
@@ -222,6 +242,15 @@ const SettingsValueSchemas = {
|
|
|
222
242
|
height: zod.z.number().int().min(DETACHED_SIDEBAR_MIN_HEIGHT)
|
|
223
243
|
})
|
|
224
244
|
]),
|
|
245
|
+
devtoolsPanelDetachedBounds: zod.z.union([
|
|
246
|
+
zod.z.null(),
|
|
247
|
+
zod.z.object({
|
|
248
|
+
x: zod.z.number().optional(),
|
|
249
|
+
y: zod.z.number().optional(),
|
|
250
|
+
width: zod.z.number().int().min(DETACHED_DEVTOOLS_MIN_WIDTH),
|
|
251
|
+
height: zod.z.number().int().min(DETACHED_DEVTOOLS_MIN_HEIGHT)
|
|
252
|
+
})
|
|
253
|
+
]),
|
|
225
254
|
mcpPort: zod.z.number().int().min(1).max(65535),
|
|
226
255
|
autoRestoreSession: zod.z.boolean(),
|
|
227
256
|
clearBookmarksOnLaunch: zod.z.boolean(),
|
|
@@ -477,6 +506,9 @@ function loadSettings() {
|
|
|
477
506
|
sidebarDetachedBounds: sanitizeSidebarDetachedBounds(
|
|
478
507
|
parsed.sidebarDetachedBounds
|
|
479
508
|
),
|
|
509
|
+
devtoolsPanelDetachedBounds: sanitizeDevToolsDetachedBounds(
|
|
510
|
+
parsed.devtoolsPanelDetachedBounds
|
|
511
|
+
),
|
|
480
512
|
sourceDoNotAllowList: sanitizeStringList(
|
|
481
513
|
parsed.sourceDoNotAllowList ?? defaults.sourceDoNotAllowList
|
|
482
514
|
),
|
|
@@ -530,6 +562,8 @@ function setSetting(key2, value) {
|
|
|
530
562
|
settings.sidebarPanelMode = sanitizeSidebarPanelMode(value);
|
|
531
563
|
} else if (key2 === "sidebarDetachedBounds") {
|
|
532
564
|
settings.sidebarDetachedBounds = sanitizeSidebarDetachedBounds(value);
|
|
565
|
+
} else if (key2 === "devtoolsPanelDetachedBounds") {
|
|
566
|
+
settings.devtoolsPanelDetachedBounds = sanitizeDevToolsDetachedBounds(value);
|
|
533
567
|
} else if (key2 === "sourceDoNotAllowList") {
|
|
534
568
|
settings.sourceDoNotAllowList = sanitizeStringList(value);
|
|
535
569
|
} else if (key2 === "chatProvider") {
|
|
@@ -682,15 +716,6 @@ function loadTrustedAppURL(wc, url) {
|
|
|
682
716
|
}
|
|
683
717
|
return wc.loadURL(parsed.toString());
|
|
684
718
|
}
|
|
685
|
-
const urlSafety = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
686
|
-
__proto__: null,
|
|
687
|
-
assertPermittedNavigationURL,
|
|
688
|
-
assertSafeURL,
|
|
689
|
-
isSafeNavigationURL,
|
|
690
|
-
loadInternalDataURL,
|
|
691
|
-
loadPermittedNavigationURL,
|
|
692
|
-
loadTrustedAppURL
|
|
693
|
-
}, Symbol.toStringTag, { value: "Module" }));
|
|
694
719
|
const MAX_CUSTOM_HISTORY = 50;
|
|
695
720
|
const READER_MODE_DATA_URL_PREFIX = "data:text/html;charset=utf-8,";
|
|
696
721
|
const logger$B = createLogger("Tab");
|
|
@@ -2598,6 +2623,9 @@ class DevToolsSession {
|
|
|
2598
2623
|
entryCounter = 0;
|
|
2599
2624
|
// Track in-flight network requests for matching response data
|
|
2600
2625
|
pendingRequests = /* @__PURE__ */ new Map();
|
|
2626
|
+
// Notified whenever a captured buffer mutates so the DevTools panel can
|
|
2627
|
+
// refresh independent of agent actions (e.g. while a user browses).
|
|
2628
|
+
onCaptureChange = null;
|
|
2601
2629
|
// Named handlers so we can remove them on detach/destroy (fixes listener leak)
|
|
2602
2630
|
onDetach = () => {
|
|
2603
2631
|
this.attached = false;
|
|
@@ -2613,6 +2641,27 @@ class DevToolsSession {
|
|
|
2613
2641
|
get isAttached() {
|
|
2614
2642
|
return this.attached;
|
|
2615
2643
|
}
|
|
2644
|
+
/**
|
|
2645
|
+
* Register a callback invoked whenever a captured console/network/error
|
|
2646
|
+
* entry is added or updated. Used to push live updates to the DevTools
|
|
2647
|
+
* panel while a user browses (not just when an agent acts).
|
|
2648
|
+
*/
|
|
2649
|
+
setOnCaptureChange(cb) {
|
|
2650
|
+
this.onCaptureChange = cb;
|
|
2651
|
+
}
|
|
2652
|
+
/**
|
|
2653
|
+
* Enable all capture domains (console, network, errors) in one call.
|
|
2654
|
+
* Idempotent per-domain. Used by the panel-open path so manual browsing is
|
|
2655
|
+
* captured without requiring an agent tool invocation.
|
|
2656
|
+
*/
|
|
2657
|
+
async enableCapture() {
|
|
2658
|
+
await this.ensureConsoleDomain();
|
|
2659
|
+
await this.ensureNetworkDomain();
|
|
2660
|
+
await this.ensureErrorCapture();
|
|
2661
|
+
}
|
|
2662
|
+
notifyCaptureChange() {
|
|
2663
|
+
this.onCaptureChange?.();
|
|
2664
|
+
}
|
|
2616
2665
|
async ensureAttached() {
|
|
2617
2666
|
if (this.attached) return;
|
|
2618
2667
|
if (this.attachingPromise) return this.attachingPromise;
|
|
@@ -2663,6 +2712,7 @@ class DevToolsSession {
|
|
|
2663
2712
|
}
|
|
2664
2713
|
destroy() {
|
|
2665
2714
|
this.detach();
|
|
2715
|
+
this.onCaptureChange = null;
|
|
2666
2716
|
this.consoleBuffer = [];
|
|
2667
2717
|
this.networkBuffer = [];
|
|
2668
2718
|
this.errorBuffer = [];
|
|
@@ -3120,6 +3170,7 @@ class DevToolsSession {
|
|
|
3120
3170
|
if (this.consoleBuffer.length > MAX_CONSOLE_ENTRIES) {
|
|
3121
3171
|
this.consoleBuffer = this.consoleBuffer.slice(-MAX_CONSOLE_ENTRIES);
|
|
3122
3172
|
}
|
|
3173
|
+
this.notifyCaptureChange();
|
|
3123
3174
|
}
|
|
3124
3175
|
// --- Network events ---
|
|
3125
3176
|
onNetworkRequest(params) {
|
|
@@ -3148,6 +3199,7 @@ class DevToolsSession {
|
|
|
3148
3199
|
if (oldest !== void 0) this.pendingRequests.delete(oldest);
|
|
3149
3200
|
}
|
|
3150
3201
|
this.pendingRequests.set(requestId, { entry });
|
|
3202
|
+
this.notifyCaptureChange();
|
|
3151
3203
|
}
|
|
3152
3204
|
onNetworkResponse(params) {
|
|
3153
3205
|
const requestId = params.requestId;
|
|
@@ -3163,6 +3215,7 @@ class DevToolsSession {
|
|
|
3163
3215
|
if (contentLength) {
|
|
3164
3216
|
pending.entry.contentLength = parseInt(contentLength, 10) || void 0;
|
|
3165
3217
|
}
|
|
3218
|
+
this.notifyCaptureChange();
|
|
3166
3219
|
}
|
|
3167
3220
|
onNetworkFinished(params) {
|
|
3168
3221
|
const requestId = params.requestId;
|
|
@@ -3177,6 +3230,7 @@ class DevToolsSession {
|
|
|
3177
3230
|
}
|
|
3178
3231
|
pending.entry.contentLength = pending.entry.contentLength ?? (params.encodedDataLength || void 0);
|
|
3179
3232
|
this.pendingRequests.delete(requestId);
|
|
3233
|
+
this.notifyCaptureChange();
|
|
3180
3234
|
}
|
|
3181
3235
|
onNetworkFailed(params) {
|
|
3182
3236
|
const requestId = params.requestId;
|
|
@@ -3191,6 +3245,7 @@ class DevToolsSession {
|
|
|
3191
3245
|
);
|
|
3192
3246
|
}
|
|
3193
3247
|
this.pendingRequests.delete(requestId);
|
|
3248
|
+
this.notifyCaptureChange();
|
|
3194
3249
|
}
|
|
3195
3250
|
// --- Error events ---
|
|
3196
3251
|
onExceptionThrown(params) {
|
|
@@ -3217,6 +3272,7 @@ class DevToolsSession {
|
|
|
3217
3272
|
if (this.errorBuffer.length > MAX_ERROR_ENTRIES) {
|
|
3218
3273
|
this.errorBuffer = this.errorBuffer.slice(-MAX_ERROR_ENTRIES);
|
|
3219
3274
|
}
|
|
3275
|
+
this.notifyCaptureChange();
|
|
3220
3276
|
}
|
|
3221
3277
|
}
|
|
3222
3278
|
function mapConsoleLevel(level) {
|
|
@@ -3940,8 +3996,19 @@ const ContentChannels = {
|
|
|
3940
3996
|
};
|
|
3941
3997
|
const DevToolsChannels = {
|
|
3942
3998
|
DEVTOOLS_PANEL_TOGGLE: "devtools-panel:toggle",
|
|
3999
|
+
DEVTOOLS_PANEL_CLOSE: "devtools-panel:close",
|
|
4000
|
+
DEVTOOLS_PANEL_OPEN_TAB: "devtools-panel:open-tab",
|
|
4001
|
+
DEVTOOLS_PANEL_SELECT_TAB: "devtools-panel:select-tab",
|
|
4002
|
+
DEVTOOLS_PANEL_STATE_GET: "devtools-panel:state-get",
|
|
3943
4003
|
DEVTOOLS_PANEL_STATE: "devtools-panel:state",
|
|
3944
|
-
|
|
4004
|
+
DEVTOOLS_PANEL_RESIZE_START: "devtools-panel:resize-start",
|
|
4005
|
+
DEVTOOLS_PANEL_RESIZE: "devtools-panel:resize",
|
|
4006
|
+
DEVTOOLS_PANEL_RESIZE_COMMIT: "devtools-panel:resize-commit",
|
|
4007
|
+
DEVTOOLS_PANEL_POPOUT: "devtools-panel:popout",
|
|
4008
|
+
DEVTOOLS_PANEL_DOCK: "devtools-panel:dock",
|
|
4009
|
+
DEVTOOLS_PANEL_HOST_STATE_GET: "devtools-panel:host-state-get",
|
|
4010
|
+
DEVTOOLS_PANEL_HOST_STATE: "devtools-panel:host-state",
|
|
4011
|
+
DEVTOOLS_PAGE_MAP_REVEAL: "devtools-panel:page-map:reveal"
|
|
3945
4012
|
};
|
|
3946
4013
|
const DownloadChannels = {
|
|
3947
4014
|
DOWNLOAD_STARTED: "download:started",
|
|
@@ -7215,6 +7282,236 @@ function sendSafe(wc, channel, ...args) {
|
|
|
7215
7282
|
}
|
|
7216
7283
|
}
|
|
7217
7284
|
}
|
|
7285
|
+
function closeDetachedViewWindow(state2, host) {
|
|
7286
|
+
const detachedWindow = host.getWindow(state2);
|
|
7287
|
+
if (!detachedWindow) return false;
|
|
7288
|
+
host.setWindow(state2, null);
|
|
7289
|
+
host.setClosing(state2, true);
|
|
7290
|
+
detachedWindow.once("closed", () => {
|
|
7291
|
+
host.setClosing(state2, false);
|
|
7292
|
+
});
|
|
7293
|
+
detachedWindow.close();
|
|
7294
|
+
return true;
|
|
7295
|
+
}
|
|
7296
|
+
function moveDetachedViewToMainWindow(state2, host) {
|
|
7297
|
+
const view = host.getView(state2);
|
|
7298
|
+
host.getWindow(state2)?.contentView.removeChildView(view);
|
|
7299
|
+
state2.mainWindow.contentView.addChildView(view);
|
|
7300
|
+
}
|
|
7301
|
+
function createDetachedViewWindow(state2, host) {
|
|
7302
|
+
const detachedWindow = new electron.BaseWindow(host.createWindowOptions(state2));
|
|
7303
|
+
const view = host.getView(state2);
|
|
7304
|
+
state2.mainWindow.contentView.removeChildView(view);
|
|
7305
|
+
detachedWindow.contentView.addChildView(view);
|
|
7306
|
+
host.setWindow(state2, detachedWindow);
|
|
7307
|
+
detachedWindow.on("resize", () => {
|
|
7308
|
+
host.layoutView(state2);
|
|
7309
|
+
host.persistBounds(state2);
|
|
7310
|
+
});
|
|
7311
|
+
detachedWindow.on("move", () => host.persistBounds(state2));
|
|
7312
|
+
detachedWindow.on("close", (event) => {
|
|
7313
|
+
if (host.isClosing(state2)) return;
|
|
7314
|
+
event.preventDefault();
|
|
7315
|
+
host.onNativeClose(state2);
|
|
7316
|
+
});
|
|
7317
|
+
detachedWindow.on("closed", () => {
|
|
7318
|
+
if (host.getWindow(state2) !== detachedWindow) return;
|
|
7319
|
+
host.onUnexpectedClosed(state2, detachedWindow);
|
|
7320
|
+
});
|
|
7321
|
+
return detachedWindow;
|
|
7322
|
+
}
|
|
7323
|
+
const devToolsDetachedHost = {
|
|
7324
|
+
getWindow: (state2) => state2.devtoolsPanelWindow,
|
|
7325
|
+
setWindow: (state2, window2) => {
|
|
7326
|
+
state2.devtoolsPanelWindow = window2;
|
|
7327
|
+
},
|
|
7328
|
+
isClosing: (state2) => state2.devtoolsPanelWindowClosing,
|
|
7329
|
+
setClosing: (state2, closing) => {
|
|
7330
|
+
state2.devtoolsPanelWindowClosing = closing;
|
|
7331
|
+
},
|
|
7332
|
+
getView: (state2) => state2.devtoolsPanelView
|
|
7333
|
+
};
|
|
7334
|
+
function setDevToolsPanelMode(state2, mode) {
|
|
7335
|
+
state2.uiState.devtoolsPanelMode = mode;
|
|
7336
|
+
}
|
|
7337
|
+
function persistDetachedBounds$1(state2) {
|
|
7338
|
+
const devtoolsWindow = state2.devtoolsPanelWindow;
|
|
7339
|
+
if (!devtoolsWindow || devtoolsWindow.isDestroyed()) return;
|
|
7340
|
+
const bounds = devtoolsWindow.getBounds();
|
|
7341
|
+
state2.uiState.devtoolsPanelDetachedBounds = {
|
|
7342
|
+
x: bounds.x,
|
|
7343
|
+
y: bounds.y,
|
|
7344
|
+
width: bounds.width,
|
|
7345
|
+
height: bounds.height
|
|
7346
|
+
};
|
|
7347
|
+
setSetting(
|
|
7348
|
+
"devtoolsPanelDetachedBounds",
|
|
7349
|
+
state2.uiState.devtoolsPanelDetachedBounds
|
|
7350
|
+
);
|
|
7351
|
+
}
|
|
7352
|
+
function moveDevToolsToMainWindow(state2) {
|
|
7353
|
+
moveDetachedViewToMainWindow(state2, devToolsDetachedHost);
|
|
7354
|
+
}
|
|
7355
|
+
function isDevToolsPanelDocked(state2) {
|
|
7356
|
+
return state2.uiState.devtoolsPanelMode === "docked";
|
|
7357
|
+
}
|
|
7358
|
+
function isDevToolsPanelDetached(state2) {
|
|
7359
|
+
return state2.uiState.devtoolsPanelMode === "detached";
|
|
7360
|
+
}
|
|
7361
|
+
function getDevToolsPanelHostState(state2) {
|
|
7362
|
+
return {
|
|
7363
|
+
open: state2.uiState.devtoolsPanelMode !== "closed",
|
|
7364
|
+
detached: isDevToolsPanelDetached(state2),
|
|
7365
|
+
height: state2.uiState.devtoolsPanelHeight
|
|
7366
|
+
};
|
|
7367
|
+
}
|
|
7368
|
+
function emitDevToolsPanelHostState(state2) {
|
|
7369
|
+
const panelState = getDevToolsPanelHostState(state2);
|
|
7370
|
+
sendSafe(
|
|
7371
|
+
state2.chromeView.webContents,
|
|
7372
|
+
Channels.DEVTOOLS_PANEL_HOST_STATE,
|
|
7373
|
+
panelState
|
|
7374
|
+
);
|
|
7375
|
+
sendSafe(
|
|
7376
|
+
state2.devtoolsPanelView.webContents,
|
|
7377
|
+
Channels.DEVTOOLS_PANEL_HOST_STATE,
|
|
7378
|
+
panelState
|
|
7379
|
+
);
|
|
7380
|
+
return panelState;
|
|
7381
|
+
}
|
|
7382
|
+
function closeDetachedDevToolsPanelWindow(state2) {
|
|
7383
|
+
return closeDetachedViewWindow(state2, devToolsDetachedHost);
|
|
7384
|
+
}
|
|
7385
|
+
function layoutDetachedDevToolsPanel(state2) {
|
|
7386
|
+
if (!state2.devtoolsPanelWindow) return;
|
|
7387
|
+
const [width, height] = state2.devtoolsPanelWindow.getContentSize();
|
|
7388
|
+
state2.devtoolsPanelView.setBounds({ x: 0, y: 0, width, height });
|
|
7389
|
+
}
|
|
7390
|
+
function toggleDockedDevToolsPanel(state2, hooks) {
|
|
7391
|
+
if (isDevToolsPanelDetached(state2)) {
|
|
7392
|
+
state2.devtoolsPanelWindow?.focus();
|
|
7393
|
+
return getDevToolsPanelHostState(state2);
|
|
7394
|
+
}
|
|
7395
|
+
setDevToolsPanelMode(
|
|
7396
|
+
state2,
|
|
7397
|
+
isDevToolsPanelDocked(state2) ? "closed" : "docked"
|
|
7398
|
+
);
|
|
7399
|
+
hooks.relayout();
|
|
7400
|
+
return emitDevToolsPanelHostState(state2);
|
|
7401
|
+
}
|
|
7402
|
+
function resizeDockedDevToolsPanel(state2, height, relayout) {
|
|
7403
|
+
state2.uiState.devtoolsPanelHeight = Math.round(height);
|
|
7404
|
+
if (isDevToolsPanelDocked(state2)) {
|
|
7405
|
+
relayout();
|
|
7406
|
+
}
|
|
7407
|
+
return emitDevToolsPanelHostState(state2);
|
|
7408
|
+
}
|
|
7409
|
+
function detachDevToolsPanel(state2, hooks) {
|
|
7410
|
+
if (state2.devtoolsPanelWindow) {
|
|
7411
|
+
state2.devtoolsPanelWindow.focus();
|
|
7412
|
+
return getDevToolsPanelHostState(state2);
|
|
7413
|
+
}
|
|
7414
|
+
const detachedBounds = state2.uiState.devtoolsPanelDetachedBounds;
|
|
7415
|
+
const devtoolsWindow = createDetachedViewWindow(state2, {
|
|
7416
|
+
...devToolsDetachedHost,
|
|
7417
|
+
createWindowOptions: () => ({
|
|
7418
|
+
...typeof detachedBounds?.x === "number" ? { x: detachedBounds.x } : {},
|
|
7419
|
+
...typeof detachedBounds?.y === "number" ? { y: detachedBounds.y } : {},
|
|
7420
|
+
width: Math.max(
|
|
7421
|
+
DETACHED_DEVTOOLS_MIN_WIDTH,
|
|
7422
|
+
Math.round(detachedBounds?.width ?? DETACHED_DEVTOOLS_DEFAULT_WIDTH)
|
|
7423
|
+
),
|
|
7424
|
+
height: Math.max(
|
|
7425
|
+
DETACHED_DEVTOOLS_MIN_HEIGHT,
|
|
7426
|
+
Math.round(detachedBounds?.height ?? DETACHED_DEVTOOLS_DEFAULT_HEIGHT)
|
|
7427
|
+
),
|
|
7428
|
+
minWidth: DETACHED_DEVTOOLS_MIN_WIDTH,
|
|
7429
|
+
minHeight: DETACHED_DEVTOOLS_MIN_HEIGHT,
|
|
7430
|
+
frame: true,
|
|
7431
|
+
show: false,
|
|
7432
|
+
backgroundColor: "#1a1a1e",
|
|
7433
|
+
title: "Vessel DevTools",
|
|
7434
|
+
icon: hooks.getWindowIconPath()
|
|
7435
|
+
}),
|
|
7436
|
+
layoutView: layoutDetachedDevToolsPanel,
|
|
7437
|
+
persistBounds: persistDetachedBounds$1,
|
|
7438
|
+
onNativeClose: () => dockDevToolsPanel(state2, hooks),
|
|
7439
|
+
onUnexpectedClosed: () => {
|
|
7440
|
+
state2.devtoolsPanelWindow = null;
|
|
7441
|
+
setDevToolsPanelMode(state2, "docked");
|
|
7442
|
+
state2.mainWindow.contentView.addChildView(state2.devtoolsPanelView);
|
|
7443
|
+
hooks.relayout();
|
|
7444
|
+
emitDevToolsPanelHostState(state2);
|
|
7445
|
+
}
|
|
7446
|
+
});
|
|
7447
|
+
setDevToolsPanelMode(state2, "detached");
|
|
7448
|
+
hooks.relayout();
|
|
7449
|
+
layoutDetachedDevToolsPanel(state2);
|
|
7450
|
+
devtoolsWindow.show();
|
|
7451
|
+
devtoolsWindow.focus();
|
|
7452
|
+
return emitDevToolsPanelHostState(state2);
|
|
7453
|
+
}
|
|
7454
|
+
function dockDevToolsPanel(state2, hooks) {
|
|
7455
|
+
const devtoolsWindow = state2.devtoolsPanelWindow;
|
|
7456
|
+
setDevToolsPanelMode(state2, "docked");
|
|
7457
|
+
if (devtoolsWindow) {
|
|
7458
|
+
moveDevToolsToMainWindow(state2);
|
|
7459
|
+
hooks.relayout();
|
|
7460
|
+
closeDetachedDevToolsPanelWindow(state2);
|
|
7461
|
+
state2.mainWindow.focus();
|
|
7462
|
+
} else {
|
|
7463
|
+
hooks.relayout();
|
|
7464
|
+
}
|
|
7465
|
+
return emitDevToolsPanelHostState(state2);
|
|
7466
|
+
}
|
|
7467
|
+
function closeDevToolsPanel(state2, hooks) {
|
|
7468
|
+
if (state2.devtoolsPanelWindow) {
|
|
7469
|
+
moveDevToolsToMainWindow(state2);
|
|
7470
|
+
closeDetachedDevToolsPanelWindow(state2);
|
|
7471
|
+
}
|
|
7472
|
+
setDevToolsPanelMode(state2, "closed");
|
|
7473
|
+
hooks.relayout();
|
|
7474
|
+
return emitDevToolsPanelHostState(state2);
|
|
7475
|
+
}
|
|
7476
|
+
function registerDisabledDevToolsPanelHandlers(ipc) {
|
|
7477
|
+
const disabledDevToolsState = {
|
|
7478
|
+
open: false,
|
|
7479
|
+
detached: false,
|
|
7480
|
+
height: 0
|
|
7481
|
+
};
|
|
7482
|
+
const disabledPanelState = {
|
|
7483
|
+
console: [],
|
|
7484
|
+
network: [],
|
|
7485
|
+
errors: [],
|
|
7486
|
+
activity: [],
|
|
7487
|
+
agentTrace: [],
|
|
7488
|
+
pageMap: null
|
|
7489
|
+
};
|
|
7490
|
+
ipc.handle(Channels.DEVTOOLS_PANEL_TOGGLE, () => disabledDevToolsState);
|
|
7491
|
+
ipc.handle(Channels.DEVTOOLS_PANEL_CLOSE, () => disabledDevToolsState);
|
|
7492
|
+
ipc.handle(Channels.DEVTOOLS_PANEL_OPEN_TAB, () => disabledDevToolsState);
|
|
7493
|
+
ipc.handle(Channels.DEVTOOLS_PANEL_STATE_GET, () => disabledPanelState);
|
|
7494
|
+
ipc.handle(Channels.DEVTOOLS_PANEL_RESIZE_START, () => void 0);
|
|
7495
|
+
ipc.handle(Channels.DEVTOOLS_PANEL_RESIZE, () => 0);
|
|
7496
|
+
ipc.handle(Channels.DEVTOOLS_PANEL_RESIZE_COMMIT, () => void 0);
|
|
7497
|
+
ipc.handle(Channels.DEVTOOLS_PANEL_POPOUT, () => disabledDevToolsState);
|
|
7498
|
+
ipc.handle(Channels.DEVTOOLS_PANEL_DOCK, () => disabledDevToolsState);
|
|
7499
|
+
ipc.handle(
|
|
7500
|
+
Channels.DEVTOOLS_PANEL_HOST_STATE_GET,
|
|
7501
|
+
() => disabledDevToolsState
|
|
7502
|
+
);
|
|
7503
|
+
}
|
|
7504
|
+
const sidebarDetachedHost = {
|
|
7505
|
+
getWindow: (state2) => state2.sidebarWindow,
|
|
7506
|
+
setWindow: (state2, window2) => {
|
|
7507
|
+
state2.sidebarWindow = window2;
|
|
7508
|
+
},
|
|
7509
|
+
isClosing: (state2) => state2.sidebarWindowClosing,
|
|
7510
|
+
setClosing: (state2, closing) => {
|
|
7511
|
+
state2.sidebarWindowClosing = closing;
|
|
7512
|
+
},
|
|
7513
|
+
getView: (state2) => state2.sidebarView
|
|
7514
|
+
};
|
|
7218
7515
|
function setSidebarPanelMode(state2, mode, reason = "user") {
|
|
7219
7516
|
state2.uiState.sidebarPanelMode = mode;
|
|
7220
7517
|
if (reason === "user") {
|
|
@@ -7234,19 +7531,10 @@ function persistDetachedBounds(state2) {
|
|
|
7234
7531
|
setSetting("sidebarDetachedBounds", state2.uiState.sidebarDetachedBounds);
|
|
7235
7532
|
}
|
|
7236
7533
|
function closeDetachedSidebarWindow(state2) {
|
|
7237
|
-
|
|
7238
|
-
if (!sidebarWindow) return false;
|
|
7239
|
-
state2.sidebarWindow = null;
|
|
7240
|
-
state2.sidebarWindowClosing = true;
|
|
7241
|
-
sidebarWindow.once("closed", () => {
|
|
7242
|
-
state2.sidebarWindowClosing = false;
|
|
7243
|
-
});
|
|
7244
|
-
sidebarWindow.close();
|
|
7245
|
-
return true;
|
|
7534
|
+
return closeDetachedViewWindow(state2, sidebarDetachedHost);
|
|
7246
7535
|
}
|
|
7247
7536
|
function moveSidebarToMainWindow(state2) {
|
|
7248
|
-
|
|
7249
|
-
state2.mainWindow.contentView.addChildView(state2.sidebarView);
|
|
7537
|
+
moveDetachedViewToMainWindow(state2, sidebarDetachedHost);
|
|
7250
7538
|
}
|
|
7251
7539
|
function getSidebarPanelState(state2) {
|
|
7252
7540
|
return {
|
|
@@ -7306,41 +7594,33 @@ function detachSidebar(state2, hooks) {
|
|
|
7306
7594
|
const detachedBounds = state2.uiState.sidebarDetachedBounds;
|
|
7307
7595
|
const detachedWidth = detachedBounds?.width ?? Math.max(DETACHED_SIDEBAR_DEFAULT_WIDTH, state2.uiState.sidebarWidth);
|
|
7308
7596
|
const detachedHeight = detachedBounds?.height ?? DETACHED_SIDEBAR_DEFAULT_HEIGHT;
|
|
7309
|
-
const sidebarWindow =
|
|
7310
|
-
...
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7597
|
+
const sidebarWindow = createDetachedViewWindow(state2, {
|
|
7598
|
+
...sidebarDetachedHost,
|
|
7599
|
+
createWindowOptions: () => ({
|
|
7600
|
+
...typeof detachedBounds?.x === "number" ? { x: detachedBounds.x } : {},
|
|
7601
|
+
...typeof detachedBounds?.y === "number" ? { y: detachedBounds.y } : {},
|
|
7602
|
+
width: Math.max(DETACHED_SIDEBAR_MIN_WIDTH, Math.round(detachedWidth)),
|
|
7603
|
+
height: Math.max(DETACHED_SIDEBAR_MIN_HEIGHT, Math.round(detachedHeight)),
|
|
7604
|
+
minWidth: DETACHED_SIDEBAR_MIN_WIDTH,
|
|
7605
|
+
minHeight: DETACHED_SIDEBAR_MIN_HEIGHT,
|
|
7606
|
+
frame: true,
|
|
7607
|
+
show: false,
|
|
7608
|
+
backgroundColor: "#1a1a1e",
|
|
7609
|
+
title: "Vessel Agent",
|
|
7610
|
+
icon: hooks.getWindowIconPath()
|
|
7611
|
+
}),
|
|
7612
|
+
layoutView: layoutDetachedSidebar,
|
|
7613
|
+
persistBounds: persistDetachedBounds,
|
|
7614
|
+
onNativeClose: () => dockSidebar(state2, hooks),
|
|
7615
|
+
onUnexpectedClosed: () => {
|
|
7616
|
+
state2.sidebarWindow = null;
|
|
7617
|
+
setSidebarPanelMode(state2, "docked");
|
|
7618
|
+
state2.mainWindow.contentView.addChildView(state2.sidebarView);
|
|
7619
|
+
hooks.relayout();
|
|
7620
|
+
emitSidebarPanelState(state2);
|
|
7621
|
+
}
|
|
7321
7622
|
});
|
|
7322
|
-
state2.mainWindow.contentView.removeChildView(state2.sidebarView);
|
|
7323
|
-
sidebarWindow.contentView.addChildView(state2.sidebarView);
|
|
7324
|
-
state2.sidebarWindow = sidebarWindow;
|
|
7325
7623
|
setSidebarPanelMode(state2, "detached");
|
|
7326
|
-
sidebarWindow.on("resize", () => {
|
|
7327
|
-
layoutDetachedSidebar(state2);
|
|
7328
|
-
persistDetachedBounds(state2);
|
|
7329
|
-
});
|
|
7330
|
-
sidebarWindow.on("move", () => persistDetachedBounds(state2));
|
|
7331
|
-
sidebarWindow.on("close", (event) => {
|
|
7332
|
-
if (state2.sidebarWindowClosing) return;
|
|
7333
|
-
event.preventDefault();
|
|
7334
|
-
dockSidebar(state2, hooks);
|
|
7335
|
-
});
|
|
7336
|
-
sidebarWindow.on("closed", () => {
|
|
7337
|
-
if (state2.sidebarWindow !== sidebarWindow) return;
|
|
7338
|
-
state2.sidebarWindow = null;
|
|
7339
|
-
setSidebarPanelMode(state2, "docked");
|
|
7340
|
-
state2.mainWindow.contentView.addChildView(state2.sidebarView);
|
|
7341
|
-
hooks.relayout();
|
|
7342
|
-
emitSidebarPanelState(state2);
|
|
7343
|
-
});
|
|
7344
7624
|
hooks.relayout();
|
|
7345
7625
|
layoutDetachedSidebar(state2);
|
|
7346
7626
|
sidebarWindow.show();
|
|
@@ -7563,8 +7843,9 @@ function createMainWindow(onTabStateChange) {
|
|
|
7563
7843
|
sidebarDetachedBounds: settings2.sidebarDetachedBounds,
|
|
7564
7844
|
focusMode: false,
|
|
7565
7845
|
settingsOpen: false,
|
|
7566
|
-
|
|
7567
|
-
devtoolsPanelHeight: DEFAULT_DEVTOOLS_PANEL_HEIGHT
|
|
7846
|
+
devtoolsPanelMode: "closed",
|
|
7847
|
+
devtoolsPanelHeight: DEFAULT_DEVTOOLS_PANEL_HEIGHT,
|
|
7848
|
+
devtoolsPanelDetachedBounds: settings2.devtoolsPanelDetachedBounds
|
|
7568
7849
|
};
|
|
7569
7850
|
const tabManager = new TabManager(mainWindow, onTabStateChange);
|
|
7570
7851
|
const sendToRendererViews = (channel, ...args) => {
|
|
@@ -7580,6 +7861,8 @@ function createMainWindow(onTabStateChange) {
|
|
|
7580
7861
|
mainWindow,
|
|
7581
7862
|
sidebarWindow: null,
|
|
7582
7863
|
sidebarWindowClosing: false,
|
|
7864
|
+
devtoolsPanelWindow: null,
|
|
7865
|
+
devtoolsPanelWindowClosing: false,
|
|
7583
7866
|
chromeView,
|
|
7584
7867
|
sidebarView,
|
|
7585
7868
|
devtoolsPanelView,
|
|
@@ -7591,6 +7874,7 @@ function createMainWindow(onTabStateChange) {
|
|
|
7591
7874
|
mainWindow.on("focus", () => layoutViews(state2));
|
|
7592
7875
|
mainWindow.on("closed", () => {
|
|
7593
7876
|
closeDetachedSidebarWindow(state2);
|
|
7877
|
+
closeDetachedDevToolsPanelWindow(state2);
|
|
7594
7878
|
});
|
|
7595
7879
|
sidebarView.webContents.on("context-menu", (event, params) => {
|
|
7596
7880
|
event.preventDefault();
|
|
@@ -7619,7 +7903,9 @@ function layoutViews(state2) {
|
|
|
7619
7903
|
const chromeHeight = uiState.focusMode ? 0 : CHROME_HEIGHT;
|
|
7620
7904
|
const sidebarAttached = isSidebarAttached(state2);
|
|
7621
7905
|
const sidebarWidth = sidebarAttached ? uiState.sidebarWidth : 0;
|
|
7622
|
-
const
|
|
7906
|
+
const devtoolsDocked = isDevToolsPanelDocked(state2);
|
|
7907
|
+
const devtoolsDetached = isDevToolsPanelDetached(state2);
|
|
7908
|
+
const devtoolsHeight = devtoolsDocked ? uiState.devtoolsPanelHeight : 0;
|
|
7623
7909
|
const chromeNeedsFullHeight = uiState.settingsOpen;
|
|
7624
7910
|
if (chromeNeedsFullHeight) {
|
|
7625
7911
|
chromeView.setBounds({ x: 0, y: 0, width, height });
|
|
@@ -7637,14 +7923,14 @@ function layoutViews(state2) {
|
|
|
7637
7923
|
sidebarView.setBounds({ x: width, y: 0, width: 0, height: 0 });
|
|
7638
7924
|
}
|
|
7639
7925
|
const contentWidth = width - sidebarWidth;
|
|
7640
|
-
if (
|
|
7926
|
+
if (devtoolsDocked) {
|
|
7641
7927
|
devtoolsPanelView.setBounds({
|
|
7642
7928
|
x: 0,
|
|
7643
7929
|
y: height - devtoolsHeight,
|
|
7644
7930
|
width: contentWidth,
|
|
7645
7931
|
height: devtoolsHeight
|
|
7646
7932
|
});
|
|
7647
|
-
} else {
|
|
7933
|
+
} else if (!devtoolsDetached) {
|
|
7648
7934
|
devtoolsPanelView.setBounds({ x: 0, y: height, width: 0, height: 0 });
|
|
7649
7935
|
}
|
|
7650
7936
|
mainWindow.contentView.removeChildView(chromeView);
|
|
@@ -7653,8 +7939,10 @@ function layoutViews(state2) {
|
|
|
7653
7939
|
mainWindow.contentView.removeChildView(sidebarView);
|
|
7654
7940
|
mainWindow.contentView.addChildView(sidebarView);
|
|
7655
7941
|
}
|
|
7656
|
-
|
|
7657
|
-
|
|
7942
|
+
if (!devtoolsDetached) {
|
|
7943
|
+
mainWindow.contentView.removeChildView(devtoolsPanelView);
|
|
7944
|
+
mainWindow.contentView.addChildView(devtoolsPanelView);
|
|
7945
|
+
}
|
|
7658
7946
|
const activeTab = tabManager.getActiveTab();
|
|
7659
7947
|
if (activeTab) {
|
|
7660
7948
|
activeTab.view.setBounds({
|
|
@@ -7670,7 +7958,8 @@ function resizeSidebarViews(state2) {
|
|
|
7670
7958
|
const [width, height] = mainWindow.getContentSize();
|
|
7671
7959
|
const chromeHeight = uiState.focusMode ? 0 : CHROME_HEIGHT;
|
|
7672
7960
|
const sidebarWidth = isSidebarAttached(state2) ? uiState.sidebarWidth : 0;
|
|
7673
|
-
const
|
|
7961
|
+
const devtoolsDocked = isDevToolsPanelDocked(state2);
|
|
7962
|
+
const devtoolsHeight = devtoolsDocked ? uiState.devtoolsPanelHeight : 0;
|
|
7674
7963
|
const contentWidth = width - sidebarWidth;
|
|
7675
7964
|
if (uiState.sidebarPanelMode !== "detached") {
|
|
7676
7965
|
sidebarView.setBounds({
|
|
@@ -7680,7 +7969,7 @@ function resizeSidebarViews(state2) {
|
|
|
7680
7969
|
height: height - chromeHeight
|
|
7681
7970
|
});
|
|
7682
7971
|
}
|
|
7683
|
-
if (
|
|
7972
|
+
if (devtoolsDocked) {
|
|
7684
7973
|
devtoolsPanelView.setBounds({
|
|
7685
7974
|
x: 0,
|
|
7686
7975
|
y: height - devtoolsHeight,
|
|
@@ -7806,6 +8095,96 @@ function isClickReadLoop(names) {
|
|
|
7806
8095
|
}
|
|
7807
8096
|
return clickReadPairs >= 2;
|
|
7808
8097
|
}
|
|
8098
|
+
const CLICK_READ_LOOP_SUPPRESS_THRESHOLD = 3;
|
|
8099
|
+
function classifyClickFailure(output) {
|
|
8100
|
+
if (/Error\[hidden\]/i.test(output)) return "hidden";
|
|
8101
|
+
if (/Error\[stale-index\]/i.test(output)) return "stale";
|
|
8102
|
+
if (/^\s*Error:/i.test(output)) return "other";
|
|
8103
|
+
return null;
|
|
8104
|
+
}
|
|
8105
|
+
function buildClickReadLoopIntervention(strikes, lastClickFailureKind) {
|
|
8106
|
+
if (strikes <= 0) return null;
|
|
8107
|
+
if (strikes >= CLICK_READ_LOOP_SUPPRESS_THRESHOLD) {
|
|
8108
|
+
const lines = [
|
|
8109
|
+
`Error: Suppressed repeated click — you have alternated click and read_page ${strikes} times without making progress and the clicks are not landing.`,
|
|
8110
|
+
`Stop calling click. Instead do one of: scroll (scroll or scroll_to_element) to load more of the page then read_page to refresh, inspect_element on a specific indexed result, or answer from the results already visible in the conversation.`
|
|
8111
|
+
];
|
|
8112
|
+
if (lastClickFailureKind === "hidden") {
|
|
8113
|
+
lines.push(
|
|
8114
|
+
`The last click target was hidden / not laid out — scrolling toward it first usually reveals it.`
|
|
8115
|
+
);
|
|
8116
|
+
} else if (lastClickFailureKind === "stale") {
|
|
8117
|
+
lines.push(
|
|
8118
|
+
`The last click target was stale — refresh page state with read_page and choose a currently listed target before clicking again.`
|
|
8119
|
+
);
|
|
8120
|
+
}
|
|
8121
|
+
return { kind: "suppress", message: lines.join("\n") };
|
|
8122
|
+
}
|
|
8123
|
+
if (strikes >= 2) {
|
|
8124
|
+
const lines = [
|
|
8125
|
+
`[System] You are alternating between click and read_page without advancing the task, and the last click did not complete.`,
|
|
8126
|
+
`The click result already includes a page snapshot, so do not read_page after every click.`
|
|
8127
|
+
];
|
|
8128
|
+
if (lastClickFailureKind === "hidden") {
|
|
8129
|
+
lines.push(
|
|
8130
|
+
`The click failed on a hidden element — call scroll (scroll or scroll_to_element) to reveal it, then read_page to refresh visible elements, before clicking again.`
|
|
8131
|
+
);
|
|
8132
|
+
} else if (lastClickFailureKind === "stale") {
|
|
8133
|
+
lines.push(
|
|
8134
|
+
`The click failed on a stale element index — call read_page to refresh current indexes before clicking again.`
|
|
8135
|
+
);
|
|
8136
|
+
} else {
|
|
8137
|
+
lines.push(
|
|
8138
|
+
`If you need detail on a specific element, use inspect_element. Otherwise continue the original task directly.`
|
|
8139
|
+
);
|
|
8140
|
+
}
|
|
8141
|
+
return { kind: "nudge", message: lines.join("\n") };
|
|
8142
|
+
}
|
|
8143
|
+
return {
|
|
8144
|
+
kind: "nudge",
|
|
8145
|
+
message: `[System] You are alternating between click and read_page without advancing the task. The click result already includes a page snapshot when it navigates, so do not read_page after every click. If you need detail on a specific element, use inspect_element. Otherwise continue the original task directly.`
|
|
8146
|
+
};
|
|
8147
|
+
}
|
|
8148
|
+
class ClickReadLoopGuard {
|
|
8149
|
+
recentToolNames = [];
|
|
8150
|
+
strikes = 0;
|
|
8151
|
+
lastClickFailureKind = null;
|
|
8152
|
+
beforeTool(toolName) {
|
|
8153
|
+
if (toolName === "click" && this.strikes >= CLICK_READ_LOOP_SUPPRESS_THRESHOLD && isClickReadLoop(this.recentToolNames)) {
|
|
8154
|
+
return buildClickReadLoopIntervention(
|
|
8155
|
+
this.strikes,
|
|
8156
|
+
this.lastClickFailureKind
|
|
8157
|
+
);
|
|
8158
|
+
}
|
|
8159
|
+
return null;
|
|
8160
|
+
}
|
|
8161
|
+
afterToolResult(toolName, output, succeeded) {
|
|
8162
|
+
if (toolName === "click") {
|
|
8163
|
+
this.lastClickFailureKind = succeeded ? null : classifyClickFailure(output);
|
|
8164
|
+
}
|
|
8165
|
+
this.recentToolNames.push(toolName);
|
|
8166
|
+
if (this.recentToolNames.length > 8) this.recentToolNames.shift();
|
|
8167
|
+
if (toolName === "click" && succeeded) {
|
|
8168
|
+
this.strikes = 0;
|
|
8169
|
+
return null;
|
|
8170
|
+
}
|
|
8171
|
+
if (toolName !== "click" && toolName !== "read_page") {
|
|
8172
|
+
this.strikes = 0;
|
|
8173
|
+
return null;
|
|
8174
|
+
}
|
|
8175
|
+
if (isClickReadLoop(this.recentToolNames) && this.lastClickFailureKind) {
|
|
8176
|
+
this.strikes += 1;
|
|
8177
|
+
if (this.strikes >= CLICK_READ_LOOP_SUPPRESS_THRESHOLD) {
|
|
8178
|
+
return null;
|
|
8179
|
+
}
|
|
8180
|
+
return buildClickReadLoopIntervention(
|
|
8181
|
+
this.strikes,
|
|
8182
|
+
this.lastClickFailureKind
|
|
8183
|
+
);
|
|
8184
|
+
}
|
|
8185
|
+
return null;
|
|
8186
|
+
}
|
|
8187
|
+
}
|
|
7809
8188
|
const TERMINAL_TOOL_RESULT = "__VESSEL_TERMINAL_TOOL_RESULT__";
|
|
7810
8189
|
const logger$w = createLogger("PromptCache");
|
|
7811
8190
|
function shortHash(value) {
|
|
@@ -7966,8 +8345,7 @@ class AnthropicProvider {
|
|
|
7966
8345
|
try {
|
|
7967
8346
|
const maxIterations = getEffectiveMaxIterations();
|
|
7968
8347
|
let iterationsUsed = 0;
|
|
7969
|
-
const
|
|
7970
|
-
let clickReadLoopNudged = false;
|
|
8348
|
+
const clickReadLoopGuard = new ClickReadLoopGuard();
|
|
7971
8349
|
for (let i = 0; i < maxIterations; i++) {
|
|
7972
8350
|
iterationsUsed = i + 1;
|
|
7973
8351
|
const stream = this.client.messages.stream(
|
|
@@ -8074,6 +8452,7 @@ class AnthropicProvider {
|
|
|
8074
8452
|
break;
|
|
8075
8453
|
}
|
|
8076
8454
|
const toolResults = [];
|
|
8455
|
+
const loopNudges = [];
|
|
8077
8456
|
for (const tb of toolUseBlocks) {
|
|
8078
8457
|
if (tb._malformedArgs !== void 0) {
|
|
8079
8458
|
onChunk(`
|
|
@@ -8087,6 +8466,19 @@ class AnthropicProvider {
|
|
|
8087
8466
|
});
|
|
8088
8467
|
continue;
|
|
8089
8468
|
}
|
|
8469
|
+
const clickLoopPreflight = clickReadLoopGuard.beforeTool(tb.name);
|
|
8470
|
+
if (clickLoopPreflight?.kind === "suppress") {
|
|
8471
|
+
onChunk(`
|
|
8472
|
+
<<tool:click:↻ loop suppressed>>
|
|
8473
|
+
`);
|
|
8474
|
+
toolResults.push({
|
|
8475
|
+
type: "tool_result",
|
|
8476
|
+
tool_use_id: tb.id,
|
|
8477
|
+
content: clickLoopPreflight.message,
|
|
8478
|
+
is_error: true
|
|
8479
|
+
});
|
|
8480
|
+
continue;
|
|
8481
|
+
}
|
|
8090
8482
|
const argSummary = [tb.input.url, tb.input.query, tb.input.text, tb.input.direction].map((v) => typeof v === "string" ? v : "").find((v) => v.length > 0) ?? "";
|
|
8091
8483
|
onChunk(`
|
|
8092
8484
|
<<tool:${tb.name}${argSummary ? ":" + argSummary : ""}>>
|
|
@@ -8101,6 +8493,7 @@ class AnthropicProvider {
|
|
|
8101
8493
|
if (result === TERMINAL_TOOL_RESULT) {
|
|
8102
8494
|
return;
|
|
8103
8495
|
}
|
|
8496
|
+
const toolSucceeded = !/^Error:/i.test(result.trim());
|
|
8104
8497
|
let parsedRich = null;
|
|
8105
8498
|
try {
|
|
8106
8499
|
const parsed = JSON.parse(result);
|
|
@@ -8132,16 +8525,18 @@ class AnthropicProvider {
|
|
|
8132
8525
|
content: result
|
|
8133
8526
|
});
|
|
8134
8527
|
}
|
|
8135
|
-
|
|
8136
|
-
|
|
8528
|
+
const clickLoopIntervention = clickReadLoopGuard.afterToolResult(
|
|
8529
|
+
tb.name,
|
|
8530
|
+
result,
|
|
8531
|
+
toolSucceeded
|
|
8532
|
+
);
|
|
8533
|
+
if (clickLoopIntervention?.kind === "nudge") {
|
|
8534
|
+
loopNudges.push(clickLoopIntervention.message);
|
|
8535
|
+
}
|
|
8137
8536
|
}
|
|
8138
8537
|
messages.push({ role: "user", content: toolResults });
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
messages.push({
|
|
8142
|
-
role: "user",
|
|
8143
|
-
content: `You are alternating between click and read_page without advancing the task. The click result already includes a page snapshot when it navigates — you do not need read_page after every click. If you need detail on a specific element, use inspect_element instead. If you have enough context, proceed with the next action directly.`
|
|
8144
|
-
});
|
|
8538
|
+
for (const nudge of loopNudges) {
|
|
8539
|
+
messages.push({ role: "user", content: nudge });
|
|
8145
8540
|
}
|
|
8146
8541
|
}
|
|
8147
8542
|
if (iterationsUsed >= maxIterations) {
|
|
@@ -9432,10 +9827,9 @@ class OpenAICompatProvider {
|
|
|
9432
9827
|
let highlightCompletionRecoveryCount = 0;
|
|
9433
9828
|
let compactCorrectionCount = 0;
|
|
9434
9829
|
const recentCompactToolSignatures = [];
|
|
9435
|
-
const recentToolNames = [];
|
|
9436
9830
|
const successfulToolNames = [];
|
|
9437
9831
|
const searchLoopGuard = new SearchLoopGuard(isSearchContextResettingTool);
|
|
9438
|
-
|
|
9832
|
+
const clickReadLoopGuard = new ClickReadLoopGuard();
|
|
9439
9833
|
for (let i = 0; i < maxIterations; i++) {
|
|
9440
9834
|
iterationsUsed = i + 1;
|
|
9441
9835
|
let textAccum = "";
|
|
@@ -9725,6 +10119,19 @@ class OpenAICompatProvider {
|
|
|
9725
10119
|
}
|
|
9726
10120
|
continue;
|
|
9727
10121
|
}
|
|
10122
|
+
const clickLoopPreflight = clickReadLoopGuard.beforeTool(tc.name);
|
|
10123
|
+
if (clickLoopPreflight?.kind === "suppress") {
|
|
10124
|
+
onChunk(`
|
|
10125
|
+
<<tool:click:↻ loop suppressed>>
|
|
10126
|
+
`);
|
|
10127
|
+
messages.push({
|
|
10128
|
+
role: "tool",
|
|
10129
|
+
tool_call_id: tc.id,
|
|
10130
|
+
content: clickLoopPreflight.message
|
|
10131
|
+
});
|
|
10132
|
+
compactCorrectionCount += 1;
|
|
10133
|
+
continue;
|
|
10134
|
+
}
|
|
9728
10135
|
const argSummary = [args.url, args.query, args.text, args.direction].map((v) => typeof v === "string" ? v : "").find((v) => v.length > 0) ?? "";
|
|
9729
10136
|
onChunk(`
|
|
9730
10137
|
<<tool:${tc.name}${argSummary ? ":" + argSummary : ""}>>
|
|
@@ -9762,15 +10169,11 @@ class OpenAICompatProvider {
|
|
|
9762
10169
|
searchToolQuery,
|
|
9763
10170
|
toolSucceeded
|
|
9764
10171
|
);
|
|
9765
|
-
|
|
9766
|
-
|
|
9767
|
-
|
|
9768
|
-
|
|
9769
|
-
|
|
9770
|
-
role: "user",
|
|
9771
|
-
content: `[System] You are alternating between click and read_page without advancing the task. The click result already includes a page snapshot when it navigates — you do not need read_page after every click. If you need detail on a specific element, use inspect_element instead. If you have enough context, proceed with the next action directly.`
|
|
9772
|
-
});
|
|
9773
|
-
}
|
|
10172
|
+
const clickLoopIntervention = clickReadLoopGuard.afterToolResult(
|
|
10173
|
+
tc.name,
|
|
10174
|
+
toolContent,
|
|
10175
|
+
toolSucceeded
|
|
10176
|
+
);
|
|
9774
10177
|
compactCorrectionCount = 0;
|
|
9775
10178
|
iterationToolResultPreviews.push(toolContent);
|
|
9776
10179
|
messages.push({
|
|
@@ -9778,6 +10181,9 @@ class OpenAICompatProvider {
|
|
|
9778
10181
|
tool_call_id: tc.id,
|
|
9779
10182
|
content: toolContent
|
|
9780
10183
|
});
|
|
10184
|
+
if (clickLoopIntervention?.kind === "nudge") {
|
|
10185
|
+
messages.push({ role: "user", content: clickLoopIntervention.message });
|
|
10186
|
+
}
|
|
9781
10187
|
}
|
|
9782
10188
|
const followUpReminder = followUpReminderForProfile(
|
|
9783
10189
|
this.agentToolProfile,
|
|
@@ -10187,9 +10593,18 @@ function summarizeToolArg(args) {
|
|
|
10187
10593
|
function normalizeCodexText(text) {
|
|
10188
10594
|
return text.trim().toLowerCase().replace(/[‘’]/g, "'").replace(/[“”]/g, '"');
|
|
10189
10595
|
}
|
|
10190
|
-
function looksLikeFailedToolOutput(output) {
|
|
10596
|
+
function looksLikeFailedToolOutput(output, toolName) {
|
|
10191
10597
|
const normalized = normalizeCodexText(output);
|
|
10192
|
-
|
|
10598
|
+
const firstLine = normalized.split(/\n+/).map((line) => line.trim()).find(Boolean) ?? "";
|
|
10599
|
+
if (/^(?:error\b|error\[|blocked\b|warning\b|target\b|no active tab\b|cannot\b|can't\b)/.test(
|
|
10600
|
+
firstLine
|
|
10601
|
+
)) {
|
|
10602
|
+
return true;
|
|
10603
|
+
}
|
|
10604
|
+
if (toolName === "click" && (normalized.includes("page did not change after click") || normalized.includes("element may need a different interaction method"))) {
|
|
10605
|
+
return true;
|
|
10606
|
+
}
|
|
10607
|
+
return toolName === "type_text" && /^(?:could not|did not|no focused|no visible)/.test(firstLine);
|
|
10193
10608
|
}
|
|
10194
10609
|
function looksLikeTravelFareContext(text) {
|
|
10195
10610
|
const normalized = normalizeCodexText(text);
|
|
@@ -10197,7 +10612,7 @@ function looksLikeTravelFareContext(text) {
|
|
|
10197
10612
|
}
|
|
10198
10613
|
function emitCodexToolChunk(onChunk, name, args, output) {
|
|
10199
10614
|
const summary = summarizeToolArg(args);
|
|
10200
|
-
const argSummary = looksLikeFailedToolOutput(output) ? ["⚠ failed", summary].filter(Boolean).join(" ") : summary;
|
|
10615
|
+
const argSummary = looksLikeFailedToolOutput(output, name) ? ["⚠ failed", summary].filter(Boolean).join(" ") : summary;
|
|
10201
10616
|
onChunk(`
|
|
10202
10617
|
<<tool:${name}${argSummary ? ":" + argSummary : ""}>>
|
|
10203
10618
|
`);
|
|
@@ -10449,12 +10864,25 @@ function buildCodexFlightPriceEvidenceRecoveryInput(userMessage, assistantText,
|
|
|
10449
10864
|
]
|
|
10450
10865
|
};
|
|
10451
10866
|
}
|
|
10452
|
-
function buildCodexFailedClickRecoveryInput(attemptedTarget, latestToolResultPreview, failedClickCount = 1) {
|
|
10867
|
+
function buildCodexFailedClickRecoveryInput(attemptedTarget, latestToolResultPreview, failedClickCount = 1, errorOutput = "") {
|
|
10453
10868
|
const stateReminder = buildLatestStateReminder(latestToolResultPreview);
|
|
10869
|
+
const clickFailureKind = classifyClickFailure(errorOutput);
|
|
10454
10870
|
const lines = [
|
|
10455
|
-
`[System] The previous click did not complete${attemptedTarget ? ` for ${attemptedTarget}` : ""}
|
|
10456
|
-
`Take the next step yourself: try a different target, refresh the page state with read_page, call inspect_element on the intended element, or answer from the results already visible in the conversation. Do not ask the user to inspect or click the result for you.`
|
|
10871
|
+
`[System] The previous click did not complete${attemptedTarget ? ` for ${attemptedTarget}` : ""}.`
|
|
10457
10872
|
];
|
|
10873
|
+
if (clickFailureKind === "hidden") {
|
|
10874
|
+
lines.push(
|
|
10875
|
+
`The click target was hidden / not laid out (collapsed, lazy-loaded, or virtual-scroll). Call scroll or scroll_to_element toward it to reveal it, then read_page to refresh visible elements, before clicking again — or inspect_element on the intended index, or answer from the results already visible. Do not ask the user to inspect or click the result for you.`
|
|
10876
|
+
);
|
|
10877
|
+
} else if (clickFailureKind === "stale") {
|
|
10878
|
+
lines.push(
|
|
10879
|
+
`The click target was stale — the page changed since the last snapshot. Call read_page to refresh current indexes, then choose a currently listed target, inspect_element on the intended element, or answer from the results already visible. Do not ask the user to inspect or click the result for you.`
|
|
10880
|
+
);
|
|
10881
|
+
} else {
|
|
10882
|
+
lines.push(
|
|
10883
|
+
`Take the next step yourself: try a different target, refresh the page state with read_page, call inspect_element on the intended element, or answer from the results already visible in the conversation. Do not ask the user to inspect or click the result for you.`
|
|
10884
|
+
);
|
|
10885
|
+
}
|
|
10458
10886
|
if (failedClickCount >= 2) {
|
|
10459
10887
|
lines.push(
|
|
10460
10888
|
`You have already had multiple failed clicks without making page progress. Do not keep clicking similar search result titles. Use the latest read_page/search result text to answer, or inspect a specific indexed result/control only if essential.`
|
|
@@ -10665,8 +11093,7 @@ class CodexProvider {
|
|
|
10665
11093
|
let flightPriceEvidenceRecoveryCount = 0;
|
|
10666
11094
|
let correctionCount = 0;
|
|
10667
11095
|
const recentToolSignatures = [];
|
|
10668
|
-
const
|
|
10669
|
-
let clickReadLoopNudged = false;
|
|
11096
|
+
const clickReadLoopGuard = new ClickReadLoopGuard();
|
|
10670
11097
|
let latestToolResultPreview = null;
|
|
10671
11098
|
let failedClickCountSinceProgress = 0;
|
|
10672
11099
|
const searchLoopGuard = new SearchLoopGuard(isRealProgressTool);
|
|
@@ -10822,6 +11249,22 @@ ${latestToolResultPreview || ""}`
|
|
|
10822
11249
|
correctionCount += 1;
|
|
10823
11250
|
continue;
|
|
10824
11251
|
}
|
|
11252
|
+
const clickLoopPreflight = clickReadLoopGuard.beforeTool(
|
|
11253
|
+
prepared.prepared.name
|
|
11254
|
+
);
|
|
11255
|
+
if (clickLoopPreflight?.kind === "suppress") {
|
|
11256
|
+
onChunk(`
|
|
11257
|
+
<<tool:click:↻ loop suppressed>>
|
|
11258
|
+
`);
|
|
11259
|
+
const suppressed = createCodexToolOutput(
|
|
11260
|
+
prepared.prepared.callId,
|
|
11261
|
+
clickLoopPreflight.message
|
|
11262
|
+
);
|
|
11263
|
+
currentInput.push(suppressed);
|
|
11264
|
+
latestToolResultPreview = previewToolResult(suppressed.output);
|
|
11265
|
+
correctionCount += 1;
|
|
11266
|
+
continue;
|
|
11267
|
+
}
|
|
10825
11268
|
const output = await executePreparedCodexFunctionCall(
|
|
10826
11269
|
prepared.prepared,
|
|
10827
11270
|
onChunk,
|
|
@@ -10834,7 +11277,10 @@ ${latestToolResultPreview || ""}`
|
|
|
10834
11277
|
toolHistoryCount += 1;
|
|
10835
11278
|
latestToolResultPreview = previewToolResult(output.output);
|
|
10836
11279
|
const outputText = toolResultTextContent(output.output);
|
|
10837
|
-
const toolSucceeded = !looksLikeFailedToolOutput(
|
|
11280
|
+
const toolSucceeded = !looksLikeFailedToolOutput(
|
|
11281
|
+
outputText,
|
|
11282
|
+
prepared.prepared.name
|
|
11283
|
+
);
|
|
10838
11284
|
if (toolSucceeded && isRealProgressTool(prepared.prepared.name)) {
|
|
10839
11285
|
failedClickCountSinceProgress = 0;
|
|
10840
11286
|
}
|
|
@@ -10843,13 +11289,14 @@ ${latestToolResultPreview || ""}`
|
|
|
10843
11289
|
searchToolQuery,
|
|
10844
11290
|
toolSucceeded
|
|
10845
11291
|
);
|
|
10846
|
-
if (prepared.prepared.name === "click" && looksLikeFailedToolOutput(outputText)) {
|
|
11292
|
+
if (prepared.prepared.name === "click" && looksLikeFailedToolOutput(outputText, prepared.prepared.name)) {
|
|
10847
11293
|
failedClickCountSinceProgress += 1;
|
|
10848
11294
|
currentInput.push(
|
|
10849
11295
|
buildCodexFailedClickRecoveryInput(
|
|
10850
11296
|
summarizeToolArg(prepared.prepared.args),
|
|
10851
11297
|
latestToolResultPreview,
|
|
10852
|
-
failedClickCountSinceProgress
|
|
11298
|
+
failedClickCountSinceProgress,
|
|
11299
|
+
outputText
|
|
10853
11300
|
)
|
|
10854
11301
|
);
|
|
10855
11302
|
}
|
|
@@ -10857,17 +11304,16 @@ ${latestToolResultPreview || ""}`
|
|
|
10857
11304
|
if (recentToolSignatures.length > 4) {
|
|
10858
11305
|
recentToolSignatures.shift();
|
|
10859
11306
|
}
|
|
10860
|
-
|
|
10861
|
-
|
|
10862
|
-
|
|
10863
|
-
|
|
11307
|
+
const clickLoopIntervention = clickReadLoopGuard.afterToolResult(
|
|
11308
|
+
prepared.prepared.name,
|
|
11309
|
+
outputText,
|
|
11310
|
+
toolSucceeded
|
|
11311
|
+
);
|
|
11312
|
+
if (clickLoopIntervention?.kind === "nudge") {
|
|
10864
11313
|
currentInput.push({
|
|
10865
11314
|
type: "message",
|
|
10866
11315
|
role: "user",
|
|
10867
|
-
content: [{
|
|
10868
|
-
type: "input_text",
|
|
10869
|
-
text: `[System] You are alternating between click and read_page without advancing the task. The click result already includes a page snapshot when it navigates, so do not read_page after every click. If you need detail on a specific element, use inspect_element. Otherwise continue the original task directly.`
|
|
10870
|
-
}]
|
|
11316
|
+
content: [{ type: "input_text", text: clickLoopIntervention.message }]
|
|
10871
11317
|
});
|
|
10872
11318
|
}
|
|
10873
11319
|
correctionCount = 0;
|
|
@@ -12091,8 +12537,7 @@ function pageBusyError(action) {
|
|
|
12091
12537
|
return `Error: Page is still busy; ${action} timed out waiting for page scripts. Retry in a moment.`;
|
|
12092
12538
|
}
|
|
12093
12539
|
async function loadPermittedUrl(wc, url) {
|
|
12094
|
-
|
|
12095
|
-
assertPermittedNavigationURL2(url);
|
|
12540
|
+
assertPermittedNavigationURL(url);
|
|
12096
12541
|
await wc.loadURL(url);
|
|
12097
12542
|
}
|
|
12098
12543
|
async function executePageScript(wc, script, options) {
|
|
@@ -16432,6 +16877,60 @@ async function clickElement(wc, selector) {
|
|
|
16432
16877
|
}));
|
|
16433
16878
|
}
|
|
16434
16879
|
|
|
16880
|
+
// Sum offsetTop up the offsetParent chain until reaching "container",
|
|
16881
|
+
// giving the element's vertical position within that scroll container.
|
|
16882
|
+
function offsetTopWithin(el, container) {
|
|
16883
|
+
let top = 0;
|
|
16884
|
+
let node = el;
|
|
16885
|
+
while (node && node !== container) {
|
|
16886
|
+
top += node.offsetTop || 0;
|
|
16887
|
+
node = node.offsetParent;
|
|
16888
|
+
}
|
|
16889
|
+
return top;
|
|
16890
|
+
}
|
|
16891
|
+
|
|
16892
|
+
function nearestScrollableAncestor(el) {
|
|
16893
|
+
let node = el.parentElement;
|
|
16894
|
+
while (node) {
|
|
16895
|
+
if (node instanceof HTMLElement) {
|
|
16896
|
+
const style = window.getComputedStyle(node);
|
|
16897
|
+
if (
|
|
16898
|
+
(style.overflowY === "auto" || style.overflowY === "scroll") &&
|
|
16899
|
+
node.scrollHeight > node.clientHeight
|
|
16900
|
+
) {
|
|
16901
|
+
return node;
|
|
16902
|
+
}
|
|
16903
|
+
}
|
|
16904
|
+
node = node.parentElement;
|
|
16905
|
+
}
|
|
16906
|
+
return null;
|
|
16907
|
+
}
|
|
16908
|
+
|
|
16909
|
+
// Wait for the element to gain a non-zero layout box, polling for up to
|
|
16910
|
+
// maxFrames animation frames. Lazy / virtual-scroll renderers
|
|
16911
|
+
// (content-visibility, intersection-triggered list items) often lay out
|
|
16912
|
+
// a frame or two after the scroller moves. Falls back to setTimeout when
|
|
16913
|
+
// the window is hidden (requestAnimationFrame does not fire then).
|
|
16914
|
+
function waitForBox(el, maxFrames) {
|
|
16915
|
+
return new Promise((resolve) => {
|
|
16916
|
+
let frames = 0;
|
|
16917
|
+
const rafAvailable =
|
|
16918
|
+
typeof requestAnimationFrame === "function" &&
|
|
16919
|
+
document.visibilityState === "visible";
|
|
16920
|
+
const schedule = rafAvailable
|
|
16921
|
+
? (cb) => requestAnimationFrame(cb)
|
|
16922
|
+
: (cb) => setTimeout(cb, 16);
|
|
16923
|
+
const check = () => {
|
|
16924
|
+
const r = el.getBoundingClientRect();
|
|
16925
|
+
if (r.width > 0 && r.height > 0) return resolve(true);
|
|
16926
|
+
if (frames >= maxFrames) return resolve(false);
|
|
16927
|
+
frames += 1;
|
|
16928
|
+
schedule(check);
|
|
16929
|
+
};
|
|
16930
|
+
check();
|
|
16931
|
+
});
|
|
16932
|
+
}
|
|
16933
|
+
|
|
16435
16934
|
const el = document.querySelector(${JSON.stringify(selector)});
|
|
16436
16935
|
if (!el) return { error: "Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh." };
|
|
16437
16936
|
|
|
@@ -16439,21 +16938,26 @@ async function clickElement(wc, selector) {
|
|
|
16439
16938
|
el.scrollIntoView({ behavior: "instant", block: "center", inline: "center" });
|
|
16440
16939
|
}
|
|
16441
16940
|
|
|
16442
|
-
|
|
16443
|
-
|
|
16444
|
-
|
|
16445
|
-
|
|
16446
|
-
|
|
16447
|
-
|
|
16448
|
-
|
|
16449
|
-
|
|
16450
|
-
|
|
16451
|
-
|
|
16452
|
-
)
|
|
16453
|
-
|
|
16941
|
+
// Give the renderer a brief grace to lay the element out after the
|
|
16942
|
+
// initial scroll. Already-visible elements resolve on the first check.
|
|
16943
|
+
let revealed = await waitForBox(el, 4);
|
|
16944
|
+
|
|
16945
|
+
// scrollIntoView is a no-op on zero-rect elements (collapsed, lazy, or
|
|
16946
|
+
// virtual-scroll content). Force the nearest scrollable ancestor to bring
|
|
16947
|
+
// the element's offset position into view, then wait longer for the
|
|
16948
|
+
// renderer to produce a layout box. This recovers many hidden targets
|
|
16949
|
+
// without the model having to scroll manually.
|
|
16950
|
+
if (!revealed) {
|
|
16951
|
+
const scroller = nearestScrollableAncestor(el);
|
|
16952
|
+
if (scroller) {
|
|
16953
|
+
const targetTop = offsetTopWithin(el, scroller) - scroller.clientHeight / 2;
|
|
16954
|
+
scroller.scrollTop = Math.max(0, targetTop);
|
|
16454
16955
|
}
|
|
16455
|
-
|
|
16456
|
-
|
|
16956
|
+
if (el instanceof HTMLElement) {
|
|
16957
|
+
el.scrollIntoView({ behavior: "instant", block: "center", inline: "center" });
|
|
16958
|
+
}
|
|
16959
|
+
revealed = await waitForBox(el, 24);
|
|
16960
|
+
}
|
|
16457
16961
|
|
|
16458
16962
|
const rect = el.getBoundingClientRect();
|
|
16459
16963
|
if (rect.width <= 0 || rect.height <= 0) {
|
|
@@ -18016,9 +18520,13 @@ Go back and select a different product.`;
|
|
|
18016
18520
|
const clickText = `Clicked: ${elInfo.text}${tagLabel}`;
|
|
18017
18521
|
const clickResult = await clickElement(wc, selector);
|
|
18018
18522
|
if (clickResult.startsWith("Error:")) return clickResult;
|
|
18019
|
-
|
|
18523
|
+
const initialNavigationWaitMs = /DOM activation/i.test(clickResult) && !elInfo.href ? 800 : void 0;
|
|
18524
|
+
await waitForPotentialNavigation$1(wc, beforeUrl, initialNavigationWaitMs);
|
|
18020
18525
|
const afterUrl = wc.getURL();
|
|
18021
18526
|
if (afterUrl !== beforeUrl) {
|
|
18527
|
+
if (/DOM activation/i.test(clickResult)) {
|
|
18528
|
+
return `${clickText} -> ${afterUrl} (recovered via DOM activation)`;
|
|
18529
|
+
}
|
|
18022
18530
|
return `${clickText} -> ${afterUrl}`;
|
|
18023
18531
|
}
|
|
18024
18532
|
const overlayHint = await detectPostClickOverlay(wc);
|
|
@@ -18045,6 +18553,9 @@ ${overlayHint}`;
|
|
|
18045
18553
|
}
|
|
18046
18554
|
return `${clickText} (${clickResult})${await buildCartSuccessSuffix(wc, beforeUrl)}`;
|
|
18047
18555
|
}
|
|
18556
|
+
if (/DOM activation/i.test(clickResult) && (!elInfo.href || elInfo.target === "_blank")) {
|
|
18557
|
+
return `${clickText} (${clickResult})`;
|
|
18558
|
+
}
|
|
18048
18559
|
const activationResult = await activateElement(wc, selector);
|
|
18049
18560
|
if (!activationResult.startsWith("Error:")) {
|
|
18050
18561
|
await waitForPotentialNavigation$1(wc, beforeUrl);
|
|
@@ -22777,7 +23288,7 @@ function registerPrivateIpcHandlers(state2) {
|
|
|
22777
23288
|
});
|
|
22778
23289
|
ipc.handle(Channels.FOCUS_MODE_TOGGLE, () => false);
|
|
22779
23290
|
ipc.handle(Channels.SIDEBAR_TOGGLE, () => ({ open: false, width: 0 }));
|
|
22780
|
-
ipc
|
|
23291
|
+
registerDisabledDevToolsPanelHandlers(ipc);
|
|
22781
23292
|
ipc.handle(
|
|
22782
23293
|
Channels.FIND_IN_PAGE_START,
|
|
22783
23294
|
(_e, text, options) => {
|
|
@@ -23041,7 +23552,7 @@ function registerSecondaryIpcHandlers(state2) {
|
|
|
23041
23552
|
ipc.handle(Channels.SETTINGS_VISIBILITY, () => false);
|
|
23042
23553
|
ipc.handle(Channels.FOCUS_MODE_TOGGLE, () => false);
|
|
23043
23554
|
ipc.handle(Channels.SIDEBAR_TOGGLE, () => ({ open: false, width: 0 }));
|
|
23044
|
-
ipc
|
|
23555
|
+
registerDisabledDevToolsPanelHandlers(ipc);
|
|
23045
23556
|
ipc.handle(
|
|
23046
23557
|
Channels.FIND_IN_PAGE_START,
|
|
23047
23558
|
(_e, text, options) => {
|
|
@@ -23847,6 +24358,9 @@ All open tabs: ${allTabs.map((t) => `${t.id === activeTabId ? "→ " : ""}${t.ti
|
|
|
23847
24358
|
let isError = false;
|
|
23848
24359
|
try {
|
|
23849
24360
|
output = await executeAction(name, args, actionCtx);
|
|
24361
|
+
if (/^\s*Error:/i.test(output)) {
|
|
24362
|
+
isError = true;
|
|
24363
|
+
}
|
|
23850
24364
|
if (provider.agentToolProfile === "compact") {
|
|
23851
24365
|
runtime2.updateTaskTracker(name, output);
|
|
23852
24366
|
const trackerCtx = runtime2.getTaskTrackerContext();
|
|
@@ -24571,8 +25085,14 @@ const DANGEROUS_DEVTOOLS_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
24571
25085
|
]);
|
|
24572
25086
|
let stateListener = null;
|
|
24573
25087
|
const activityLog = [];
|
|
25088
|
+
const agentTraceLog = [];
|
|
24574
25089
|
const MAX_ACTIVITY_ENTRIES = 100;
|
|
25090
|
+
const MAX_TRACE_ENTRIES = 200;
|
|
24575
25091
|
let activityCounter = 0;
|
|
25092
|
+
let traceCounter = 0;
|
|
25093
|
+
let latestPageMap = null;
|
|
25094
|
+
let pageMapRefreshInFlight = false;
|
|
25095
|
+
const traceEntriesByActionId = /* @__PURE__ */ new Map();
|
|
24576
25096
|
function setDevToolsPanelListener(listener) {
|
|
24577
25097
|
stateListener = listener;
|
|
24578
25098
|
}
|
|
@@ -24582,14 +25102,325 @@ function getDevToolsPanelState(tabId) {
|
|
|
24582
25102
|
console: session?.getConsoleLogs() ?? [],
|
|
24583
25103
|
network: session?.getNetworkLog() ?? [],
|
|
24584
25104
|
errors: session?.getErrors() ?? [],
|
|
24585
|
-
activity: activityLog
|
|
25105
|
+
activity: activityLog,
|
|
25106
|
+
agentTrace: agentTraceLog,
|
|
25107
|
+
pageMap: latestPageMap
|
|
24586
25108
|
};
|
|
24587
25109
|
}
|
|
24588
|
-
function
|
|
25110
|
+
function broadcastDevToolsPanelState(tabManager) {
|
|
24589
25111
|
if (!stateListener) return;
|
|
24590
25112
|
const tabId = tabManager.getActiveTabId();
|
|
24591
25113
|
stateListener(getDevToolsPanelState(tabId));
|
|
24592
25114
|
}
|
|
25115
|
+
let panelBroadcastScheduled = false;
|
|
25116
|
+
let panelBroadcastTabManager = null;
|
|
25117
|
+
let panelCapturedTabId = null;
|
|
25118
|
+
const panelOwnedCaptureTabs = /* @__PURE__ */ new Set();
|
|
25119
|
+
function schedulePanelBroadcast(tabManager) {
|
|
25120
|
+
panelBroadcastTabManager = tabManager;
|
|
25121
|
+
if (panelBroadcastScheduled) return;
|
|
25122
|
+
panelBroadcastScheduled = true;
|
|
25123
|
+
queueMicrotask(() => {
|
|
25124
|
+
panelBroadcastScheduled = false;
|
|
25125
|
+
if (panelBroadcastTabManager) {
|
|
25126
|
+
broadcastDevToolsPanelState(panelBroadcastTabManager);
|
|
25127
|
+
}
|
|
25128
|
+
});
|
|
25129
|
+
}
|
|
25130
|
+
function releasePanelCaptureForTab(tabId) {
|
|
25131
|
+
const session = getSession(tabId);
|
|
25132
|
+
session?.setOnCaptureChange(null);
|
|
25133
|
+
if (panelCapturedTabId === tabId) {
|
|
25134
|
+
panelCapturedTabId = null;
|
|
25135
|
+
}
|
|
25136
|
+
if (panelOwnedCaptureTabs.delete(tabId)) {
|
|
25137
|
+
destroySession(tabId);
|
|
25138
|
+
}
|
|
25139
|
+
}
|
|
25140
|
+
function markActiveSessionToolOwned(tabManager) {
|
|
25141
|
+
const tabId = tabManager.getActiveTabId();
|
|
25142
|
+
if (tabId) {
|
|
25143
|
+
panelOwnedCaptureTabs.delete(tabId);
|
|
25144
|
+
}
|
|
25145
|
+
}
|
|
25146
|
+
async function enableCaptureForTab(tabManager) {
|
|
25147
|
+
const tabId = tabManager.getActiveTabId();
|
|
25148
|
+
if (!tabId) return;
|
|
25149
|
+
if (panelCapturedTabId && panelCapturedTabId !== tabId) {
|
|
25150
|
+
releasePanelCaptureForTab(panelCapturedTabId);
|
|
25151
|
+
}
|
|
25152
|
+
let session = getSession(tabId);
|
|
25153
|
+
const panelCreatedSession = !session;
|
|
25154
|
+
session ??= getOrCreateSession(tabManager);
|
|
25155
|
+
if (panelCreatedSession) {
|
|
25156
|
+
panelOwnedCaptureTabs.add(tabId);
|
|
25157
|
+
}
|
|
25158
|
+
panelCapturedTabId = tabId;
|
|
25159
|
+
session.setOnCaptureChange(() => {
|
|
25160
|
+
if (panelCapturedTabId === tabId) {
|
|
25161
|
+
schedulePanelBroadcast(tabManager);
|
|
25162
|
+
}
|
|
25163
|
+
});
|
|
25164
|
+
await session.enableCapture();
|
|
25165
|
+
if (panelCapturedTabId === tabId) {
|
|
25166
|
+
broadcastDevToolsPanelState(tabManager);
|
|
25167
|
+
}
|
|
25168
|
+
}
|
|
25169
|
+
function disableCaptureForTab(tabId) {
|
|
25170
|
+
const targetTabId = panelCapturedTabId;
|
|
25171
|
+
if (targetTabId) {
|
|
25172
|
+
releasePanelCaptureForTab(targetTabId);
|
|
25173
|
+
}
|
|
25174
|
+
{
|
|
25175
|
+
for (const ownedTabId of [...panelOwnedCaptureTabs]) {
|
|
25176
|
+
releasePanelCaptureForTab(ownedTabId);
|
|
25177
|
+
}
|
|
25178
|
+
panelBroadcastTabManager = null;
|
|
25179
|
+
}
|
|
25180
|
+
}
|
|
25181
|
+
function pushTrace(entry) {
|
|
25182
|
+
const traceEntry = {
|
|
25183
|
+
id: ++traceCounter,
|
|
25184
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25185
|
+
...entry
|
|
25186
|
+
};
|
|
25187
|
+
agentTraceLog.push(traceEntry);
|
|
25188
|
+
if (agentTraceLog.length > MAX_TRACE_ENTRIES) {
|
|
25189
|
+
const removed = agentTraceLog.splice(
|
|
25190
|
+
0,
|
|
25191
|
+
agentTraceLog.length - MAX_TRACE_ENTRIES
|
|
25192
|
+
);
|
|
25193
|
+
for (const trace of removed) {
|
|
25194
|
+
if (trace.actionId) traceEntriesByActionId.delete(trace.actionId);
|
|
25195
|
+
}
|
|
25196
|
+
}
|
|
25197
|
+
return traceEntry;
|
|
25198
|
+
}
|
|
25199
|
+
function formatTraceActionName(name) {
|
|
25200
|
+
return name.replace(/^devtools_/, "").split(/[_-]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
25201
|
+
}
|
|
25202
|
+
function traceDetail(event) {
|
|
25203
|
+
const sourcePrefix = event.source === "ai" ? "Agent" : event.source.toUpperCase();
|
|
25204
|
+
return [sourcePrefix, event.detail].filter(Boolean).join(": ").slice(0, 240);
|
|
25205
|
+
}
|
|
25206
|
+
function recordDevToolsAgentAction(event, tabManager) {
|
|
25207
|
+
const actionName = formatTraceActionName(event.name);
|
|
25208
|
+
if (event.phase === "started") {
|
|
25209
|
+
const entry = pushTrace({
|
|
25210
|
+
actionId: event.actionId,
|
|
25211
|
+
kind: "tool-start",
|
|
25212
|
+
title: `Started ${actionName}`,
|
|
25213
|
+
detail: traceDetail(event),
|
|
25214
|
+
status: "running",
|
|
25215
|
+
tool: event.name
|
|
25216
|
+
});
|
|
25217
|
+
traceEntriesByActionId.set(event.actionId, entry);
|
|
25218
|
+
broadcastDevToolsPanelState(tabManager);
|
|
25219
|
+
return;
|
|
25220
|
+
}
|
|
25221
|
+
const startEntry = traceEntriesByActionId.get(event.actionId);
|
|
25222
|
+
if (startEntry) {
|
|
25223
|
+
startEntry.status = event.phase === "completed" ? "completed" : "failed";
|
|
25224
|
+
startEntry.durationMs = event.durationMs;
|
|
25225
|
+
if (event.phase === "waiting-approval") {
|
|
25226
|
+
startEntry.status = "running";
|
|
25227
|
+
startEntry.detail = traceDetail(event);
|
|
25228
|
+
}
|
|
25229
|
+
}
|
|
25230
|
+
if (event.phase === "waiting-approval") {
|
|
25231
|
+
broadcastDevToolsPanelState(tabManager);
|
|
25232
|
+
return;
|
|
25233
|
+
}
|
|
25234
|
+
pushTrace({
|
|
25235
|
+
actionId: event.actionId,
|
|
25236
|
+
kind: event.phase === "completed" ? "tool-complete" : "tool-error",
|
|
25237
|
+
title: event.phase === "completed" ? `Completed ${actionName}` : `${event.phase === "rejected" ? "Rejected" : "Failed"} ${actionName}`,
|
|
25238
|
+
detail: traceDetail(event),
|
|
25239
|
+
status: event.phase === "completed" ? "completed" : "failed",
|
|
25240
|
+
tool: event.name,
|
|
25241
|
+
durationMs: event.durationMs
|
|
25242
|
+
});
|
|
25243
|
+
traceEntriesByActionId.delete(event.actionId);
|
|
25244
|
+
broadcastDevToolsPanelState(tabManager);
|
|
25245
|
+
}
|
|
25246
|
+
const PAGE_MAP_SCRIPT = `
|
|
25247
|
+
(() => {
|
|
25248
|
+
const INTERACTIVE_SELECTOR = [
|
|
25249
|
+
"a[href]",
|
|
25250
|
+
"button",
|
|
25251
|
+
"input",
|
|
25252
|
+
"select",
|
|
25253
|
+
"textarea",
|
|
25254
|
+
"summary",
|
|
25255
|
+
"[role='button']",
|
|
25256
|
+
"[role='link']",
|
|
25257
|
+
"[role='checkbox']",
|
|
25258
|
+
"[role='radio']",
|
|
25259
|
+
"[role='switch']",
|
|
25260
|
+
"[role='tab']",
|
|
25261
|
+
"[role='menuitem']",
|
|
25262
|
+
"[contenteditable='true']",
|
|
25263
|
+
"[tabindex]:not([tabindex='-1'])"
|
|
25264
|
+
].join(",");
|
|
25265
|
+
|
|
25266
|
+
function round(value) {
|
|
25267
|
+
return Math.round(value * 10) / 10;
|
|
25268
|
+
}
|
|
25269
|
+
|
|
25270
|
+
function textFor(el) {
|
|
25271
|
+
const direct =
|
|
25272
|
+
el.getAttribute("aria-label") ||
|
|
25273
|
+
el.getAttribute("title") ||
|
|
25274
|
+
el.getAttribute("placeholder") ||
|
|
25275
|
+
el.getAttribute("alt") ||
|
|
25276
|
+
el.value ||
|
|
25277
|
+
el.innerText ||
|
|
25278
|
+
el.textContent ||
|
|
25279
|
+
"";
|
|
25280
|
+
return String(direct).replace(/\\s+/g, " ").trim().slice(0, 140);
|
|
25281
|
+
}
|
|
25282
|
+
|
|
25283
|
+
function selectorFor(el) {
|
|
25284
|
+
if (el.id) return "#" + CSS.escape(el.id);
|
|
25285
|
+
const parts = [];
|
|
25286
|
+
let node = el;
|
|
25287
|
+
while (node && node.nodeType === Node.ELEMENT_NODE && parts.length < 4) {
|
|
25288
|
+
let part = node.localName;
|
|
25289
|
+
const classes = Array.from(node.classList || []).slice(0, 2);
|
|
25290
|
+
if (classes.length) part += "." + classes.map((c) => CSS.escape(c)).join(".");
|
|
25291
|
+
const parent = node.parentElement;
|
|
25292
|
+
if (parent) {
|
|
25293
|
+
const siblings = Array.from(parent.children).filter((child) => child.localName === node.localName);
|
|
25294
|
+
if (siblings.length > 1) part += ":nth-of-type(" + (siblings.indexOf(node) + 1) + ")";
|
|
25295
|
+
}
|
|
25296
|
+
parts.unshift(part);
|
|
25297
|
+
node = parent;
|
|
25298
|
+
}
|
|
25299
|
+
return parts.join(" > ");
|
|
25300
|
+
}
|
|
25301
|
+
|
|
25302
|
+
function isVisible(el, style, rect) {
|
|
25303
|
+
return rect.width > 0 &&
|
|
25304
|
+
rect.height > 0 &&
|
|
25305
|
+
style.display !== "none" &&
|
|
25306
|
+
style.visibility !== "hidden" &&
|
|
25307
|
+
Number(style.opacity || "1") > 0.01;
|
|
25308
|
+
}
|
|
25309
|
+
|
|
25310
|
+
const viewport = {
|
|
25311
|
+
width: window.innerWidth,
|
|
25312
|
+
height: window.innerHeight,
|
|
25313
|
+
scrollX: window.scrollX,
|
|
25314
|
+
scrollY: window.scrollY,
|
|
25315
|
+
};
|
|
25316
|
+
|
|
25317
|
+
const candidates = Array.from(document.querySelectorAll(INTERACTIVE_SELECTOR));
|
|
25318
|
+
const elements = candidates.slice(0, 120).map((el, index) => {
|
|
25319
|
+
const style = window.getComputedStyle(el);
|
|
25320
|
+
const rect = el.getBoundingClientRect();
|
|
25321
|
+
const visible = isVisible(el, style, rect);
|
|
25322
|
+
const disabled = Boolean(el.disabled) || el.getAttribute("aria-disabled") === "true";
|
|
25323
|
+
const inViewport = rect.bottom >= 0 && rect.right >= 0 && rect.top <= viewport.height && rect.left <= viewport.width;
|
|
25324
|
+
let issue = "";
|
|
25325
|
+
let blocked = false;
|
|
25326
|
+
if (!visible) issue = "hidden";
|
|
25327
|
+
else if (!inViewport) issue = "offscreen";
|
|
25328
|
+
else {
|
|
25329
|
+
const centerX = Math.min(Math.max(rect.left + rect.width / 2, 0), viewport.width - 1);
|
|
25330
|
+
const centerY = Math.min(Math.max(rect.top + rect.height / 2, 0), viewport.height - 1);
|
|
25331
|
+
const top = document.elementFromPoint(centerX, centerY);
|
|
25332
|
+
if (top && top !== el && !el.contains(top)) {
|
|
25333
|
+
blocked = true;
|
|
25334
|
+
issue = "covered by " + top.localName;
|
|
25335
|
+
}
|
|
25336
|
+
}
|
|
25337
|
+
return {
|
|
25338
|
+
id: index + 1,
|
|
25339
|
+
tag: el.localName,
|
|
25340
|
+
role: el.getAttribute("role") || undefined,
|
|
25341
|
+
label: textFor(el) || "(unlabeled)",
|
|
25342
|
+
selector: selectorFor(el),
|
|
25343
|
+
href: el.href || undefined,
|
|
25344
|
+
type: el.type || undefined,
|
|
25345
|
+
visible,
|
|
25346
|
+
interactable: visible && inViewport && !disabled && !blocked,
|
|
25347
|
+
disabled,
|
|
25348
|
+
issue: issue || undefined,
|
|
25349
|
+
bounds: {
|
|
25350
|
+
x: round(rect.x),
|
|
25351
|
+
y: round(rect.y),
|
|
25352
|
+
width: round(rect.width),
|
|
25353
|
+
height: round(rect.height),
|
|
25354
|
+
},
|
|
25355
|
+
};
|
|
25356
|
+
});
|
|
25357
|
+
|
|
25358
|
+
const counts = elements.reduce((acc, element) => {
|
|
25359
|
+
acc.total += 1;
|
|
25360
|
+
if (element.visible) acc.visible += 1;
|
|
25361
|
+
if (element.interactable) acc.interactable += 1;
|
|
25362
|
+
if (element.disabled) acc.disabled += 1;
|
|
25363
|
+
if (element.issue && element.issue.startsWith("covered")) acc.blocked += 1;
|
|
25364
|
+
return acc;
|
|
25365
|
+
}, { total: 0, visible: 0, interactable: 0, disabled: 0, blocked: 0 });
|
|
25366
|
+
|
|
25367
|
+
return {
|
|
25368
|
+
timestamp: new Date().toISOString(),
|
|
25369
|
+
pageUrl: location.href,
|
|
25370
|
+
title: document.title || "",
|
|
25371
|
+
viewport,
|
|
25372
|
+
counts,
|
|
25373
|
+
elements,
|
|
25374
|
+
accessIssues: elements.length === 0 ? ["No obvious interactive elements found."] : [],
|
|
25375
|
+
};
|
|
25376
|
+
})()
|
|
25377
|
+
`;
|
|
25378
|
+
async function capturePageMapSnapshot(tabManager) {
|
|
25379
|
+
const tab = tabManager.getActiveTab();
|
|
25380
|
+
if (!tab || tab.view.webContents.isDestroyed()) {
|
|
25381
|
+
return {
|
|
25382
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25383
|
+
pageUrl: "",
|
|
25384
|
+
title: "No active tab",
|
|
25385
|
+
viewport: { width: 0, height: 0, scrollX: 0, scrollY: 0 },
|
|
25386
|
+
counts: { total: 0, visible: 0, interactable: 0, disabled: 0, blocked: 0 },
|
|
25387
|
+
elements: [],
|
|
25388
|
+
accessIssues: ["No active tab is available."]
|
|
25389
|
+
};
|
|
25390
|
+
}
|
|
25391
|
+
try {
|
|
25392
|
+
return await tab.view.webContents.executeJavaScript(PAGE_MAP_SCRIPT, true);
|
|
25393
|
+
} catch (error) {
|
|
25394
|
+
return {
|
|
25395
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25396
|
+
pageUrl: tab.view.webContents.getURL(),
|
|
25397
|
+
title: "Page map unavailable",
|
|
25398
|
+
viewport: { width: 0, height: 0, scrollX: 0, scrollY: 0 },
|
|
25399
|
+
counts: { total: 0, visible: 0, interactable: 0, disabled: 0, blocked: 0 },
|
|
25400
|
+
elements: [],
|
|
25401
|
+
accessIssues: [
|
|
25402
|
+
error instanceof Error ? error.message : "Could not inspect active page."
|
|
25403
|
+
]
|
|
25404
|
+
};
|
|
25405
|
+
}
|
|
25406
|
+
}
|
|
25407
|
+
async function refreshDevToolsPageMap(tabManager) {
|
|
25408
|
+
if (pageMapRefreshInFlight) {
|
|
25409
|
+
return getDevToolsPanelState(tabManager.getActiveTabId());
|
|
25410
|
+
}
|
|
25411
|
+
pageMapRefreshInFlight = true;
|
|
25412
|
+
try {
|
|
25413
|
+
latestPageMap = await capturePageMapSnapshot(tabManager);
|
|
25414
|
+
} finally {
|
|
25415
|
+
pageMapRefreshInFlight = false;
|
|
25416
|
+
}
|
|
25417
|
+
broadcastDevToolsPanelState(tabManager);
|
|
25418
|
+
return getDevToolsPanelState(tabManager.getActiveTabId());
|
|
25419
|
+
}
|
|
25420
|
+
function broadcastState(tabManager) {
|
|
25421
|
+
broadcastDevToolsPanelState(tabManager);
|
|
25422
|
+
void refreshDevToolsPageMap(tabManager);
|
|
25423
|
+
}
|
|
24593
25424
|
async function withDevToolsAction(runtime2, tabManager, name, args, executor) {
|
|
24594
25425
|
try {
|
|
24595
25426
|
assertFeatureUnlocked("devtools", "DevTools");
|
|
@@ -24598,6 +25429,7 @@ async function withDevToolsAction(runtime2, tabManager, name, args, executor) {
|
|
|
24598
25429
|
`Error: ${error instanceof Error ? error.message : "DevTools require Vessel Premium."}`
|
|
24599
25430
|
);
|
|
24600
25431
|
}
|
|
25432
|
+
markActiveSessionToolOwned(tabManager);
|
|
24601
25433
|
const activityEntry = {
|
|
24602
25434
|
id: ++activityCounter,
|
|
24603
25435
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -30216,6 +31048,76 @@ async function togglePictureInPicture(tabManager) {
|
|
|
30216
31048
|
return false;
|
|
30217
31049
|
}
|
|
30218
31050
|
}
|
|
31051
|
+
const REVEAL_SCRIPT = `
|
|
31052
|
+
(function () {
|
|
31053
|
+
var selector = __SELECTOR_JSON__;
|
|
31054
|
+
try {
|
|
31055
|
+
var el = document.querySelector(selector);
|
|
31056
|
+
} catch (e) {
|
|
31057
|
+
return "invalid-selector";
|
|
31058
|
+
}
|
|
31059
|
+
if (!el) return "not-found";
|
|
31060
|
+
try {
|
|
31061
|
+
el.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
31062
|
+
} catch (e) {}
|
|
31063
|
+
var rect = el.getBoundingClientRect();
|
|
31064
|
+
var overlay = document.createElement("div");
|
|
31065
|
+
overlay.setAttribute("data-vessel-devtools-reveal", "");
|
|
31066
|
+
overlay.style.cssText = [
|
|
31067
|
+
"position: fixed",
|
|
31068
|
+
"left: " + rect.left + "px",
|
|
31069
|
+
"top: " + rect.top + "px",
|
|
31070
|
+
"width: " + rect.width + "px",
|
|
31071
|
+
"height: " + rect.height + "px",
|
|
31072
|
+
"box-sizing: border-box",
|
|
31073
|
+
"border: 2px solid #4f8cff",
|
|
31074
|
+
"background: rgba(79, 140, 255, 0.18)",
|
|
31075
|
+
"border-radius: 4px",
|
|
31076
|
+
"pointer-events: none",
|
|
31077
|
+
"z-index: 2147483647",
|
|
31078
|
+
"transition: opacity 250ms ease-out",
|
|
31079
|
+
"opacity: 1",
|
|
31080
|
+
].join("; ");
|
|
31081
|
+
document.documentElement.appendChild(overlay);
|
|
31082
|
+
var start = performance.now();
|
|
31083
|
+
function track(now) {
|
|
31084
|
+
var r = el.getBoundingClientRect();
|
|
31085
|
+
overlay.style.left = r.left + "px";
|
|
31086
|
+
overlay.style.top = r.top + "px";
|
|
31087
|
+
overlay.style.width = r.width + "px";
|
|
31088
|
+
overlay.style.height = r.height + "px";
|
|
31089
|
+
if (now - start < 1000) {
|
|
31090
|
+
requestAnimationFrame(track);
|
|
31091
|
+
} else {
|
|
31092
|
+
overlay.style.opacity = "0";
|
|
31093
|
+
setTimeout(function () {
|
|
31094
|
+
if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
|
|
31095
|
+
}, 300);
|
|
31096
|
+
}
|
|
31097
|
+
}
|
|
31098
|
+
requestAnimationFrame(track);
|
|
31099
|
+
return "revealed";
|
|
31100
|
+
})();
|
|
31101
|
+
`;
|
|
31102
|
+
async function revealPageMapElement(tabManager, selector) {
|
|
31103
|
+
const tab = tabManager.getActiveTab();
|
|
31104
|
+
if (!tab || tab.view.webContents.isDestroyed()) {
|
|
31105
|
+
return "no-active-tab";
|
|
31106
|
+
}
|
|
31107
|
+
try {
|
|
31108
|
+
const script = REVEAL_SCRIPT.replace(
|
|
31109
|
+
"__SELECTOR_JSON__",
|
|
31110
|
+
JSON.stringify(selector)
|
|
31111
|
+
);
|
|
31112
|
+
const result = await tab.view.webContents.executeJavaScript(script, true);
|
|
31113
|
+
if (result === "revealed" || result === "not-found" || result === "invalid-selector") {
|
|
31114
|
+
return result;
|
|
31115
|
+
}
|
|
31116
|
+
return "revealed";
|
|
31117
|
+
} catch {
|
|
31118
|
+
return "invalid-selector";
|
|
31119
|
+
}
|
|
31120
|
+
}
|
|
30219
31121
|
const VALID_KIT_CATEGORIES = /* @__PURE__ */ new Set([
|
|
30220
31122
|
"research",
|
|
30221
31123
|
"shopping",
|
|
@@ -30736,21 +31638,177 @@ function stopScheduler() {
|
|
|
30736
31638
|
const KitIdSchema = zod.z.string().min(1);
|
|
30737
31639
|
const SkillSourceSchema = zod.z.string().min(1).max(1e5);
|
|
30738
31640
|
const OriginSchema = zod.z.string().min(1);
|
|
31641
|
+
const DevToolsHeightSchema = zod.z.number().finite().min(0).max(2e3);
|
|
31642
|
+
const DevToolsPageMapRevealSchema = zod.z.object({
|
|
31643
|
+
selector: zod.z.string().min(1)
|
|
31644
|
+
});
|
|
31645
|
+
const DevToolsPanelTabSchema = zod.z.enum([
|
|
31646
|
+
"console",
|
|
31647
|
+
"network",
|
|
31648
|
+
"activity",
|
|
31649
|
+
"agentTrace",
|
|
31650
|
+
"pageMap"
|
|
31651
|
+
]);
|
|
31652
|
+
const RendererViewSchema = zod.z.enum(["chrome", "sidebar", "devtools"]);
|
|
30739
31653
|
function registerSystemHandlers(windowState, sendToRendererViews) {
|
|
30740
31654
|
const { tabManager } = windowState;
|
|
31655
|
+
let devToolsResizeRecoveryTimer = null;
|
|
31656
|
+
let devToolsResizeActive = false;
|
|
31657
|
+
const relayout = () => layoutViews(windowState);
|
|
31658
|
+
const clearDevToolsResizeRecoveryTimer = () => {
|
|
31659
|
+
if (!devToolsResizeRecoveryTimer) return;
|
|
31660
|
+
clearTimeout(devToolsResizeRecoveryTimer);
|
|
31661
|
+
devToolsResizeRecoveryTimer = null;
|
|
31662
|
+
};
|
|
31663
|
+
const stopDevToolsResize = () => {
|
|
31664
|
+
devToolsResizeActive = false;
|
|
31665
|
+
clearDevToolsResizeRecoveryTimer();
|
|
31666
|
+
};
|
|
31667
|
+
const restoreDevToolsLayoutAfterResize = () => {
|
|
31668
|
+
clearDevToolsResizeRecoveryTimer();
|
|
31669
|
+
if (!devToolsResizeActive) return;
|
|
31670
|
+
devToolsResizeActive = false;
|
|
31671
|
+
relayout();
|
|
31672
|
+
};
|
|
31673
|
+
const scheduleDevToolsResizeRecovery = () => {
|
|
31674
|
+
clearDevToolsResizeRecoveryTimer();
|
|
31675
|
+
devToolsResizeRecoveryTimer = setTimeout(() => {
|
|
31676
|
+
restoreDevToolsLayoutAfterResize();
|
|
31677
|
+
}, 1200);
|
|
31678
|
+
};
|
|
31679
|
+
const maxDockedDevToolsHeight = () => {
|
|
31680
|
+
const [, windowHeight] = windowState.mainWindow.getContentSize();
|
|
31681
|
+
const chromeHeight = windowState.uiState.focusMode ? 0 : CHROME_HEIGHT;
|
|
31682
|
+
return Math.max(
|
|
31683
|
+
MIN_DEVTOOLS_PANEL,
|
|
31684
|
+
Math.min(MAX_DEVTOOLS_PANEL, windowHeight - chromeHeight - 80)
|
|
31685
|
+
);
|
|
31686
|
+
};
|
|
31687
|
+
windowState.mainWindow.once("closed", stopDevToolsResize);
|
|
30741
31688
|
electron.ipcMain.handle(Channels.DEVTOOLS_PANEL_TOGGLE, (event) => {
|
|
30742
31689
|
assertTrustedIpcSender(event);
|
|
30743
|
-
|
|
30744
|
-
|
|
30745
|
-
|
|
31690
|
+
stopDevToolsResize();
|
|
31691
|
+
const hostState = toggleDockedDevToolsPanel(windowState, { relayout });
|
|
31692
|
+
if (hostState.open) {
|
|
31693
|
+
void enableCaptureForTab(tabManager);
|
|
31694
|
+
} else {
|
|
31695
|
+
disableCaptureForTab();
|
|
31696
|
+
}
|
|
31697
|
+
return hostState;
|
|
31698
|
+
});
|
|
31699
|
+
electron.ipcMain.handle(Channels.DEVTOOLS_PANEL_CLOSE, (event) => {
|
|
31700
|
+
assertTrustedIpcSender(event);
|
|
31701
|
+
stopDevToolsResize();
|
|
31702
|
+
const hostState = closeDevToolsPanel(windowState, { relayout });
|
|
31703
|
+
disableCaptureForTab();
|
|
31704
|
+
return hostState;
|
|
31705
|
+
});
|
|
31706
|
+
electron.ipcMain.handle(Channels.DEVTOOLS_PANEL_OPEN_TAB, (event, tab) => {
|
|
31707
|
+
assertTrustedIpcSender(event);
|
|
31708
|
+
const selectedTab = parseIpc(DevToolsPanelTabSchema, tab, "tab");
|
|
31709
|
+
stopDevToolsResize();
|
|
31710
|
+
const wasOpen = getDevToolsPanelHostState(windowState).open;
|
|
31711
|
+
if (!wasOpen) {
|
|
31712
|
+
toggleDockedDevToolsPanel(windowState, { relayout });
|
|
31713
|
+
void enableCaptureForTab(tabManager);
|
|
31714
|
+
} else if (getDevToolsPanelHostState(windowState).detached) {
|
|
31715
|
+
windowState.devtoolsPanelWindow?.focus();
|
|
31716
|
+
}
|
|
31717
|
+
emitDevToolsPanelHostState(windowState);
|
|
31718
|
+
sendSafe(
|
|
31719
|
+
windowState.devtoolsPanelView.webContents,
|
|
31720
|
+
Channels.DEVTOOLS_PANEL_SELECT_TAB,
|
|
31721
|
+
selectedTab
|
|
31722
|
+
);
|
|
31723
|
+
if (selectedTab === "pageMap") {
|
|
31724
|
+
void refreshDevToolsPageMap(tabManager);
|
|
31725
|
+
}
|
|
31726
|
+
return getDevToolsPanelHostState(windowState);
|
|
31727
|
+
});
|
|
31728
|
+
electron.ipcMain.handle(Channels.DEVTOOLS_PANEL_RESIZE_START, (event) => {
|
|
31729
|
+
assertTrustedIpcSender(event);
|
|
31730
|
+
if (!getDevToolsPanelHostState(windowState).open) return;
|
|
31731
|
+
if (getDevToolsPanelHostState(windowState).detached) return;
|
|
31732
|
+
devToolsResizeActive = true;
|
|
31733
|
+
clearDevToolsResizeRecoveryTimer();
|
|
31734
|
+
const [windowWidth, windowHeight] = windowState.mainWindow.getContentSize();
|
|
31735
|
+
const chromeHeight = windowState.uiState.focusMode ? 0 : CHROME_HEIGHT;
|
|
31736
|
+
const sidebarWidth = isSidebarAttached(windowState) ? windowState.uiState.sidebarWidth : 0;
|
|
31737
|
+
windowState.devtoolsPanelView.setBounds({
|
|
31738
|
+
x: 0,
|
|
31739
|
+
y: chromeHeight,
|
|
31740
|
+
width: windowWidth - sidebarWidth,
|
|
31741
|
+
height: windowHeight - chromeHeight
|
|
31742
|
+
});
|
|
31743
|
+
scheduleDevToolsResizeRecovery();
|
|
30746
31744
|
});
|
|
30747
31745
|
electron.ipcMain.handle(Channels.DEVTOOLS_PANEL_RESIZE, (event, height) => {
|
|
30748
31746
|
assertTrustedIpcSender(event);
|
|
30749
|
-
const
|
|
30750
|
-
|
|
30751
|
-
|
|
31747
|
+
const validatedHeight = parseIpc(DevToolsHeightSchema, height, "height");
|
|
31748
|
+
const clamped = Math.max(
|
|
31749
|
+
MIN_DEVTOOLS_PANEL,
|
|
31750
|
+
Math.min(maxDockedDevToolsHeight(), Math.round(validatedHeight))
|
|
31751
|
+
);
|
|
31752
|
+
if (devToolsResizeActive) {
|
|
31753
|
+
windowState.uiState.devtoolsPanelHeight = clamped;
|
|
31754
|
+
scheduleDevToolsResizeRecovery();
|
|
31755
|
+
emitDevToolsPanelHostState(windowState);
|
|
31756
|
+
return clamped;
|
|
31757
|
+
}
|
|
31758
|
+
resizeDockedDevToolsPanel(windowState, clamped, relayout);
|
|
30752
31759
|
return clamped;
|
|
30753
31760
|
});
|
|
31761
|
+
electron.ipcMain.handle(Channels.DEVTOOLS_PANEL_RESIZE_COMMIT, (event) => {
|
|
31762
|
+
assertTrustedIpcSender(event);
|
|
31763
|
+
stopDevToolsResize();
|
|
31764
|
+
relayout();
|
|
31765
|
+
});
|
|
31766
|
+
electron.ipcMain.handle(Channels.DEVTOOLS_PANEL_POPOUT, (event) => {
|
|
31767
|
+
assertTrustedIpcSender(event);
|
|
31768
|
+
stopDevToolsResize();
|
|
31769
|
+
return detachDevToolsPanel(windowState, {
|
|
31770
|
+
relayout,
|
|
31771
|
+
getWindowIconPath
|
|
31772
|
+
});
|
|
31773
|
+
});
|
|
31774
|
+
electron.ipcMain.handle(Channels.DEVTOOLS_PANEL_DOCK, (event) => {
|
|
31775
|
+
assertTrustedIpcSender(event);
|
|
31776
|
+
stopDevToolsResize();
|
|
31777
|
+
return dockDevToolsPanel(windowState, { relayout });
|
|
31778
|
+
});
|
|
31779
|
+
electron.ipcMain.handle(Channels.DEVTOOLS_PANEL_STATE_GET, async (event) => {
|
|
31780
|
+
assertTrustedIpcSender(event);
|
|
31781
|
+
if (getDevToolsPanelHostState(windowState).open) {
|
|
31782
|
+
void enableCaptureForTab(tabManager);
|
|
31783
|
+
}
|
|
31784
|
+
return await refreshDevToolsPageMap(tabManager);
|
|
31785
|
+
});
|
|
31786
|
+
electron.ipcMain.handle(
|
|
31787
|
+
Channels.DEVTOOLS_PAGE_MAP_REVEAL,
|
|
31788
|
+
async (event, payload) => {
|
|
31789
|
+
assertTrustedIpcSender(event);
|
|
31790
|
+
const { selector } = parseIpc(
|
|
31791
|
+
DevToolsPageMapRevealSchema,
|
|
31792
|
+
payload,
|
|
31793
|
+
"payload"
|
|
31794
|
+
);
|
|
31795
|
+
return await revealPageMapElement(tabManager, selector);
|
|
31796
|
+
}
|
|
31797
|
+
);
|
|
31798
|
+
electron.ipcMain.handle(Channels.DEVTOOLS_PANEL_HOST_STATE_GET, (event) => {
|
|
31799
|
+
assertTrustedIpcSender(event);
|
|
31800
|
+
return getDevToolsPanelHostState(windowState);
|
|
31801
|
+
});
|
|
31802
|
+
electron.ipcMain.on(Channels.RENDERER_VIEW_READY, (event, view) => {
|
|
31803
|
+
assertTrustedIpcSender(event);
|
|
31804
|
+
const readyView = parseIpc(RendererViewSchema, view, "view");
|
|
31805
|
+
if (readyView !== "devtools") return;
|
|
31806
|
+
emitDevToolsPanelHostState(windowState);
|
|
31807
|
+
void refreshDevToolsPageMap(tabManager);
|
|
31808
|
+
if (getDevToolsPanelHostState(windowState).open) {
|
|
31809
|
+
void enableCaptureForTab(tabManager);
|
|
31810
|
+
}
|
|
31811
|
+
});
|
|
30754
31812
|
electron.ipcMain.handle(Channels.AUTOMATION_GET_INSTALLED, async (event) => {
|
|
30755
31813
|
assertTrustedIpcSender(event);
|
|
30756
31814
|
assertFeatureUnlocked("automation_kits", "Skills");
|
|
@@ -33140,6 +34198,7 @@ class AgentRuntime {
|
|
|
33140
34198
|
tabManager;
|
|
33141
34199
|
state;
|
|
33142
34200
|
updateListener = null;
|
|
34201
|
+
actionLifecycleListener = null;
|
|
33143
34202
|
pendingResolvers = /* @__PURE__ */ new Map();
|
|
33144
34203
|
undoSnapshots = [];
|
|
33145
34204
|
mcpUnsubscribe = null;
|
|
@@ -33149,6 +34208,9 @@ class AgentRuntime {
|
|
|
33149
34208
|
listener(this.getState());
|
|
33150
34209
|
}
|
|
33151
34210
|
}
|
|
34211
|
+
setActionLifecycleListener(listener) {
|
|
34212
|
+
this.actionLifecycleListener = listener;
|
|
34213
|
+
}
|
|
33152
34214
|
/**
|
|
33153
34215
|
* Release all resources, listeners, and pending promises.
|
|
33154
34216
|
* Call when the window is closing to prevent memory leaks.
|
|
@@ -33167,6 +34229,7 @@ class AgentRuntime {
|
|
|
33167
34229
|
this.state.flowState = null;
|
|
33168
34230
|
this.state.taskTracker = null;
|
|
33169
34231
|
this.updateListener = null;
|
|
34232
|
+
this.actionLifecycleListener = null;
|
|
33170
34233
|
}
|
|
33171
34234
|
getState() {
|
|
33172
34235
|
const snapshot2 = clone(this.state);
|
|
@@ -33471,8 +34534,18 @@ ${progress}
|
|
|
33471
34534
|
args,
|
|
33472
34535
|
tabId
|
|
33473
34536
|
});
|
|
34537
|
+
const actionStartedAt = Date.now();
|
|
33474
34538
|
const transcriptStreamId = `action:${action.id}`;
|
|
33475
34539
|
const transcriptTitle = humanizeActionName(name);
|
|
34540
|
+
this.emitActionLifecycle({
|
|
34541
|
+
actionId: action.id,
|
|
34542
|
+
source,
|
|
34543
|
+
name,
|
|
34544
|
+
args,
|
|
34545
|
+
tabId,
|
|
34546
|
+
phase: "started",
|
|
34547
|
+
detail: summarizeArgs(args)
|
|
34548
|
+
});
|
|
33476
34549
|
this.publishTranscript({
|
|
33477
34550
|
source,
|
|
33478
34551
|
kind: "status",
|
|
@@ -33483,6 +34556,16 @@ ${progress}
|
|
|
33483
34556
|
});
|
|
33484
34557
|
const approvalReason = this.getApprovalReason(dangerous, requiresApproval);
|
|
33485
34558
|
if (approvalReason) {
|
|
34559
|
+
this.emitActionLifecycle({
|
|
34560
|
+
actionId: action.id,
|
|
34561
|
+
source,
|
|
34562
|
+
name,
|
|
34563
|
+
args,
|
|
34564
|
+
tabId,
|
|
34565
|
+
phase: "waiting-approval",
|
|
34566
|
+
detail: approvalReason,
|
|
34567
|
+
durationMs: Date.now() - actionStartedAt
|
|
34568
|
+
});
|
|
33486
34569
|
this.publishTranscript({
|
|
33487
34570
|
source,
|
|
33488
34571
|
kind: "status",
|
|
@@ -33493,6 +34576,16 @@ ${progress}
|
|
|
33493
34576
|
});
|
|
33494
34577
|
const approved = await this.awaitApproval(action, approvalReason);
|
|
33495
34578
|
if (!approved) {
|
|
34579
|
+
this.emitActionLifecycle({
|
|
34580
|
+
actionId: action.id,
|
|
34581
|
+
source,
|
|
34582
|
+
name,
|
|
34583
|
+
args,
|
|
34584
|
+
tabId,
|
|
34585
|
+
phase: "rejected",
|
|
34586
|
+
detail: approvalReason,
|
|
34587
|
+
durationMs: Date.now() - actionStartedAt
|
|
34588
|
+
});
|
|
33496
34589
|
this.publishTranscript({
|
|
33497
34590
|
source,
|
|
33498
34591
|
kind: "status",
|
|
@@ -33524,6 +34617,16 @@ ${progress}
|
|
|
33524
34617
|
this.pushUndoSnapshot(undoSnapshot);
|
|
33525
34618
|
}
|
|
33526
34619
|
this.finishAction(action.id, "completed", summarizeText(result));
|
|
34620
|
+
this.emitActionLifecycle({
|
|
34621
|
+
actionId: action.id,
|
|
34622
|
+
source,
|
|
34623
|
+
name,
|
|
34624
|
+
args,
|
|
34625
|
+
tabId,
|
|
34626
|
+
phase: "completed",
|
|
34627
|
+
detail: summarizeText(result),
|
|
34628
|
+
durationMs: Date.now() - actionStartedAt
|
|
34629
|
+
});
|
|
33527
34630
|
this.publishTranscript({
|
|
33528
34631
|
source,
|
|
33529
34632
|
kind: "status",
|
|
@@ -33538,6 +34641,16 @@ ${progress}
|
|
|
33538
34641
|
const message = error instanceof Error ? error.message : "Unknown action failure";
|
|
33539
34642
|
this.state.supervisor.lastError = message;
|
|
33540
34643
|
this.finishAction(action.id, "failed", void 0, message);
|
|
34644
|
+
this.emitActionLifecycle({
|
|
34645
|
+
actionId: action.id,
|
|
34646
|
+
source,
|
|
34647
|
+
name,
|
|
34648
|
+
args,
|
|
34649
|
+
tabId,
|
|
34650
|
+
phase: "failed",
|
|
34651
|
+
detail: summarizeText(message),
|
|
34652
|
+
durationMs: Date.now() - actionStartedAt
|
|
34653
|
+
});
|
|
33541
34654
|
this.publishTranscript({
|
|
33542
34655
|
source,
|
|
33543
34656
|
kind: "status",
|
|
@@ -33549,6 +34662,15 @@ ${progress}
|
|
|
33549
34662
|
throw error;
|
|
33550
34663
|
}
|
|
33551
34664
|
}
|
|
34665
|
+
emitActionLifecycle(event) {
|
|
34666
|
+
try {
|
|
34667
|
+
this.actionLifecycleListener?.(event);
|
|
34668
|
+
} catch (error) {
|
|
34669
|
+
logger$3.warn("Action lifecycle listener failed", {
|
|
34670
|
+
error: error instanceof Error ? error.message : String(error)
|
|
34671
|
+
});
|
|
34672
|
+
}
|
|
34673
|
+
}
|
|
33552
34674
|
createUndoSnapshot(name) {
|
|
33553
34675
|
return {
|
|
33554
34676
|
id: crypto$1.randomUUID(),
|
|
@@ -34122,6 +35244,10 @@ async function bootstrap() {
|
|
|
34122
35244
|
if (meta.persistSession) {
|
|
34123
35245
|
runtime?.onTabStateChanged();
|
|
34124
35246
|
}
|
|
35247
|
+
if (windowState.uiState.devtoolsPanelMode !== "closed") {
|
|
35248
|
+
void enableCaptureForTab(windowState.tabManager);
|
|
35249
|
+
void refreshDevToolsPageMap(windowState.tabManager);
|
|
35250
|
+
}
|
|
34125
35251
|
});
|
|
34126
35252
|
windowStateForShutdown = windowState;
|
|
34127
35253
|
let didRevealMainWindow = false;
|
|
@@ -34138,6 +35264,9 @@ async function bootstrap() {
|
|
|
34138
35264
|
}, 8e3);
|
|
34139
35265
|
const { chromeView, sidebarView, devtoolsPanelView, tabManager } = windowState;
|
|
34140
35266
|
runtime = new AgentRuntime(tabManager);
|
|
35267
|
+
runtime.setActionLifecycleListener((event) => {
|
|
35268
|
+
recordDevToolsAgentAction(event, tabManager);
|
|
35269
|
+
});
|
|
34141
35270
|
installAdBlocking(tabManager);
|
|
34142
35271
|
setDevToolsPanelListener((state2) => {
|
|
34143
35272
|
sendSafe(devtoolsPanelView.webContents, Channels.DEVTOOLS_PANEL_STATE, state2);
|