@quanta-intellect/vessel-browser 0.1.29 → 0.1.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -162,8 +162,10 @@ The installer:
162
162
  - creates a `vessel-browser-status` helper in `~/.local/bin`
163
163
  - creates a desktop entry for Linux app launchers
164
164
  - writes `~/.config/vessel/vessel-settings.json` with MCP port `3100`
165
+ - writes `~/.config/vessel/mcp-stdio-snippet.json`
165
166
  - writes `~/.config/vessel/mcp-http-snippet.json`
166
- - prints the exact HTTP MCP snippet to paste into your harness config
167
+ - installs a `vessel-browser-mcp` helper that can run as a stdio-to-HTTP proxy (`--stdio`) or print config snippets
168
+ - prints the exact recommended stdio MCP snippet to paste into your harness config
167
169
 
168
170
  The packaged AppImage path:
169
171
 
@@ -204,7 +206,7 @@ Notes:
204
206
 
205
207
  - `npm run dev` still launches the stock Electron binary, so Linux may continue showing the default Electron gear icon in development
206
208
  - packaged builds created with `npm run dist` / `npm run dist:dir` use the Vessel app icon
207
- - the tracked smoke test runs typecheck, build, and the Electron navigation regression harness
209
+ - the tracked smoke test runs typecheck, build, the MCP stdio proxy regression check, and the Electron navigation regression harness
208
210
  - for headless CI, run the smoke test under `xvfb-run -a npm run smoke:test`
209
211
 
210
212
  ### Setting up Vessel for Hermes Agent or OpenClaw
@@ -214,7 +216,7 @@ Vessel is designed to act as the browser runtime that your external agent harnes
214
216
  1. Launch Vessel
215
217
  2. Open Settings (`Ctrl+,`) to confirm MCP status, copy the endpoint, or change the MCP port
216
218
  3. Optional: set an Obsidian vault path or session preferences
217
- 4. Start Hermes Agent or OpenClaw and configure it to connect to Vessel's MCP endpoint at `http://127.0.0.1:<mcpPort>/mcp`
219
+ 4. Start Hermes Agent or OpenClaw and point it at Vessel the easiest way is `vessel-browser-mcp --stdio` as the MCP command (auth is resolved automatically), or connect directly to `http://127.0.0.1:<mcpPort>/mcp` with the bearer token from `~/.config/vessel/mcp-auth.json`
218
220
  5. Use the Supervisor panel in Vessel's sidebar to pause the agent, change approval mode, review pending approvals, checkpoint, or restore the browser session while the harness runs
219
221
  6. Use the Bookmarks panel to organize saved pages into folders and expose those bookmarks back to the agent over MCP
220
222
 
@@ -316,7 +318,23 @@ The extraction output can distinguish:
316
318
  - active blocking overlays
317
319
  - dormant consent/modal UI present in the DOM but not active for the current session or region
318
320
 
319
- Generic HTTP MCP config:
321
+ Stdio proxy MCP config (recommended — resolves auth automatically):
322
+
323
+ ```json
324
+ {
325
+ "mcpServers": {
326
+ "vessel": {
327
+ "command": "vessel-browser-mcp",
328
+ "args": ["--stdio"]
329
+ }
330
+ }
331
+ }
332
+ ```
333
+
334
+ The stdio proxy reads the bearer token from `~/.config/vessel/mcp-auth.json` at connection time, so no manual token management is needed.
335
+ Vessel must already be running when your MCP client connects, and `~/.config/vessel/mcp-auth.json` must exist from install or first launch.
336
+
337
+ Generic HTTP MCP config (requires copying the token manually):
320
338
 
321
339
  ```json
322
340
  {
@@ -346,8 +364,9 @@ mcp_servers:
346
364
 
347
365
  ## Configuration
348
366
 
349
- The installer writes both snippets to:
367
+ The installer writes three snippets to:
350
368
 
369
+ - `~/.config/vessel/mcp-stdio-snippet.json`
351
370
  - `~/.config/vessel/mcp-http-snippet.json`
352
371
  - `~/.config/vessel/mcp-hermes-snippet.yaml`
353
372
 
@@ -360,9 +379,15 @@ vessel-browser-mcp
360
379
  Helper examples:
361
380
 
362
381
  ```bash
363
- # Generic JSON snippet with Authorization header
382
+ # Run as stdio-to-HTTP proxy (for MCP client integration)
383
+ vessel-browser-mcp --stdio
384
+
385
+ # Recommended stdio MCP snippet
364
386
  vessel-browser-mcp
365
387
 
388
+ # Generic JSON snippet with Authorization header
389
+ vessel-browser-mcp --format json
390
+
366
391
  # Hermes-ready YAML snippet with Authorization header
367
392
  vessel-browser-mcp --format hermes
368
393
 
