@quanta-intellect/vessel-browser 0.1.146 → 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") {
@@ -2589,6 +2623,9 @@ class DevToolsSession {
2589
2623
  entryCounter = 0;
2590
2624
  // Track in-flight network requests for matching response data
2591
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;
2592
2629
  // Named handlers so we can remove them on detach/destroy (fixes listener leak)
2593
2630
  onDetach = () => {
2594
2631
  this.attached = false;
@@ -2604,6 +2641,27 @@ class DevToolsSession {
2604
2641
  get isAttached() {
2605
2642
  return this.attached;
2606
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
+ }
2607
2665
  async ensureAttached() {
2608
2666
  if (this.attached) return;
2609
2667
  if (this.attachingPromise) return this.attachingPromise;
@@ -2654,6 +2712,7 @@ class DevToolsSession {
2654
2712
  }
2655
2713
  destroy() {
2656
2714
  this.detach();
2715
+ this.onCaptureChange = null;
2657
2716
  this.consoleBuffer = [];
2658
2717
  this.networkBuffer = [];
2659
2718
  this.errorBuffer = [];
@@ -3111,6 +3170,7 @@ class DevToolsSession {
3111
3170
  if (this.consoleBuffer.length > MAX_CONSOLE_ENTRIES) {
3112
3171
  this.consoleBuffer = this.consoleBuffer.slice(-MAX_CONSOLE_ENTRIES);
3113
3172
  }
3173
+ this.notifyCaptureChange();
3114
3174
  }
3115
3175
  // --- Network events ---
3116
3176
  onNetworkRequest(params) {
@@ -3139,6 +3199,7 @@ class DevToolsSession {
3139
3199
  if (oldest !== void 0) this.pendingRequests.delete(oldest);
3140
3200
  }
3141
3201
  this.pendingRequests.set(requestId, { entry });
3202
+ this.notifyCaptureChange();
3142
3203
  }
3143
3204
  onNetworkResponse(params) {
3144
3205
  const requestId = params.requestId;
@@ -3154,6 +3215,7 @@ class DevToolsSession {
3154
3215
  if (contentLength) {
3155
3216
  pending.entry.contentLength = parseInt(contentLength, 10) || void 0;
3156
3217
  }
3218
+ this.notifyCaptureChange();
3157
3219
  }
3158
3220
  onNetworkFinished(params) {
3159
3221
  const requestId = params.requestId;
@@ -3168,6 +3230,7 @@ class DevToolsSession {
3168
3230
  }
3169
3231
  pending.entry.contentLength = pending.entry.contentLength ?? (params.encodedDataLength || void 0);
3170
3232
  this.pendingRequests.delete(requestId);
3233
+ this.notifyCaptureChange();
3171
3234
  }
3172
3235
  onNetworkFailed(params) {
3173
3236
  const requestId = params.requestId;
@@ -3182,6 +3245,7 @@ class DevToolsSession {
3182
3245
  );
3183
3246
  }
3184
3247
  this.pendingRequests.delete(requestId);
3248
+ this.notifyCaptureChange();
3185
3249
  }
3186
3250
  // --- Error events ---
3187
3251
  onExceptionThrown(params) {
@@ -3208,6 +3272,7 @@ class DevToolsSession {
3208
3272
  if (this.errorBuffer.length > MAX_ERROR_ENTRIES) {
3209
3273
  this.errorBuffer = this.errorBuffer.slice(-MAX_ERROR_ENTRIES);
3210
3274
  }
3275
+ this.notifyCaptureChange();
3211
3276
  }
3212
3277
  }
3213
3278
  function mapConsoleLevel(level) {
@@ -3931,8 +3996,19 @@ const ContentChannels = {
3931
3996
  };
3932
3997
  const DevToolsChannels = {
3933
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",
3934
4003
  DEVTOOLS_PANEL_STATE: "devtools-panel:state",
3935
- DEVTOOLS_PANEL_RESIZE: "devtools-panel:resize"
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"
3936
4012
  };
3937
4013
  const DownloadChannels = {
3938
4014
  DOWNLOAD_STARTED: "download:started",
@@ -7206,6 +7282,236 @@ function sendSafe(wc, channel, ...args) {
7206
7282
  }
7207
7283
  }
7208
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
+ };
7209
7515
  function setSidebarPanelMode(state2, mode, reason = "user") {
7210
7516
  state2.uiState.sidebarPanelMode = mode;
7211
7517
  if (reason === "user") {
@@ -7225,19 +7531,10 @@ function persistDetachedBounds(state2) {
7225
7531
  setSetting("sidebarDetachedBounds", state2.uiState.sidebarDetachedBounds);
7226
7532
  }
7227
7533
  function closeDetachedSidebarWindow(state2) {
7228
- const sidebarWindow = state2.sidebarWindow;
7229
- if (!sidebarWindow) return false;
7230
- state2.sidebarWindow = null;
7231
- state2.sidebarWindowClosing = true;
7232
- sidebarWindow.once("closed", () => {
7233
- state2.sidebarWindowClosing = false;
7234
- });
7235
- sidebarWindow.close();
7236
- return true;
7534
+ return closeDetachedViewWindow(state2, sidebarDetachedHost);
7237
7535
  }
7238
7536
  function moveSidebarToMainWindow(state2) {
7239
- state2.sidebarWindow?.contentView.removeChildView(state2.sidebarView);
7240
- state2.mainWindow.contentView.addChildView(state2.sidebarView);
7537
+ moveDetachedViewToMainWindow(state2, sidebarDetachedHost);
7241
7538
  }
7242
7539
  function getSidebarPanelState(state2) {
7243
7540
  return {
@@ -7297,41 +7594,33 @@ function detachSidebar(state2, hooks) {
7297
7594
  const detachedBounds = state2.uiState.sidebarDetachedBounds;
7298
7595
  const detachedWidth = detachedBounds?.width ?? Math.max(DETACHED_SIDEBAR_DEFAULT_WIDTH, state2.uiState.sidebarWidth);
7299
7596
  const detachedHeight = detachedBounds?.height ?? DETACHED_SIDEBAR_DEFAULT_HEIGHT;
7300
- const sidebarWindow = new electron.BaseWindow({
7301
- ...typeof detachedBounds?.x === "number" ? { x: detachedBounds.x } : {},
7302
- ...typeof detachedBounds?.y === "number" ? { y: detachedBounds.y } : {},
7303
- width: Math.max(DETACHED_SIDEBAR_MIN_WIDTH, Math.round(detachedWidth)),
7304
- height: Math.max(DETACHED_SIDEBAR_MIN_HEIGHT, Math.round(detachedHeight)),
7305
- minWidth: DETACHED_SIDEBAR_MIN_WIDTH,
7306
- minHeight: DETACHED_SIDEBAR_MIN_HEIGHT,
7307
- frame: true,
7308
- show: false,
7309
- backgroundColor: "#1a1a1e",
7310
- title: "Vessel Agent",
7311
- icon: hooks.getWindowIconPath()
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
+ }
7312
7622
  });
7313
- state2.mainWindow.contentView.removeChildView(state2.sidebarView);
7314
- sidebarWindow.contentView.addChildView(state2.sidebarView);
7315
- state2.sidebarWindow = sidebarWindow;
7316
7623
  setSidebarPanelMode(state2, "detached");
7317
- sidebarWindow.on("resize", () => {
7318
- layoutDetachedSidebar(state2);
7319
- persistDetachedBounds(state2);
7320
- });
7321
- sidebarWindow.on("move", () => persistDetachedBounds(state2));
7322
- sidebarWindow.on("close", (event) => {
7323
- if (state2.sidebarWindowClosing) return;
7324
- event.preventDefault();
7325
- dockSidebar(state2, hooks);
7326
- });
7327
- sidebarWindow.on("closed", () => {
7328
- if (state2.sidebarWindow !== sidebarWindow) return;
7329
- state2.sidebarWindow = null;
7330
- setSidebarPanelMode(state2, "docked");
7331
- state2.mainWindow.contentView.addChildView(state2.sidebarView);
7332
- hooks.relayout();
7333
- emitSidebarPanelState(state2);
7334
- });
7335
7624
  hooks.relayout();
7336
7625
  layoutDetachedSidebar(state2);
7337
7626
  sidebarWindow.show();
@@ -7554,8 +7843,9 @@ function createMainWindow(onTabStateChange) {
7554
7843
  sidebarDetachedBounds: settings2.sidebarDetachedBounds,
7555
7844
  focusMode: false,
7556
7845
  settingsOpen: false,
7557
- devtoolsPanelOpen: false,
7558
- devtoolsPanelHeight: DEFAULT_DEVTOOLS_PANEL_HEIGHT
7846
+ devtoolsPanelMode: "closed",
7847
+ devtoolsPanelHeight: DEFAULT_DEVTOOLS_PANEL_HEIGHT,
7848
+ devtoolsPanelDetachedBounds: settings2.devtoolsPanelDetachedBounds
7559
7849
  };
7560
7850
  const tabManager = new TabManager(mainWindow, onTabStateChange);
7561
7851
  const sendToRendererViews = (channel, ...args) => {
@@ -7571,6 +7861,8 @@ function createMainWindow(onTabStateChange) {
7571
7861
  mainWindow,
7572
7862
  sidebarWindow: null,
7573
7863
  sidebarWindowClosing: false,
7864
+ devtoolsPanelWindow: null,
7865
+ devtoolsPanelWindowClosing: false,
7574
7866
  chromeView,
7575
7867
  sidebarView,
7576
7868
  devtoolsPanelView,
@@ -7582,6 +7874,7 @@ function createMainWindow(onTabStateChange) {
7582
7874
  mainWindow.on("focus", () => layoutViews(state2));
7583
7875
  mainWindow.on("closed", () => {
7584
7876
  closeDetachedSidebarWindow(state2);
7877
+ closeDetachedDevToolsPanelWindow(state2);
7585
7878
  });
7586
7879
  sidebarView.webContents.on("context-menu", (event, params) => {
7587
7880
  event.preventDefault();
@@ -7610,7 +7903,9 @@ function layoutViews(state2) {
7610
7903
  const chromeHeight = uiState.focusMode ? 0 : CHROME_HEIGHT;
7611
7904
  const sidebarAttached = isSidebarAttached(state2);
7612
7905
  const sidebarWidth = sidebarAttached ? uiState.sidebarWidth : 0;
7613
- const devtoolsHeight = uiState.devtoolsPanelOpen ? uiState.devtoolsPanelHeight : 0;
7906
+ const devtoolsDocked = isDevToolsPanelDocked(state2);
7907
+ const devtoolsDetached = isDevToolsPanelDetached(state2);
7908
+ const devtoolsHeight = devtoolsDocked ? uiState.devtoolsPanelHeight : 0;
7614
7909
  const chromeNeedsFullHeight = uiState.settingsOpen;
7615
7910
  if (chromeNeedsFullHeight) {
7616
7911
  chromeView.setBounds({ x: 0, y: 0, width, height });
@@ -7628,14 +7923,14 @@ function layoutViews(state2) {
7628
7923
  sidebarView.setBounds({ x: width, y: 0, width: 0, height: 0 });
7629
7924
  }
7630
7925
  const contentWidth = width - sidebarWidth;
7631
- if (uiState.devtoolsPanelOpen) {
7926
+ if (devtoolsDocked) {
7632
7927
  devtoolsPanelView.setBounds({
7633
7928
  x: 0,
7634
7929
  y: height - devtoolsHeight,
7635
7930
  width: contentWidth,
7636
7931
  height: devtoolsHeight
7637
7932
  });
7638
- } else {
7933
+ } else if (!devtoolsDetached) {
7639
7934
  devtoolsPanelView.setBounds({ x: 0, y: height, width: 0, height: 0 });
7640
7935
  }
7641
7936
  mainWindow.contentView.removeChildView(chromeView);
@@ -7644,8 +7939,10 @@ function layoutViews(state2) {
7644
7939
  mainWindow.contentView.removeChildView(sidebarView);
7645
7940
  mainWindow.contentView.addChildView(sidebarView);
7646
7941
  }
7647
- mainWindow.contentView.removeChildView(devtoolsPanelView);
7648
- mainWindow.contentView.addChildView(devtoolsPanelView);
7942
+ if (!devtoolsDetached) {
7943
+ mainWindow.contentView.removeChildView(devtoolsPanelView);
7944
+ mainWindow.contentView.addChildView(devtoolsPanelView);
7945
+ }
7649
7946
  const activeTab = tabManager.getActiveTab();
7650
7947
  if (activeTab) {
7651
7948
  activeTab.view.setBounds({
@@ -7661,7 +7958,8 @@ function resizeSidebarViews(state2) {
7661
7958
  const [width, height] = mainWindow.getContentSize();
7662
7959
  const chromeHeight = uiState.focusMode ? 0 : CHROME_HEIGHT;
7663
7960
  const sidebarWidth = isSidebarAttached(state2) ? uiState.sidebarWidth : 0;
7664
- const devtoolsHeight = uiState.devtoolsPanelOpen ? uiState.devtoolsPanelHeight : 0;
7961
+ const devtoolsDocked = isDevToolsPanelDocked(state2);
7962
+ const devtoolsHeight = devtoolsDocked ? uiState.devtoolsPanelHeight : 0;
7665
7963
  const contentWidth = width - sidebarWidth;
7666
7964
  if (uiState.sidebarPanelMode !== "detached") {
7667
7965
  sidebarView.setBounds({
@@ -7671,7 +7969,7 @@ function resizeSidebarViews(state2) {
7671
7969
  height: height - chromeHeight
7672
7970
  });
7673
7971
  }
7674
- if (uiState.devtoolsPanelOpen) {
7972
+ if (devtoolsDocked) {
7675
7973
  devtoolsPanelView.setBounds({
7676
7974
  x: 0,
7677
7975
  y: height - devtoolsHeight,
@@ -10295,9 +10593,18 @@ function summarizeToolArg(args) {
10295
10593
  function normalizeCodexText(text) {
10296
10594
  return text.trim().toLowerCase().replace(/[‘’]/g, "'").replace(/[“”]/g, '"');
10297
10595
  }
10298
- function looksLikeFailedToolOutput(output) {
10596
+ function looksLikeFailedToolOutput(output, toolName) {
10299
10597
  const normalized = normalizeCodexText(output);
10300
- return normalized.startsWith("error") || normalized.startsWith("warning") || normalized.startsWith("target") || normalized.startsWith("no active tab") || normalized.includes("same page — results may have loaded dynamically") || normalized.includes("could not ") || normalized.includes("did not ");
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);
10301
10608
  }
10302
10609
  function looksLikeTravelFareContext(text) {
10303
10610
  const normalized = normalizeCodexText(text);
@@ -10305,7 +10612,7 @@ function looksLikeTravelFareContext(text) {
10305
10612
  }
10306
10613
  function emitCodexToolChunk(onChunk, name, args, output) {
10307
10614
  const summary = summarizeToolArg(args);
10308
- const argSummary = looksLikeFailedToolOutput(output) ? ["⚠ failed", summary].filter(Boolean).join(" ") : summary;
10615
+ const argSummary = looksLikeFailedToolOutput(output, name) ? ["⚠ failed", summary].filter(Boolean).join(" ") : summary;
10309
10616
  onChunk(`
10310
10617
  <<tool:${name}${argSummary ? ":" + argSummary : ""}>>
10311
10618
  `);
@@ -10970,7 +11277,10 @@ ${latestToolResultPreview || ""}`
10970
11277
  toolHistoryCount += 1;
10971
11278
  latestToolResultPreview = previewToolResult(output.output);
10972
11279
  const outputText = toolResultTextContent(output.output);
10973
- const toolSucceeded = !looksLikeFailedToolOutput(outputText);
11280
+ const toolSucceeded = !looksLikeFailedToolOutput(
11281
+ outputText,
11282
+ prepared.prepared.name
11283
+ );
10974
11284
  if (toolSucceeded && isRealProgressTool(prepared.prepared.name)) {
10975
11285
  failedClickCountSinceProgress = 0;
10976
11286
  }
@@ -10979,7 +11289,7 @@ ${latestToolResultPreview || ""}`
10979
11289
  searchToolQuery,
10980
11290
  toolSucceeded
10981
11291
  );
10982
- if (prepared.prepared.name === "click" && looksLikeFailedToolOutput(outputText)) {
11292
+ if (prepared.prepared.name === "click" && looksLikeFailedToolOutput(outputText, prepared.prepared.name)) {
10983
11293
  failedClickCountSinceProgress += 1;
10984
11294
  currentInput.push(
10985
11295
  buildCodexFailedClickRecoveryInput(
@@ -22978,7 +23288,7 @@ function registerPrivateIpcHandlers(state2) {
22978
23288
  });
22979
23289
  ipc.handle(Channels.FOCUS_MODE_TOGGLE, () => false);
22980
23290
  ipc.handle(Channels.SIDEBAR_TOGGLE, () => ({ open: false, width: 0 }));
22981
- ipc.handle(Channels.DEVTOOLS_PANEL_TOGGLE, () => ({ open: false }));
23291
+ registerDisabledDevToolsPanelHandlers(ipc);
22982
23292
  ipc.handle(
22983
23293
  Channels.FIND_IN_PAGE_START,
22984
23294
  (_e, text, options) => {
@@ -23242,7 +23552,7 @@ function registerSecondaryIpcHandlers(state2) {
23242
23552
  ipc.handle(Channels.SETTINGS_VISIBILITY, () => false);
23243
23553
  ipc.handle(Channels.FOCUS_MODE_TOGGLE, () => false);
23244
23554
  ipc.handle(Channels.SIDEBAR_TOGGLE, () => ({ open: false, width: 0 }));
23245
- ipc.handle(Channels.DEVTOOLS_PANEL_TOGGLE, () => ({ open: false }));
23555
+ registerDisabledDevToolsPanelHandlers(ipc);
23246
23556
  ipc.handle(
23247
23557
  Channels.FIND_IN_PAGE_START,
23248
23558
  (_e, text, options) => {
@@ -24775,8 +25085,14 @@ const DANGEROUS_DEVTOOLS_ACTIONS = /* @__PURE__ */ new Set([
24775
25085
  ]);
24776
25086
  let stateListener = null;
24777
25087
  const activityLog = [];
25088
+ const agentTraceLog = [];
24778
25089
  const MAX_ACTIVITY_ENTRIES = 100;
25090
+ const MAX_TRACE_ENTRIES = 200;
24779
25091
  let activityCounter = 0;
25092
+ let traceCounter = 0;
25093
+ let latestPageMap = null;
25094
+ let pageMapRefreshInFlight = false;
25095
+ const traceEntriesByActionId = /* @__PURE__ */ new Map();
24780
25096
  function setDevToolsPanelListener(listener) {
24781
25097
  stateListener = listener;
24782
25098
  }
@@ -24786,14 +25102,325 @@ function getDevToolsPanelState(tabId) {
24786
25102
  console: session?.getConsoleLogs() ?? [],
24787
25103
  network: session?.getNetworkLog() ?? [],
24788
25104
  errors: session?.getErrors() ?? [],
24789
- activity: activityLog
25105
+ activity: activityLog,
25106
+ agentTrace: agentTraceLog,
25107
+ pageMap: latestPageMap
24790
25108
  };
24791
25109
  }
24792
- function broadcastState(tabManager) {
25110
+ function broadcastDevToolsPanelState(tabManager) {
24793
25111
  if (!stateListener) return;
24794
25112
  const tabId = tabManager.getActiveTabId();
24795
25113
  stateListener(getDevToolsPanelState(tabId));
24796
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
+ }
24797
25424
  async function withDevToolsAction(runtime2, tabManager, name, args, executor) {
24798
25425
  try {
24799
25426
  assertFeatureUnlocked("devtools", "DevTools");
@@ -24802,6 +25429,7 @@ async function withDevToolsAction(runtime2, tabManager, name, args, executor) {
24802
25429
  `Error: ${error instanceof Error ? error.message : "DevTools require Vessel Premium."}`
24803
25430
  );
24804
25431
  }
25432
+ markActiveSessionToolOwned(tabManager);
24805
25433
  const activityEntry = {
24806
25434
  id: ++activityCounter,
24807
25435
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -30420,6 +31048,76 @@ async function togglePictureInPicture(tabManager) {
30420
31048
  return false;
30421
31049
  }
30422
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
+ }
30423
31121
  const VALID_KIT_CATEGORIES = /* @__PURE__ */ new Set([
30424
31122
  "research",
30425
31123
  "shopping",
@@ -30940,21 +31638,177 @@ function stopScheduler() {
30940
31638
  const KitIdSchema = zod.z.string().min(1);
30941
31639
  const SkillSourceSchema = zod.z.string().min(1).max(1e5);
30942
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"]);
30943
31653
  function registerSystemHandlers(windowState, sendToRendererViews) {
30944
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);
30945
31688
  electron.ipcMain.handle(Channels.DEVTOOLS_PANEL_TOGGLE, (event) => {
30946
31689
  assertTrustedIpcSender(event);
30947
- windowState.uiState.devtoolsPanelOpen = !windowState.uiState.devtoolsPanelOpen;
30948
- layoutViews(windowState);
30949
- return { open: windowState.uiState.devtoolsPanelOpen };
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();
30950
31744
  });
30951
31745
  electron.ipcMain.handle(Channels.DEVTOOLS_PANEL_RESIZE, (event, height) => {
30952
31746
  assertTrustedIpcSender(event);
30953
- const clamped = Math.max(MIN_DEVTOOLS_PANEL, Math.min(MAX_DEVTOOLS_PANEL, Math.round(height)));
30954
- windowState.uiState.devtoolsPanelHeight = clamped;
30955
- layoutViews(windowState);
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);
30956
31759
  return clamped;
30957
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
+ });
30958
31812
  electron.ipcMain.handle(Channels.AUTOMATION_GET_INSTALLED, async (event) => {
30959
31813
  assertTrustedIpcSender(event);
30960
31814
  assertFeatureUnlocked("automation_kits", "Skills");
@@ -33344,6 +34198,7 @@ class AgentRuntime {
33344
34198
  tabManager;
33345
34199
  state;
33346
34200
  updateListener = null;
34201
+ actionLifecycleListener = null;
33347
34202
  pendingResolvers = /* @__PURE__ */ new Map();
33348
34203
  undoSnapshots = [];
33349
34204
  mcpUnsubscribe = null;
@@ -33353,6 +34208,9 @@ class AgentRuntime {
33353
34208
  listener(this.getState());
33354
34209
  }
33355
34210
  }
34211
+ setActionLifecycleListener(listener) {
34212
+ this.actionLifecycleListener = listener;
34213
+ }
33356
34214
  /**
33357
34215
  * Release all resources, listeners, and pending promises.
33358
34216
  * Call when the window is closing to prevent memory leaks.
@@ -33371,6 +34229,7 @@ class AgentRuntime {
33371
34229
  this.state.flowState = null;
33372
34230
  this.state.taskTracker = null;
33373
34231
  this.updateListener = null;
34232
+ this.actionLifecycleListener = null;
33374
34233
  }
33375
34234
  getState() {
33376
34235
  const snapshot2 = clone(this.state);
@@ -33675,8 +34534,18 @@ ${progress}
33675
34534
  args,
33676
34535
  tabId
33677
34536
  });
34537
+ const actionStartedAt = Date.now();
33678
34538
  const transcriptStreamId = `action:${action.id}`;
33679
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
+ });
33680
34549
  this.publishTranscript({
33681
34550
  source,
33682
34551
  kind: "status",
@@ -33687,6 +34556,16 @@ ${progress}
33687
34556
  });
33688
34557
  const approvalReason = this.getApprovalReason(dangerous, requiresApproval);
33689
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
+ });
33690
34569
  this.publishTranscript({
33691
34570
  source,
33692
34571
  kind: "status",
@@ -33697,6 +34576,16 @@ ${progress}
33697
34576
  });
33698
34577
  const approved = await this.awaitApproval(action, approvalReason);
33699
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
+ });
33700
34589
  this.publishTranscript({
33701
34590
  source,
33702
34591
  kind: "status",
@@ -33728,6 +34617,16 @@ ${progress}
33728
34617
  this.pushUndoSnapshot(undoSnapshot);
33729
34618
  }
33730
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
+ });
33731
34630
  this.publishTranscript({
33732
34631
  source,
33733
34632
  kind: "status",
@@ -33742,6 +34641,16 @@ ${progress}
33742
34641
  const message = error instanceof Error ? error.message : "Unknown action failure";
33743
34642
  this.state.supervisor.lastError = message;
33744
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
+ });
33745
34654
  this.publishTranscript({
33746
34655
  source,
33747
34656
  kind: "status",
@@ -33753,6 +34662,15 @@ ${progress}
33753
34662
  throw error;
33754
34663
  }
33755
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
+ }
33756
34674
  createUndoSnapshot(name) {
33757
34675
  return {
33758
34676
  id: crypto$1.randomUUID(),
@@ -34326,6 +35244,10 @@ async function bootstrap() {
34326
35244
  if (meta.persistSession) {
34327
35245
  runtime?.onTabStateChanged();
34328
35246
  }
35247
+ if (windowState.uiState.devtoolsPanelMode !== "closed") {
35248
+ void enableCaptureForTab(windowState.tabManager);
35249
+ void refreshDevToolsPageMap(windowState.tabManager);
35250
+ }
34329
35251
  });
34330
35252
  windowStateForShutdown = windowState;
34331
35253
  let didRevealMainWindow = false;
@@ -34342,6 +35264,9 @@ async function bootstrap() {
34342
35264
  }, 8e3);
34343
35265
  const { chromeView, sidebarView, devtoolsPanelView, tabManager } = windowState;
34344
35266
  runtime = new AgentRuntime(tabManager);
35267
+ runtime.setActionLifecycleListener((event) => {
35268
+ recordDevToolsAgentAction(event, tabManager);
35269
+ });
34345
35270
  installAdBlocking(tabManager);
34346
35271
  setDevToolsPanelListener((state2) => {
34347
35272
  sendSafe(devtoolsPanelView.webContents, Channels.DEVTOOLS_PANEL_STATE, state2);