@quanta-intellect/vessel-browser 0.1.19 → 0.1.20

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
@@ -362,7 +362,7 @@ class Tab {
362
362
  get state() {
363
363
  return { ...this._state };
364
364
  }
365
- navigate(url) {
365
+ navigate(url, postBody) {
366
366
  if (!/^https?:\/\//i.test(url) && !url.startsWith("about:")) {
367
367
  if (url.includes(".") && !url.includes(" ")) {
368
368
  url = "https://" + url;
@@ -375,7 +375,24 @@ class Tab {
375
375
  }
376
376
  const policyError = checkDomainPolicy(url);
377
377
  if (policyError) return policyError;
378
- this.view.webContents.loadURL(url);
378
+ if (postBody) {
379
+ const params = new URLSearchParams();
380
+ for (const [key, value] of Object.entries(postBody)) {
381
+ params.set(key, value);
382
+ }
383
+ this.view.webContents.loadURL(url, {
384
+ method: "POST",
385
+ extraHeaders: "Content-Type: application/x-www-form-urlencoded\r\n",
386
+ postData: [
387
+ {
388
+ type: "rawData",
389
+ bytes: Buffer.from(params.toString())
390
+ }
391
+ ]
392
+ });
393
+ } else {
394
+ this.view.webContents.loadURL(url);
395
+ }
379
396
  return null;
380
397
  }
381
398
  goBack() {
@@ -2037,9 +2054,10 @@ class TabManager {
2037
2054
  this.broadcastState();
2038
2055
  }
2039
2056
  }
2040
- navigateTab(id, url) {
2057
+ navigateTab(id, url, postBody) {
2041
2058
  const tab = this.tabs.get(id);
2042
- if (tab) tab.navigate(url);
2059
+ if (!tab) return `No tab with id ${id}`;
2060
+ return tab.navigate(url, postBody);
2043
2061
  }
2044
2062
  goBack(id) {
2045
2063
  return this.tabs.get(id)?.goBack() ?? false;
@@ -7137,9 +7155,12 @@ const TOOL_DEFINITIONS = [
7137
7155
  {
7138
7156
  name: "navigate",
7139
7157
  title: "Navigate",
7140
- description: "Navigate the browser to a URL.",
7158
+ description: "Navigate the browser to a URL. Use postBody to submit data via POST request (e.g. form submissions).",
7141
7159
  inputSchema: {
7142
- url: zod.z.string().describe("The URL to navigate to")
7160
+ url: zod.z.string().describe("The URL to navigate to"),
7161
+ postBody: zod.z.record(zod.z.string(), zod.z.string()).optional().describe(
7162
+ "Optional form fields to submit via POST (application/x-www-form-urlencoded). Only supported on http/https URLs."
7163
+ )
7143
7164
  },
7144
7165
  tier: 0
7145
7166
  },
@@ -11418,7 +11439,7 @@ async function executeAction(name, args, ctx) {
11418
11439
  const createdId = ctx.tabManager.createTab(
11419
11440
  typeof args.url === "string" && args.url.trim() ? args.url.trim() : "about:blank"
11420
11441
  );
11421
- const created = ctx.tabManager.getActiveTab();
11442
+ const created = ctx.tabManager.getTab(createdId);
11422
11443
  if (created) {
11423
11444
  await waitForLoad$1(created.view.webContents);
11424
11445
  return `Created tab ${createdId}${await getPostNavSummary(created.view.webContents)}`;
@@ -11431,7 +11452,8 @@ async function executeAction(name, args, ctx) {
11431
11452
  if (navValidation.status === "dead") {
11432
11453
  return `Navigation blocked: ${args.url} returned ${navValidation.detail || "dead link"}. Try a different URL or go back and choose another link.`;
11433
11454
  }
11434
- ctx.tabManager.navigateTab(tabId, args.url);
11455
+ const navError = ctx.tabManager.navigateTab(tabId, args.url, args.postBody);
11456
+ if (navError) return navError;
11435
11457
  await waitForLoad$1(wc);
11436
11458
  return `Navigated to ${wc.getURL()}${await getPostNavSummary(wc)}`;
11437
11459
  }
@@ -15175,10 +15197,15 @@ ${buildScopedContext(pageContent, mode)}`;
15175
15197
  "vessel_navigate",
15176
15198
  {
15177
15199
  title: "Navigate",
15178
- description: "Navigate the active browser tab to a URL.",
15179
- inputSchema: { url: zod.z.string().describe("The URL to navigate to") }
15200
+ description: "Navigate the active browser tab to a URL. Use postBody to submit data via POST request (e.g. form submissions).",
15201
+ inputSchema: {
15202
+ url: zod.z.string().describe("The URL to navigate to"),
15203
+ postBody: zod.z.record(zod.z.string(), zod.z.string()).optional().describe(
15204
+ "Optional form fields to submit via POST (application/x-www-form-urlencoded). Only supported on http/https URLs."
15205
+ )
15206
+ }
15180
15207
  },
15181
- async ({ url }) => {
15208
+ async ({ url, postBody }) => {
15182
15209
  const tab = tabManager.getActiveTab();
15183
15210
  if (!tab) return asTextResponse("Error: No active tab");
15184
15211
  const preCheck = await validateLinkDestination(url);
@@ -15189,7 +15216,8 @@ ${buildScopedContext(pageContent, mode)}`;
15189
15216
  }
15190
15217
  return withAction(runtime2, tabManager, "navigate", { url }, async () => {
15191
15218
  const id = tabManager.getActiveTabId();
15192
- tabManager.navigateTab(id, url);
15219
+ const navError = tabManager.navigateTab(id, url, postBody);
15220
+ if (navError) return navError;
15193
15221
  const { httpStatus } = await waitForLoadWithStatus(
15194
15222
  tab.view.webContents
15195
15223
  );
@@ -18235,11 +18263,14 @@ function registerIpcHandlers(windowState, runtime2) {
18235
18263
  tabManager.switchTab(id);
18236
18264
  layoutViews(windowState);
18237
18265
  });
18238
- electron.ipcMain.handle(Channels.TAB_NAVIGATE, (_, id, url) => {
18239
- assertString(id, "tabId");
18240
- assertString(url, "url");
18241
- tabManager.navigateTab(id, url);
18242
- });
18266
+ electron.ipcMain.handle(
18267
+ Channels.TAB_NAVIGATE,
18268
+ (_, id, url, postBody) => {
18269
+ assertString(id, "tabId");
18270
+ assertString(url, "url");
18271
+ return tabManager.navigateTab(id, url, postBody);
18272
+ }
18273
+ );
18243
18274
  electron.ipcMain.handle(Channels.TAB_BACK, (_, id) => {
18244
18275
  tabManager.goBack(id);
18245
18276
  });
@@ -19290,13 +19321,70 @@ function installDownloadHandler(chromeView) {
19290
19321
  });
19291
19322
  });
19292
19323
  }
19293
- let runtime = null;
19324
+ function registerHighlightShortcut(mainWindow, tabManager) {
19325
+ const register = () => {
19326
+ electron.globalShortcut.unregister("CommandOrControl+H");
19327
+ const success = electron.globalShortcut.register("CommandOrControl+H", () => {
19328
+ const activeTab = tabManager.getActiveTab();
19329
+ if (!activeTab) return;
19330
+ tabManager.captureHighlightFromActiveTab();
19331
+ });
19332
+ if (!success) {
19333
+ console.warn("[Vessel] Failed to register Ctrl+H shortcut");
19334
+ }
19335
+ };
19336
+ register();
19337
+ mainWindow.on("focus", register);
19338
+ return () => {
19339
+ electron.globalShortcut.unregister("CommandOrControl+H");
19340
+ mainWindow.removeListener("focus", register);
19341
+ };
19342
+ }
19343
+ function setupAppMenu() {
19344
+ const appMenu = electron.Menu.buildFromTemplate([
19345
+ {
19346
+ label: "Edit",
19347
+ submenu: [
19348
+ { role: "undo" },
19349
+ { role: "redo" },
19350
+ { type: "separator" },
19351
+ { role: "cut" },
19352
+ { role: "copy" },
19353
+ { role: "paste" },
19354
+ { role: "selectAll" }
19355
+ ]
19356
+ }
19357
+ ]);
19358
+ electron.Menu.setApplicationMenu(appMenu);
19359
+ }
19294
19360
  function rendererUrlFor(view) {
19295
19361
  if (!process.env.ELECTRON_RENDERER_URL) return null;
19296
19362
  const url = new URL(process.env.ELECTRON_RENDERER_URL);
19297
19363
  url.searchParams.set("view", view);
19298
19364
  return url.toString();
19299
19365
  }
19366
+ function loadRenderers(chromeView, sidebarView, devtoolsPanelView) {
19367
+ const chromeUrl = rendererUrlFor("chrome");
19368
+ const sidebarUrl = rendererUrlFor("sidebar");
19369
+ const devtoolsUrl = rendererUrlFor("devtools");
19370
+ if (chromeUrl && sidebarUrl && devtoolsUrl) {
19371
+ chromeView.webContents.loadURL(chromeUrl);
19372
+ sidebarView.webContents.loadURL(sidebarUrl);
19373
+ devtoolsPanelView.webContents.loadURL(devtoolsUrl);
19374
+ } else {
19375
+ const rendererFile = path$1.join(__dirname, "../../renderer/index.html");
19376
+ chromeView.webContents.loadFile(rendererFile, {
19377
+ query: { view: "chrome" }
19378
+ });
19379
+ sidebarView.webContents.loadFile(rendererFile, {
19380
+ query: { view: "sidebar" }
19381
+ });
19382
+ devtoolsPanelView.webContents.loadFile(rendererFile, {
19383
+ query: { view: "devtools" }
19384
+ });
19385
+ }
19386
+ }
19387
+ let runtime = null;
19300
19388
  function checkWritableUserData(userDataPath) {
19301
19389
  const issues = [];
19302
19390
  try {
@@ -19389,34 +19477,8 @@ async function bootstrap() {
19389
19477
  }
19390
19478
  });
19391
19479
  registerIpcHandlers(windowState, runtime);
19392
- const registerHighlightShortcut = () => {
19393
- electron.globalShortcut.unregister("CommandOrControl+H");
19394
- const success = electron.globalShortcut.register("CommandOrControl+H", () => {
19395
- const activeTab = tabManager.getActiveTab();
19396
- if (!activeTab) return;
19397
- tabManager.captureHighlightFromActiveTab();
19398
- });
19399
- if (!success) {
19400
- console.warn("[Vessel] Failed to register Ctrl+H shortcut");
19401
- }
19402
- };
19403
- registerHighlightShortcut();
19404
- windowState.mainWindow.on("focus", registerHighlightShortcut);
19405
- const appMenu = electron.Menu.buildFromTemplate([
19406
- {
19407
- label: "Edit",
19408
- submenu: [
19409
- { role: "undo" },
19410
- { role: "redo" },
19411
- { type: "separator" },
19412
- { role: "cut" },
19413
- { role: "copy" },
19414
- { role: "paste" },
19415
- { role: "selectAll" }
19416
- ]
19417
- }
19418
- ]);
19419
- electron.Menu.setApplicationMenu(appMenu);
19480
+ registerHighlightShortcut(windowState.mainWindow, tabManager);
19481
+ setupAppMenu();
19420
19482
  subscribe((state2) => {
19421
19483
  chromeView.webContents.send(Channels.BOOKMARKS_UPDATE, state2);
19422
19484
  sidebarView.webContents.send(Channels.BOOKMARKS_UPDATE, state2);
@@ -19428,25 +19490,7 @@ async function bootstrap() {
19428
19490
  installDownloadHandler(chromeView);
19429
19491
  startBackgroundRevalidation();
19430
19492
  startTelemetry();
19431
- const chromeUrl = rendererUrlFor("chrome");
19432
- const sidebarUrl = rendererUrlFor("sidebar");
19433
- const devtoolsUrl = rendererUrlFor("devtools");
19434
- if (chromeUrl && sidebarUrl && devtoolsUrl) {
19435
- chromeView.webContents.loadURL(chromeUrl);
19436
- sidebarView.webContents.loadURL(sidebarUrl);
19437
- devtoolsPanelView.webContents.loadURL(devtoolsUrl);
19438
- } else {
19439
- const rendererFile = path.join(__dirname, "../renderer/index.html");
19440
- chromeView.webContents.loadFile(rendererFile, {
19441
- query: { view: "chrome" }
19442
- });
19443
- sidebarView.webContents.loadFile(rendererFile, {
19444
- query: { view: "sidebar" }
19445
- });
19446
- devtoolsPanelView.webContents.loadFile(rendererFile, {
19447
- query: { view: "devtools" }
19448
- });
19449
- }
19493
+ loadRenderers(chromeView, sidebarView, devtoolsPanelView);
19450
19494
  await startMcpServer(tabManager, runtime, settings2.mcpPort);
19451
19495
  chromeView.webContents.once("did-finish-load", () => {
19452
19496
  const savedSession = runtime.getState().session;
@@ -19461,6 +19505,16 @@ async function bootstrap() {
19461
19505
  void maybeShowStartupHealthDialog(windowState);
19462
19506
  });
19463
19507
  }
19508
+ process.on("uncaughtException", (error) => {
19509
+ console.error("[Vessel] Uncaught exception:", error.message, error.stack);
19510
+ electron.app.quit();
19511
+ });
19512
+ process.on("unhandledRejection", (reason) => {
19513
+ console.error(
19514
+ "[Vessel] Unhandled rejection:",
19515
+ reason instanceof Error ? reason.message : reason
19516
+ );
19517
+ });
19464
19518
  electron.app.whenReady().then(bootstrap).catch((error) => {
19465
19519
  console.error("[Vessel] Failed to bootstrap application:", error);
19466
19520
  electron.app.quit();
@@ -97,7 +97,7 @@ const api = {
97
97
  create: (url) => electron.ipcRenderer.invoke(Channels.TAB_CREATE, url),
98
98
  close: (id) => electron.ipcRenderer.invoke(Channels.TAB_CLOSE, id),
99
99
  switch: (id) => electron.ipcRenderer.invoke(Channels.TAB_SWITCH, id),
100
- navigate: (id, url) => electron.ipcRenderer.invoke(Channels.TAB_NAVIGATE, id, url),
100
+ navigate: (id, url, postBody) => electron.ipcRenderer.invoke(Channels.TAB_NAVIGATE, id, url, postBody),
101
101
  back: (id) => electron.ipcRenderer.invoke(Channels.TAB_BACK, id),
102
102
  forward: (id) => electron.ipcRenderer.invoke(Channels.TAB_FORWARD, id),
103
103
  reload: (id) => electron.ipcRenderer.invoke(Channels.TAB_RELOAD, id),
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.19",
4
+ "version": "0.1.20",
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": {