package/out/main/index.js CHANGED
@@ -5177,9 +5177,6 @@ class AnthropicProvider {
5177
5177
  let iterationsUsed = 0;
5178
5178
  for (let i = 0; i < maxIterations; i++) {
5179
5179
  iterationsUsed = i + 1;
5180
- const msgTokenEstimate = JSON.stringify(messages).length;
5181
- const sysTokenEstimate = systemPrompt.length;
5182
- const streamStartTime = Date.now();
5183
5180
  const stream = this.client.messages.stream(
5184
5181
  {
5185
5182
  model: this.model,
@@ -5496,8 +5493,6 @@ class OpenAICompatProvider {
5496
5493
  let iterationsUsed = 0;
5497
5494
  for (let i = 0; i < maxIterations; i++) {
5498
5495
  iterationsUsed = i + 1;
5499
- const msgTokenEstimate = JSON.stringify(messages).length;
5500
- const streamStartTime = Date.now();
5501
5496
  let textAccum = "";
5502
5497
  const toolCallAccums = {};
5503
5498
  let finishReason = null;
@@ -12135,7 +12130,7 @@ async function executeAction(name, args, ctx) {
12135
12130
  }
12136
12131
  case "click": {
12137
12132
  if (!wc) return "Error: No active tab";
12138
- const selector = await resolveSelector$1(wc, args.index, args.selector);
12133
+ const selector = typeof args.selector === "string" && args.selector.trim() ? await resolveSelector$1(wc, void 0, args.selector) : typeof args.index === "number" ? `__vessel_idx:${args.index}` : await resolveSelector$1(wc, args.index, args.selector);
12139
12134
  if (!selector) return "Error: No element index or selector provided";
12140
12135
  return clickResolvedSelector$1(wc, selector);
12141
12136
  }
@@ -15966,7 +15961,7 @@ ${buildScopedContext(pageContent, mode)}`;
15966
15961
  { index, selector },
15967
15962
  async () => {
15968
15963
  const wc = tab.view.webContents;
15969
- const resolvedSelector = await resolveSelector(wc, index, selector);
15964
+ const resolvedSelector = typeof selector === "string" && selector.trim() ? await resolveSelector(wc, void 0, selector) : typeof index === "number" ? `__vessel_idx:${index}` : await resolveSelector(wc, index, selector);
15970
15965
  if (!resolvedSelector) {
15971
15966
  return "Error: No index or selector provided";
15972
15967
  }
@@ -3195,6 +3195,25 @@ function interactByIndex(index, action, value) {
3195
3195
  if (action === "click") {
3196
3196
  el.focus();
3197
3197
  el.click();
3198
+ if (el instanceof HTMLInputElement) {
3199
+ if (el.type === "checkbox") {
3200
+ const label = getInputLabel(el) || el.getAttribute("aria-label") || el.name || "checkbox";
3201
+ return `${el.checked ? "Checked" : "Unchecked"}: ${label}`;
3202
+ }
3203
+ if (el.type === "radio") {
3204
+ const label = getTrimmedText(el.value) || getInputLabel(el) || el.getAttribute("aria-label") || el.name || "radio";
3205
+ return `${el.checked ? "Selected" : "Clicked"}: ${label}`;
3206
+ }
3207
+ }
3208
+ const role = el.getAttribute("role");
3209
+ if (role === "checkbox" || role === "radio") {
3210
+ const label = getTrimmedText(el.getAttribute("aria-label")) || getTrimmedText(el.textContent) || el.tagName.toLowerCase();
3211
+ const ariaChecked = el.getAttribute("aria-checked");
3212
+ if (role === "checkbox") {
3213
+ return `${ariaChecked === "true" ? "Checked" : "Unchecked"}: ${label}`;
3214
+ }
3215
+ return `${ariaChecked === "true" ? "Selected" : "Clicked"}: ${label}`;
3216
+ }
3198
3217
  return "Clicked: " + (el.getAttribute("aria-label") || el.textContent?.trim().slice(0, 60) || el.tagName.toLowerCase());
3199
3218
  }
3200
3219
  if (action === "focus") {
@@ -1696,6 +1696,7 @@ function getAgentPresence(state, currentTime = Date.now()) {
1696
1696
  return "idle";
1697
1697
  }
1698
1698
  var _tmpl$$d = /* @__PURE__ */ template(`<img class=tab-favicon alt>`), _tmpl$2$b = /* @__PURE__ */ template(`<span class=tab-favicon-fallback>`), _tmpl$3$8 = /* @__PURE__ */ template(`<div class=tab-bar><div class=tab-list><button class=tab-new data-tooltip="New Tab">+`), _tmpl$4$8 = /* @__PURE__ */ template(`<div role=tab><span class=tab-title></span><button class=tab-close>×`), _tmpl$5$6 = /* @__PURE__ */ template(`<span class=tab-agent-indicator aria-hidden=true title="Agent active on this tab">`), _tmpl$6$6 = /* @__PURE__ */ template(`<span class=tab-loading>`);
1699
+ const TAB_CLOSE_MS = 200;
1699
1700
  function stringToHue(str) {
1700
1701
  let hash = 0;
1701
1702
  for (let i = 0; i < str.length; i++) {
@@ -1747,7 +1748,19 @@ const TabBar = () => {
1747
1748
  runtimeState: runtimeState2
1748
1749
  } = useRuntime();
1749
1750
  const now2 = useNow();
1751
+ const [closingTabIds, setClosingTabIds] = createSignal(/* @__PURE__ */ new Set());
1750
1752
  const modelActiveTabIds = createMemo(() => getAgentActiveTabIds(runtimeState2(), now2()));
1753
+ const handleClose = (id) => {
1754
+ setClosingTabIds((prev) => new Set(prev).add(id));
1755
+ setTimeout(() => {
1756
+ closeTab(id);
1757
+ setClosingTabIds((prev) => {
1758
+ const next = new Set(prev);
1759
+ next.delete(id);
1760
+ return next;
1761
+ });
1762
+ }, TAB_CLOSE_MS);
1763
+ };
1751
1764
  return (() => {
1752
1765
  var _el$3 = _tmpl$3$8(), _el$4 = _el$3.firstChild, _el$5 = _el$4.firstChild;
1753
1766
  insert(_el$4, createComponent(For, {
@@ -1757,7 +1770,7 @@ const TabBar = () => {
1757
1770
  children: (tab) => (() => {
1758
1771
  var _el$6 = _tmpl$4$8(), _el$7 = _el$6.firstChild, _el$8 = _el$7.nextSibling;
1759
1772
  _el$6.addEventListener("auxclick", (e) => {
1760
- if (e.button === 1) closeTab(tab.id);
1773
+ if (e.button === 1) handleClose(tab.id);
1761
1774
  });
1762
1775
  _el$6.$$click = () => switchTab(tab.id);
1763
1776
  insert(_el$6, createComponent(TabFavicon, {
@@ -1782,16 +1795,18 @@ const TabBar = () => {
1782
1795
  })(), _el$8);
1783
1796
  _el$8.$$click = (e) => {
1784
1797
  e.stopPropagation();
1785
- closeTab(tab.id);
1798
+ handleClose(tab.id);
1786
1799
  };
1787
1800
  createRenderEffect((_p$) => {
1788
- var _v$ = `tab-item ${tab.id === activeTabId2() ? "active" : ""} ${modelActiveTabIds().has(tab.id) ? "model-active" : ""}`, _v$2 = modelActiveTabIds().has(tab.id) ? `${tab.title || "New Tab"} • Agent active` : tab.title;
1801
+ var _v$ = `tab-item ${tab.id === activeTabId2() ? "active" : ""} ${modelActiveTabIds().has(tab.id) ? "model-active" : ""}`, _v$2 = !!closingTabIds().has(tab.id), _v$3 = modelActiveTabIds().has(tab.id) ? `${tab.title || "New Tab"} • Agent active` : tab.title;
1789
1802
  _v$ !== _p$.e && className(_el$6, _p$.e = _v$);
1790
- _v$2 !== _p$.t && setAttribute(_el$6, "title", _p$.t = _v$2);
1803
+ _v$2 !== _p$.t && _el$6.classList.toggle("closing", _p$.t = _v$2);
1804
+ _v$3 !== _p$.a && setAttribute(_el$6, "title", _p$.a = _v$3);
1791
1805
  return _p$;
1792
1806
  }, {
1793
1807
  e: void 0,
1794
- t: void 0
1808
+ t: void 0,
1809
+ a: void 0
1795
1810
  });
1796
1811
  return _el$6;
1797
1812
  })()
@@ -2122,6 +2137,15 @@ function useScrollFade(el) {
2122
2137
  observer.disconnect();
2123
2138
  });
2124
2139
  }
2140
+ function formatTime(iso, options) {
2141
+ const d = new Date(iso);
2142
+ if (Number.isNaN(d.getTime())) return "";
2143
+ return d.toLocaleTimeString([], {
2144
+ hour: options?.includeSeconds ? "2-digit" : "numeric",
2145
+ minute: "2-digit",
2146
+ ...options?.includeSeconds && { second: "2-digit" }
2147
+ });
2148
+ }
2125
2149
  var _tmpl$$9 = /* @__PURE__ */ template(`<span class=agent-transcript-live><span class=agent-transcript-live-dot aria-hidden=true></span>Live`), _tmpl$2$8 = /* @__PURE__ */ template(`<div class=agent-transcript-list>`), _tmpl$3$7 = /* @__PURE__ */ template(`<aside class=agent-transcript-dock><div class=agent-transcript-header><div class=agent-transcript-title-row><span class=agent-transcript-title>Agent Transcript</span></div><div class=agent-transcript-actions><button class=agent-transcript-icon></button><button class=agent-transcript-icon data-tooltip=Hide>×`), _tmpl$4$7 = /* @__PURE__ */ template(`<article><div class=agent-transcript-meta><span class=agent-transcript-badge></span><span class=agent-transcript-time></span></div><div class=agent-transcript-text>`);
2126
2150
  const AgentTranscriptDock = () => {
2127
2151
  const {
@@ -2140,14 +2164,6 @@ const AgentTranscriptDock = () => {
2140
2164
  });
2141
2165
  const visibleEntries = createMemo(() => runtimeState2().transcript.slice(-6).reverse());
2142
2166
  const hasStreamingEntry = createMemo(() => visibleEntries().some((entry) => entry.status === "streaming"));
2143
- const formatTime2 = (value) => {
2144
- const date = new Date(value);
2145
- if (Number.isNaN(date.getTime())) return "";
2146
- return date.toLocaleTimeString([], {
2147
- hour: "numeric",
2148
- minute: "2-digit"
2149
- });
2150
- };
2151
2167
  const hideDock = async () => {
2152
2168
  setMode("off");
2153
2169
  await window.vessel.settings.set("agentTranscriptMode", "off");
@@ -2185,7 +2201,7 @@ const AgentTranscriptDock = () => {
2185
2201
  children: (entry) => (() => {
2186
2202
  var _el$0 = _tmpl$4$7(), _el$1 = _el$0.firstChild, _el$10 = _el$1.firstChild, _el$11 = _el$10.nextSibling, _el$12 = _el$1.nextSibling;
2187
2203
  insert(_el$10, () => entry.title || entry.kind);
2188
- insert(_el$11, () => formatTime2(entry.updatedAt));
2204
+ insert(_el$11, () => formatTime(entry.updatedAt));
2189
2205
  insert(_el$12, () => entry.text);
2190
2206
  createRenderEffect((_p$) => {
2191
2207
  var _v$3 = `agent-transcript-entry ${entry.kind}`, _v$4 = !!(entry.status === "streaming");
@@ -2459,7 +2475,35 @@ function useAI() {
2459
2475
  }
2460
2476
  };
2461
2477
  }
2478
+ function useAnimatedPresence(isOpen, exitDurationMs) {
2479
+ const [visible, setVisible] = createSignal(false);
2480
+ const [closing, setClosing] = createSignal(false);
2481
+ let exitTimer;
2482
+ createEffect(() => {
2483
+ const open = isOpen();
2484
+ if (open) {
2485
+ if (exitTimer) {
2486
+ clearTimeout(exitTimer);
2487
+ exitTimer = void 0;
2488
+ }
2489
+ setClosing(false);
2490
+ setVisible(true);
2491
+ } else if (visible()) {
2492
+ setClosing(true);
2493
+ exitTimer = window.setTimeout(() => {
2494
+ setVisible(false);
2495
+ setClosing(false);
2496
+ exitTimer = void 0;
2497
+ }, exitDurationMs);
2498
+ }
2499
+ });
2500
+ onCleanup(() => {
2501
+ if (exitTimer) clearTimeout(exitTimer);
2502
+ });
2503
+ return { visible, closing };
2504
+ }
2462
2505
  var _tmpl$$8 = /* @__PURE__ */ template(`<div class=command-bar-no-provider><p>Configure a chat provider to start using the AI assistant.</p><button class=command-bar-no-provider-btn>Open Settings <kbd>Ctrl+,`), _tmpl$2$7 = /* @__PURE__ */ template(`<div class=command-bar-recent><span class=command-bar-recent-label>Recent</span><div class=command-bar-recent-list>`), _tmpl$3$6 = /* @__PURE__ */ template(`<span>Try "summarize" or ask a question`), _tmpl$4$6 = /* @__PURE__ */ template(`<div class=command-bar-overlay><div class=command-bar><form><div class=command-bar-icon><svg width=16 height=16 viewBox="0 0 16 16"><circle cx=8 cy=8 r=6 fill=none stroke=var(--accent-primary) stroke-width=1.5></circle><circle cx=6 cy=7 r=0.8 fill=var(--accent-primary)></circle><circle cx=10 cy=7 r=0.8 fill=var(--accent-primary)></circle><path d="M6 10c0.5 0.8 3.5 0.8 4 0"fill=none stroke=var(--accent-primary) stroke-width=0.8 stroke-linecap=round></path></svg></div><input class=command-bar-input type=text></form><div class=command-bar-hints><span><kbd>Enter</kbd> to ask</span><span><kbd>Esc</kbd> to close`), _tmpl$5$5 = /* @__PURE__ */ template(`<button class=command-bar-recent-item type=button>`), _tmpl$6$5 = /* @__PURE__ */ template(`<span>Set up a provider in Settings first`);
2506
+ const COMMAND_BAR_EXIT_MS = 200;
2463
2507
  const CommandBar = () => {
2464
2508
  const {
2465
2509
  commandBarOpen: commandBarOpen2,
@@ -2467,6 +2511,10 @@ const CommandBar = () => {
2467
2511
  toggleSidebar,
2468
2512
  openSettings
2469
2513
  } = useUI();
2514
+ const {
2515
+ visible,
2516
+ closing
2517
+ } = useAnimatedPresence(commandBarOpen2, COMMAND_BAR_EXIT_MS);
2470
2518
  const {
2471
2519
  query,
2472
2520
  recentQueries: recentQueries2,
@@ -2505,7 +2553,7 @@ const CommandBar = () => {
2505
2553
  };
2506
2554
  return createComponent(Show, {
2507
2555
  get when() {
2508
- return commandBarOpen2();
2556
+ return visible();
2509
2557
  },
2510
2558
  get children() {
2511
2559
  var _el$ = _tmpl$4$6(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _el$4 = _el$3.firstChild, _el$5 = _el$4.nextSibling, _el$10 = _el$3.nextSibling, _el$11 = _el$10.firstChild;
@@ -2562,13 +2610,15 @@ const CommandBar = () => {
2562
2610
  }
2563
2611
  }), null);
2564
2612
  createRenderEffect((_p$) => {
2565
- var _v$ = hasProvider() ? "Ask about this page, summarize, or search..." : "No chat provider configured", _v$2 = !hasProvider();
2566
- _v$ !== _p$.e && setAttribute(_el$5, "placeholder", _p$.e = _v$);
2567
- _v$2 !== _p$.t && (_el$5.disabled = _p$.t = _v$2);
2613
+ var _v$ = !!closing(), _v$2 = hasProvider() ? "Ask about this page, summarize, or search..." : "No chat provider configured", _v$3 = !hasProvider();
2614
+ _v$ !== _p$.e && _el$.classList.toggle("closing", _p$.e = _v$);
2615
+ _v$2 !== _p$.t && setAttribute(_el$5, "placeholder", _p$.t = _v$2);
2616
+ _v$3 !== _p$.a && (_el$5.disabled = _p$.a = _v$3);
2568
2617
  return _p$;
2569
2618
  }, {
2570
2619
  e: void 0,
2571
- t: void 0
2620
+ t: void 0,
2621
+ a: void 0
2572
2622
  });
2573
2623
  createRenderEffect(() => _el$5.value = input());
2574
2624
  return _el$;
@@ -4034,6 +4084,9 @@ ${token}
4034
4084
  ALLOWED_ATTR: ["href", "target", "rel", "data-language", "style", "class"]
4035
4085
  });
4036
4086
  }
4087
+ function isPremiumStatus(status) {
4088
+ return status === "active" || status === "trialing";
4089
+ }
4037
4090
  const WORD_NORMALIZATIONS = [
4038
4091
  [/\bminutes?\b/g, "min"],
4039
4092
  [/\bmins?\b/g, "min"],
@@ -4643,10 +4696,7 @@ const AutomationTab = (props) => {
4643
4696
  const [premiumData] = createResource(() => window.vessel.premium.getState().catch(() => ({
4644
4697
  status: "free"
4645
4698
  })));
4646
- const isPremium = () => {
4647
- const s = premiumData()?.status;
4648
- return s === "active" || s === "trialing";
4649
- };
4699
+ const isPremium = () => isPremiumStatus(premiumData()?.status);
4650
4700
  const [installedKits, {
4651
4701
  refetch: refetchInstalled
4652
4702
  }] = createResource(() => isPremium(), (active) => active ? window.vessel.automation.getInstalled().catch(() => []) : Promise.resolve([]));
@@ -5456,10 +5506,7 @@ const Sidebar = (props) => {
5456
5506
  expiresAt: ""
5457
5507
  });
5458
5508
  const trackedPremiumContexts = /* @__PURE__ */ new Set();
5459
- const isPremium = () => {
5460
- const status = premiumState().status;
5461
- return status === "active" || status === "trialing";
5462
- };
5509
+ const isPremium = () => isPremiumStatus(premiumState().status);
5463
5510
  const trackPremiumContext = (step) => {
5464
5511
  if (trackedPremiumContexts.has(step)) return;
5465
5512
  trackedPremiumContexts.add(step);
@@ -6549,18 +6596,6 @@ ${contextBlock}` : contextBlock);
6549
6596
  };
6550
6597
  delegateEvents(["click", "pointerdown", "input", "keydown"]);
6551
6598
  var _tmpl$$3 = /* @__PURE__ */ template(`<div class=devtools-console>`), _tmpl$2$3 = /* @__PURE__ */ template(`<div class=devtools-empty>Waiting for console output... Console monitoring activates when an agent uses devtools.`), _tmpl$3$2 = /* @__PURE__ */ template(`<div><span></span><span class=console-time></span><span class=console-text></span><span class=console-source>`), _tmpl$4$2 = /* @__PURE__ */ template(`<div class=devtools-network><div class=network-header><span>Method</span><span>URL</span><span>Status</span><span>Type</span><span>Time`), _tmpl$5$2 = /* @__PURE__ */ template(`<div class=devtools-empty>Waiting for network requests... Network monitoring activates when an agent uses devtools.`), _tmpl$6$2 = /* @__PURE__ */ template(`<div><span class=network-method></span><span class=network-url></span><span></span><span class=network-type></span><span class=network-duration>`), _tmpl$7$1 = /* @__PURE__ */ template(`<div class=devtools-activity>`), _tmpl$8$1 = /* @__PURE__ */ template(`<div class=devtools-empty>Waiting for agent devtools activity...`), _tmpl$9$1 = /* @__PURE__ */ template(`<div class=activity-entry><span class=activity-time></span><span class=activity-tool></span><span class=activity-args></span><span></span><span class=activity-duration>`), _tmpl$0$1 = /* @__PURE__ */ template(`<span class="devtools-tab-badge error">`), _tmpl$1$1 = /* @__PURE__ */ template(`<span class="devtools-tab-badge count">`), _tmpl$10$1 = /* @__PURE__ */ template(`<div class=export-date-inputs><div class=export-date-row><span class=export-date-label>From</span><input class=export-date-input type=date></div><div class=export-date-row><span class=export-date-label>To</span><input class=export-date-input type=date>`), _tmpl$11$1 = /* @__PURE__ */ template(`<div class=devtools-export-dropdown><div class=export-section><div class=export-section-label>Log Types</div><label class=export-checkbox><input type=checkbox>Console</label><label class=export-checkbox><input type=checkbox>Network</label><label class=export-checkbox><input type=checkbox>Activity</label></div><div class=export-section><div class=export-section-label>Date Range</div><div class=export-date-btns><button>Today</button><button>Custom</button></div></div><button class=export-submit>Export JSON`), _tmpl$12$1 = /* @__PURE__ */ template(`<div class=devtools-panel><div class=devtools-tabs><button>Console</button><button>Network</button><button>Activity</button><div class=devtools-tab-spacer></div><div class=devtools-export-wrap><button title="Export Logs"><svg width=13 height=13 viewBox="0 0 13 13"fill=none style=vertical-align:middle><path d="M6.5 1v7M3.5 5l3 3 3-3"stroke=currentColor stroke-width=1.3 stroke-linecap=round stroke-linejoin=round></path><path d="M1 9.5v1A1.5 1.5 0 0 0 2.5 12h8A1.5 1.5 0 0 0 12 10.5v-1"stroke=currentColor stroke-width=1.3 stroke-linecap=round></path></svg></button></div><button class=devtools-close-btn title="Close DevTools">×</button></div><div class=devtools-content>`);
6552
- function formatTime(iso) {
6553
- try {
6554
- const d = new Date(iso);
6555
- return d.toLocaleTimeString([], {
6556
- hour: "2-digit",
6557
- minute: "2-digit",
6558
- second: "2-digit"
6559
- });
6560
- } catch {
6561
- return "";
6562
- }
6563
- }
6564
6599
  function statusClass(status) {
6565
6600
  if (status == null) return "pending";
6566
6601
  if (status >= 200 && status < 300) return "ok";
@@ -6635,7 +6670,9 @@ const ConsoleView = (props) => {
6635
6670
  children: (entry) => (() => {
6636
6671
  var _el$3 = _tmpl$3$2(), _el$4 = _el$3.firstChild, _el$5 = _el$4.nextSibling, _el$6 = _el$5.nextSibling, _el$7 = _el$6.nextSibling;
6637
6672
  insert(_el$4, () => entry.level);
6638
- insert(_el$5, () => formatTime(entry.timestamp));
6673
+ insert(_el$5, () => formatTime(entry.timestamp, {
6674
+ includeSeconds: true
6675
+ }));
6639
6676
  insert(_el$6, () => entry.text);
6640
6677
  insert(_el$7, () => shortenSource(entry.url, entry.line));
6641
6678
  createRenderEffect((_p$) => {
@@ -6734,7 +6771,9 @@ const ActivityView = (props) => {
6734
6771
  },
6735
6772
  children: (entry) => (() => {
6736
6773
  var _el$17 = _tmpl$9$1(), _el$18 = _el$17.firstChild, _el$19 = _el$18.nextSibling, _el$20 = _el$19.nextSibling, _el$21 = _el$20.nextSibling, _el$22 = _el$21.nextSibling;
6737
- insert(_el$18, () => formatTime(entry.timestamp));
6774
+ insert(_el$18, () => formatTime(entry.timestamp, {
6775
+ includeSeconds: true
6776
+ }));
6738
6777
  insert(_el$19, () => entry.tool.replace("devtools_", ""));
6739
6778
  insert(_el$20, () => entry.args);
6740
6779
  insert(_el$21, (() => {
@@ -6994,6 +7033,9 @@ var _tmpl$$2 = /* @__PURE__ */ template(`<div class=welcome-banner-actions><butt
6994
7033
  inset 0 1px 0 rgba(255, 255, 255, 0.04);
6995
7034
  animation: command-bar-enter 350ms var(--ease-out-expo) both;
6996
7035
  }
7036
+ .command-bar-overlay.closing .settings-panel {
7037
+ animation: command-bar-exit 200ms var(--ease-in-out) both;
7038
+ }
6997
7039
  .settings-title {
6998
7040
  font-size: 16px;
6999
7041
  font-weight: 600;
@@ -7580,6 +7622,10 @@ const Settings = () => {
7580
7622
  settingsOpen: settingsOpen2,
7581
7623
  closeSettings
7582
7624
  } = useUI();
7625
+ const {
7626
+ visible: settingsVisible,
7627
+ closing: settingsClosing
7628
+ } = useAnimatedPresence(settingsOpen2, 200);
7583
7629
  const [autoRestoreSession, setAutoRestoreSession] = createSignal(true);
7584
7630
  const [clearBookmarksOnLaunch, setClearBookmarksOnLaunch] = createSignal(false);
7585
7631
  const [obsidianVaultPath, setObsidianVaultPath] = createSignal("");
@@ -7841,7 +7887,7 @@ const Settings = () => {
7841
7887
  };
7842
7888
  return createComponent(Show, {
7843
7889
  get when() {
7844
- return settingsOpen2();
7890
+ return settingsVisible();
7845
7891
  },
7846
7892
  get children() {
7847
7893
  return [(() => {
@@ -7962,9 +8008,9 @@ const Settings = () => {
7962
8008
  })()
7963
8009
  }), null);
7964
8010
  createRenderEffect((_p$) => {
7965
- var _v$9 = !!(issue.severity === "warning"), _v$0 = !!(issue.severity === "error");
7966
- _v$9 !== _p$.e && _el$110.classList.toggle("warning", _p$.e = _v$9);
7967
- _v$0 !== _p$.t && _el$110.classList.toggle("error", _p$.t = _v$0);
8011
+ var _v$0 = !!(issue.severity === "warning"), _v$1 = !!(issue.severity === "error");
8012
+ _v$0 !== _p$.e && _el$110.classList.toggle("warning", _p$.e = _v$0);
8013
+ _v$1 !== _p$.t && _el$110.classList.toggle("error", _p$.t = _v$1);
7968
8014
  return _p$;
7969
8015
  }, {
7970
8016
  e: void 0,
@@ -8148,9 +8194,9 @@ const Settings = () => {
8148
8194
  var _el$124 = _tmpl$29();
8149
8195
  insert(_el$124, () => msg().text);
8150
8196
  createRenderEffect((_p$) => {
8151
- var _v$1 = !!(msg().kind === "success"), _v$10 = !!(msg().kind === "error");
8152
- _v$1 !== _p$.e && _el$124.classList.toggle("success", _p$.e = _v$1);
8153
- _v$10 !== _p$.t && _el$124.classList.toggle("error", _p$.t = _v$10);
8197
+ var _v$10 = !!(msg().kind === "success"), _v$11 = !!(msg().kind === "error");
8198
+ _v$10 !== _p$.e && _el$124.classList.toggle("success", _p$.e = _v$10);
8199
+ _v$11 !== _p$.t && _el$124.classList.toggle("error", _p$.t = _v$11);
8154
8200
  return _p$;
8155
8201
  }, {
8156
8202
  e: void 0,
@@ -8312,9 +8358,9 @@ const Settings = () => {
8312
8358
  var _el$132 = _tmpl$29();
8313
8359
  insert(_el$132, () => msg().text);
8314
8360
  createRenderEffect((_p$) => {
8315
- var _v$11 = !!(msg().kind === "success"), _v$12 = !!(msg().kind === "error");
8316
- _v$11 !== _p$.e && _el$132.classList.toggle("success", _p$.e = _v$11);
8317
- _v$12 !== _p$.t && _el$132.classList.toggle("error", _p$.t = _v$12);
8361
+ var _v$12 = !!(msg().kind === "success"), _v$13 = !!(msg().kind === "error");
8362
+ _v$12 !== _p$.e && _el$132.classList.toggle("success", _p$.e = _v$12);
8363
+ _v$13 !== _p$.t && _el$132.classList.toggle("error", _p$.t = _v$13);
8318
8364
  return _p$;
8319
8365
  }, {
8320
8366
  e: void 0,
@@ -8336,9 +8382,9 @@ const Settings = () => {
8336
8382
  var _el$133 = _tmpl$29();
8337
8383
  insert(_el$133, () => currentStatus().text);
8338
8384
  createRenderEffect((_p$) => {
8339
- var _v$13 = !!(currentStatus().kind === "success"), _v$14 = !!(currentStatus().kind === "error");
8340
- _v$13 !== _p$.e && _el$133.classList.toggle("success", _p$.e = _v$13);
8341
- _v$14 !== _p$.t && _el$133.classList.toggle("error", _p$.t = _v$14);
8385
+ var _v$14 = !!(currentStatus().kind === "success"), _v$15 = !!(currentStatus().kind === "error");
8386
+ _v$14 !== _p$.e && _el$133.classList.toggle("success", _p$.e = _v$14);
8387
+ _v$15 !== _p$.t && _el$133.classList.toggle("error", _p$.t = _v$15);
8342
8388
  return _p$;
8343
8389
  }, {
8344
8390
  e: void 0,
@@ -8348,15 +8394,16 @@ const Settings = () => {
8348
8394
  })()
8349
8395
  }), null);
8350
8396
  createRenderEffect((_p$) => {
8351
- var _v$ = !!autoRestoreSession(), _v$2 = autoRestoreSession(), _v$3 = !!clearBookmarksOnLaunch(), _v$4 = clearBookmarksOnLaunch(), _v$5 = !!chatEnabled(), _v$6 = chatEnabled(), _v$7 = !!telemetryEnabled(), _v$8 = telemetryEnabled();
8352
- _v$ !== _p$.e && _el$36.classList.toggle("on", _p$.e = _v$);
8353
- _v$2 !== _p$.t && setAttribute(_el$36, "aria-checked", _p$.t = _v$2);
8354
- _v$3 !== _p$.a && _el$39.classList.toggle("on", _p$.a = _v$3);
8355
- _v$4 !== _p$.o && setAttribute(_el$39, "aria-checked", _p$.o = _v$4);
8356
- _v$5 !== _p$.i && _el$43.classList.toggle("on", _p$.i = _v$5);
8357
- _v$6 !== _p$.n && setAttribute(_el$43, "aria-checked", _p$.n = _v$6);
8358
- _v$7 !== _p$.s && _el$93.classList.toggle("on", _p$.s = _v$7);
8359
- _v$8 !== _p$.h && setAttribute(_el$93, "aria-checked", _p$.h = _v$8);
8397
+ var _v$ = !!settingsClosing(), _v$2 = !!autoRestoreSession(), _v$3 = autoRestoreSession(), _v$4 = !!clearBookmarksOnLaunch(), _v$5 = clearBookmarksOnLaunch(), _v$6 = !!chatEnabled(), _v$7 = chatEnabled(), _v$8 = !!telemetryEnabled(), _v$9 = telemetryEnabled();
8398
+ _v$ !== _p$.e && _el$.classList.toggle("closing", _p$.e = _v$);
8399
+ _v$2 !== _p$.t && _el$36.classList.toggle("on", _p$.t = _v$2);
8400
+ _v$3 !== _p$.a && setAttribute(_el$36, "aria-checked", _p$.a = _v$3);
8401
+ _v$4 !== _p$.o && _el$39.classList.toggle("on", _p$.o = _v$4);
8402
+ _v$5 !== _p$.i && setAttribute(_el$39, "aria-checked", _p$.i = _v$5);
8403
+ _v$6 !== _p$.n && _el$43.classList.toggle("on", _p$.n = _v$6);
8404
+ _v$7 !== _p$.s && setAttribute(_el$43, "aria-checked", _p$.s = _v$7);
8405
+ _v$8 !== _p$.h && _el$93.classList.toggle("on", _p$.h = _v$8);
8406
+ _v$9 !== _p$.r && setAttribute(_el$93, "aria-checked", _p$.r = _v$9);
8360
8407
  return _p$;
8361
8408
  }, {
8362
8409
  e: void 0,
@@ -8366,7 +8413,8 @@ const Settings = () => {
8366
8413
  i: void 0,
8367
8414
  n: void 0,
8368
8415
  s: void 0,
8369
- h: void 0
8416
+ h: void 0,
8417
+ r: void 0
8370
8418
  });
8371
8419
  createRenderEffect(() => _el$20.value = defaultUrl());
8372
8420
  createRenderEffect(() => _el$23.value = mcpPort());
@@ -8391,6 +8439,9 @@ var _tmpl$$1 = /* @__PURE__ */ template(`<div class=command-bar-overlay><div cla
8391
8439
  inset 0 1px 0 rgba(255, 255, 255, 0.04);
8392
8440
  animation: command-bar-enter 350ms var(--ease-out-expo) both;
8393
8441
  }
8442
+ .command-bar-overlay.closing .keyboard-help {
8443
+ animation: command-bar-exit 200ms var(--ease-in-out) both;
8444
+ }
8394
8445
  .keyboard-help-header {
8395
8446
  display: flex;
8396
8447
  justify-content: space-between;
@@ -8487,9 +8538,13 @@ const SHORTCUTS = [{
8487
8538
  action: "This help overlay"
8488
8539
  }];
8489
8540
  const KeyboardHelp = (props) => {
8541
+ const {
8542
+ visible,
8543
+ closing
8544
+ } = useAnimatedPresence(() => props.open, 200);
8490
8545
  return createComponent(Show, {
8491
8546
  get when() {
8492
- return props.open;
8547
+ return visible();
8493
8548
  },
8494
8549
  get children() {
8495
8550
  return [(() => {
@@ -8510,6 +8565,7 @@ const KeyboardHelp = (props) => {
8510
8565
  insert(_el$9, () => s.action);
8511
8566
  return _el$9;
8512
8567
  })()]));
8568
+ createRenderEffect(() => _el$.classList.toggle("closing", !!closing()));
8513
8569
  return _el$;
8514
8570
  })(), _tmpl$2$1()];
8515
8571
  }
@@ -8589,28 +8645,7 @@ const App = () => {
8589
8645
  } = useTabs();
8590
8646
  const [highlightToast, setHighlightToast] = createSignal(null);
8591
8647
  const [keyboardHelpOpen, setKeyboardHelpOpen] = createSignal(false);
8592
- const captureHighlight = async () => {
8593
- try {
8594
- const result = await window.vessel.highlights.capture();
8595
- if (result.success && result.text) {
8596
- const preview = result.text.length > 60 ? result.text.slice(0, 57) + "..." : result.text;
8597
- setHighlightToast({
8598
- title: "Highlight saved",
8599
- message: preview
8600
- });
8601
- } else {
8602
- setHighlightToast({
8603
- title: "No selection",
8604
- message: result.message || "Select text on the page first, then press Ctrl+H"
8605
- });
8606
- }
8607
- } catch {
8608
- setHighlightToast({
8609
- title: "Highlight failed",
8610
- message: "Could not capture selection"
8611
- });
8612
- }
8613
- };
8648
+ const loadingPresence = useAnimatedPresence(() => !!activeTab()?.isLoading, 300);
8614
8649
  const showHighlightResult = (result) => {
8615
8650
  if (result.success && result.text) {
8616
8651
  const preview = result.text.length > 60 ? result.text.slice(0, 57) + "..." : result.text;
@@ -8625,6 +8660,17 @@ const App = () => {
8625
8660
  });
8626
8661
  }
8627
8662
  };
8663
+ const captureHighlight = async () => {
8664
+ try {
8665
+ const result = await window.vessel.highlights.capture();
8666
+ showHighlightResult(result);
8667
+ } catch {
8668
+ setHighlightToast({
8669
+ title: "Highlight failed",
8670
+ message: "Could not capture selection"
8671
+ });
8672
+ }
8673
+ };
8628
8674
  onMount(() => {
8629
8675
  if (view !== "chrome") return;
8630
8676
  const cleanupKeys = setupKeybindings({
@@ -8672,10 +8718,12 @@ const App = () => {
8672
8718
  insert(_el$2, createComponent(AddressBar, {}), null);
8673
8719
  insert(_el$2, createComponent(Show, {
8674
8720
  get when() {
8675
- return activeTab()?.isLoading;
8721
+ return loadingPresence.visible();
8676
8722
  },
8677
8723
  get children() {
8678
- return _tmpl$();
8724
+ var _el$3 = _tmpl$();
8725
+ createRenderEffect(() => _el$3.classList.toggle("closing", !!loadingPresence.closing()));
8726
+ return _el$3;
8679
8727
  }
8680
8728
  }), null);
8681
8729
  insert(_el$, createComponent(CommandBar, {}), null);
@@ -177,6 +177,24 @@
177
177
  }
178
178
  }
179
179
 
180
+ .tab-item.closing {
181
+ animation: tab-close 200ms var(--ease-in-out) both;
182
+ pointer-events: none;
183
+ }
184
+
185
+ @keyframes tab-close {
186
+ from {
187
+ max-width: 200px;
188
+ padding: 0 12px;
189
+ opacity: 1;
190
+ }
191
+ to {
192
+ max-width: 0;
193
+ padding: 0;
194
+ opacity: 0;
195
+ }
196
+ }
197
+
180
198
  .tab-item:hover {
181
199
  background: rgba(255, 255, 255, 0.04);
182
200
  color: var(--text-secondary);
@@ -585,17 +603,6 @@
585
603
  animation: badge-enter var(--duration-enter) var(--ease-out-back) both;
586
604
  }
587
605
 
588
- @keyframes badge-enter {
589
- from {
590
- opacity: 0;
591
- transform: scale(0);
592
- }
593
- to {
594
- opacity: 1;
595
- transform: scale(1);
596
- }
597
- }
598
-
599
606
 
600
607
  /* ═══════════════════════════════════════
601
608
  Bookmark toast notifications — animated
@@ -941,6 +948,30 @@
941
948
  }
942
949
  }
943
950
 
951
+ @keyframes command-bar-exit {
952
+ from {
953
+ opacity: 1;
954
+ transform: translateY(0) scale(1);
955
+ }
956
+ to {
957
+ opacity: 0;
958
+ transform: translateY(-12px) scale(0.97);
959
+ }
960
+ }
961
+
962
+ @keyframes overlay-fade-out {
963
+ from { opacity: 1; }
964
+ to { opacity: 0; }
965
+ }
966
+
967
+ .command-bar-overlay.closing {
968
+ animation: overlay-fade-out 200ms var(--ease-in-out) both;
969
+ }
970
+
971
+ .command-bar-overlay.closing .command-bar {
972
+ animation: command-bar-exit 200ms var(--ease-in-out) both;
973
+ }
974
+
944
975
  .command-bar form {
945
976
  display: flex;
946
977
  align-items: center;
@@ -1307,17 +1338,6 @@
1307
1338
  animation: badge-enter var(--duration-enter) var(--ease-out-back) both;
1308
1339
  }
1309
1340
 
1310
- @keyframes badge-enter {
1311
- from {
1312
- opacity: 0;
1313
- transform: scale(0);
1314
- }
1315
- to {
1316
- opacity: 1;
1317
- transform: scale(1);
1318
- }
1319
- }
1320
-
1321
1341
  .sidebar-messages {
1322
1342
  flex: 1;
1323
1343
  overflow-y: auto;
@@ -4543,6 +4563,11 @@ button:active:not(:disabled) {
4543
4563
  loading-bar-enter var(--duration-normal) var(--ease-out-expo) both,
4544
4564
  loading-bar-shimmer 1.6s ease-in-out infinite;
4545
4565
  z-index: 10;
4566
+ transition: opacity 300ms var(--ease-in-out);
4567
+ }
4568
+
4569
+ .loading-bar.closing {
4570
+ opacity: 0;
4546
4571
  }
4547
4572
 
4548
4573
  @keyframes loading-bar-enter {
@@ -4677,3 +4702,14 @@ button:active:not(:disabled) {
4677
4702
  [data-tooltip-pos="left"]:hover::after {
4678
4703
  transform: translateY(-50%) translateX(0);
4679
4704
  }
4705
+
4706
+ @keyframes badge-enter {
4707
+ from {
4708
+ opacity: 0;
4709
+ transform: scale(0);
4710
+ }
4711
+ to {
4712
+ opacity: 1;
4713
+ transform: scale(1);
4714
+ }
4715
+ }
@@ -5,8 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; font-src 'self' data:;" />
7
7
  <title>Vessel</title>
8
- <script type="module" crossorigin src="./assets/index-BnUB1gZc.js"></script>
9
- <link rel="stylesheet" crossorigin href="./assets/index-Ct7z7yP_.css">
8
+ <script type="module" crossorigin src="./assets/index-DVD9XuhC.js"></script>
9
+ <link rel="stylesheet" crossorigin href="./assets/index-eS3ccAls.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@quanta-intellect/vessel-browser",
3
3
  "mcpName": "io.github.unmodeled-tyler/vessel-browser",
4
- "version": "0.1.29",
4
+ "version": "0.1.31",
5
5
  "description": "AI-native web browser for Linux — persistent browser runtime for autonomous agents with human supervision",
6
6
  "main": "./out/main/index.js",
7
7
  "bin": {
@@ -16,7 +16,7 @@
16
16
  "README.md"
17
17
  ],
18
18
  "scripts": {
19
- "dev": "electron-vite dev",
19
+ "dev": "ELECTRON_DISABLE_SANDBOX=1 electron-vite dev",
20
20
  "build": "electron-vite build",
21
21
  "dist": "npm run build && electron-builder --linux --publish never",
22
22
  "dist:dir": "npm run build && electron-builder --linux dir --publish never",
@@ -24,6 +24,7 @@
24
24
  "typecheck": "tsc --noEmit",
25
25
  "prepublishOnly": "npm run build",
26
26
  "test:navigation-regression": "env -u ELECTRON_RUN_AS_NODE ELECTRON_DISABLE_SANDBOX=1 electron --no-sandbox --disable-setuid-sandbox scripts/run-navigation-regression.mjs",
27
+ "test:mcp-proxy": "node scripts/test-mcp-stdio-proxy.mjs",
27
28
  "smoke:test": "node scripts/smoke-test.mjs",
28
29
  "test:coverage": "c8 --reporter=text --reporter=html tsx --test tests/*.test.ts"
29
30
  },