@quanta-intellect/vessel-browser 0.1.81 → 0.1.84

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
@@ -1,5 +1,5 @@
1
1
  <div align="center">
2
-
2
+
3
3
  ![quanta-intellect-logo-transparent](https://cdn-uploads.huggingface.co/production/uploads/686c460ba3fc457ad14ab6f8/gB6J60f9Yeyb3Thop2dUa.png)
4
4
 
5
5
  <a href="https://snapcraft.io/vessel-browser">
@@ -22,11 +22,9 @@ Vessel gives external agent harnesses a real browser with durable state, MCP con
22
22
  *Vessel is in active development and currently makes no security assurances. Use and deploy it with care.*
23
23
 
24
24
 
25
-
26
25
  https://github.com/user-attachments/assets/0a72b48a-873a-4eb0-b8f2-23e34d8472c4
27
26
 
28
27
 
29
-
30
28
  ## Quick Start
31
29
 
32
30
  Want the full agent toolkit from day one? [Start a 7-Day Free Trial of Vessel Premium — $5.99/mo](https://vesselpremium.quantaintellect.com/checkout).
@@ -80,7 +78,6 @@ Most browser automation stacks are either headless, stateless, or designed aroun
80
78
  <img width="1280" height="800" alt="@quanta-intellectvessel-browser_2026-03-17_195754_6624" src="https://github.com/user-attachments/assets/3b3d2033-5a59-4806-bbc1-359efb7b43a9" />
81
79
 
82
80
 
83
-
84
81
  <img width="1280" height="800" alt="vessel_2026-03-17_145154_5389" src="https://github.com/user-attachments/assets/b1c08d6c-bcdf-4c9a-8429-a71a23a61903" />
85
82
 
86
83
  Vessel is built for persistent web agents that need a real browser, durable state, and a human-visible interface. The agent is the primary operator. The human follows along in the live browser UI, audits what the agent is doing, and steers when needed.
@@ -420,7 +417,7 @@ mcp_servers:
420
417
  connect_timeout: 30
421
418
  ```
422
419
 
423
- ## Configuration
420
+ ## Configuration
424
421
 
425
422
  The installer writes three snippets to:
426
423
 
package/out/main/index.js CHANGED
@@ -183,6 +183,15 @@ function sanitizePort(value) {
183
183
  });
184
184
  return defaults.mcpPort;
185
185
  }
186
+ function sanitizeReasoningEffortLevel$1(value) {
187
+ return value === "low" || value === "medium" || value === "high" || value === "max" || value === "off" ? value : "off";
188
+ }
189
+ function sanitizeChatProvider(provider) {
190
+ return provider ? {
191
+ ...provider,
192
+ reasoningEffort: sanitizeReasoningEffortLevel$1(provider.reasoningEffort)
193
+ } : null;
194
+ }
186
195
  function loadSettings() {
187
196
  if (settings) return settings;
188
197
  settingsIssues = [];
@@ -194,7 +203,9 @@ function loadSettings() {
194
203
  settings = {
195
204
  ...defaults,
196
205
  ...parsed,
197
- chatProvider: mergeChatProviderSecret(parsed.chatProvider ?? null),
206
+ chatProvider: sanitizeChatProvider(
207
+ mergeChatProviderSecret(parsed.chatProvider ?? null)
208
+ ),
198
209
  mcpPort: sanitizePort(parsed.mcpPort ?? defaults.mcpPort),
199
210
  agentTranscriptMode: parsed.agentTranscriptMode === "off" || parsed.agentTranscriptMode === "summary" || parsed.agentTranscriptMode === "full" ? parsed.agentTranscriptMode : parsed.showAgentTranscript === false ? "off" : defaults.agentTranscriptMode
200
211
  };
@@ -260,7 +271,10 @@ function setSetting(key, value) {
260
271
  settings.chatProvider = {
261
272
  ...nextProvider,
262
273
  apiKey: resolvedApiKey,
263
- hasApiKey: Boolean(resolvedApiKey)
274
+ hasApiKey: Boolean(resolvedApiKey),
275
+ reasoningEffort: sanitizeReasoningEffortLevel$1(
276
+ nextProvider.reasoningEffort
277
+ )
264
278
  };
265
279
  }
266
280
  } else {
@@ -3284,6 +3298,7 @@ const Channels = {
3284
3298
  FOLDER_CREATE: "bookmarks:folder-create",
3285
3299
  FOLDER_REMOVE: "bookmarks:folder-remove",
3286
3300
  FOLDER_RENAME: "bookmarks:folder-rename",
3301
+ FOLDER_EXPORT_HTML: "bookmarks:folder-export-html",
3287
3302
  // Highlights
3288
3303
  HIGHLIGHT_CAPTURE: "highlights:capture",
3289
3304
  HIGHLIGHT_CAPTURE_RESULT: "highlights:capture-result",
@@ -6818,17 +6833,38 @@ function isClickReadLoop(names) {
6818
6833
  }
6819
6834
  return clickReadPairs >= 2;
6820
6835
  }
6836
+ const ANTHROPIC_MAX_TOKENS = 4096;
6821
6837
  function isRecord(value) {
6822
6838
  return value !== null && typeof value === "object" && !Array.isArray(value);
6823
6839
  }
6840
+ function anthropicModelLikelySupportsThinking(model) {
6841
+ return /claude-(?:opus|sonnet|haiku)-4/i.test(model.trim());
6842
+ }
6843
+ function toAnthropicThinkingConfig(effort, model) {
6844
+ if (!effort || effort === "off" || !anthropicModelLikelySupportsThinking(model)) {
6845
+ return void 0;
6846
+ }
6847
+ const budgetByEffort = {
6848
+ low: 1024,
6849
+ medium: 2048,
6850
+ high: 3072,
6851
+ max: 3584
6852
+ };
6853
+ return {
6854
+ type: "enabled",
6855
+ budget_tokens: budgetByEffort[effort]
6856
+ };
6857
+ }
6824
6858
  class AnthropicProvider {
6825
6859
  agentToolProfile = "default";
6826
6860
  client;
6827
6861
  model;
6862
+ reasoningEffort;
6828
6863
  abortController = null;
6829
- constructor(apiKey, model) {
6864
+ constructor(apiKey, model, reasoningEffort = "off") {
6830
6865
  this.client = new Anthropic({ apiKey });
6831
6866
  this.model = model || "claude-sonnet-4-20250514";
6867
+ this.reasoningEffort = reasoningEffort;
6832
6868
  }
6833
6869
  async streamQuery(systemPrompt, userMessage, onChunk, onEnd, history) {
6834
6870
  this.abortController = new AbortController();
@@ -6836,13 +6872,15 @@ class AnthropicProvider {
6836
6872
  ...(history ?? []).map((m) => ({ role: m.role, content: m.content })),
6837
6873
  { role: "user", content: userMessage }
6838
6874
  ];
6875
+ const thinking = toAnthropicThinkingConfig(this.reasoningEffort, this.model);
6839
6876
  try {
6840
6877
  const stream = this.client.messages.stream(
6841
6878
  {
6842
6879
  model: this.model,
6843
- max_tokens: 4096,
6880
+ max_tokens: ANTHROPIC_MAX_TOKENS,
6844
6881
  system: systemPrompt,
6845
- messages
6882
+ messages,
6883
+ ...thinking ? { thinking } : {}
6846
6884
  },
6847
6885
  { signal: this.abortController.signal }
6848
6886
  );
@@ -6868,6 +6906,7 @@ class AnthropicProvider {
6868
6906
  ...(history ?? []).map((m) => ({ role: m.role, content: m.content })),
6869
6907
  { role: "user", content: userMessage }
6870
6908
  ];
6909
+ const thinking = toAnthropicThinkingConfig(this.reasoningEffort, this.model);
6871
6910
  try {
6872
6911
  const maxIterations = getEffectiveMaxIterations();
6873
6912
  let iterationsUsed = 0;
@@ -6878,10 +6917,11 @@ class AnthropicProvider {
6878
6917
  const stream = this.client.messages.stream(
6879
6918
  {
6880
6919
  model: this.model,
6881
- max_tokens: 4096,
6920
+ max_tokens: ANTHROPIC_MAX_TOKENS,
6882
6921
  system: systemPrompt,
6883
6922
  messages,
6884
- tools
6923
+ tools,
6924
+ ...thinking ? { thinking } : {}
6885
6925
  },
6886
6926
  { signal: this.abortController.signal }
6887
6927
  );
@@ -6942,6 +6982,20 @@ class AnthropicProvider {
6942
6982
  }
6943
6983
  const finalMessage = await stream.finalMessage();
6944
6984
  const assistantContent = [];
6985
+ for (const block of finalMessage.content) {
6986
+ if (block.type === "thinking") {
6987
+ assistantContent.push({
6988
+ type: "thinking",
6989
+ thinking: block.thinking,
6990
+ signature: block.signature
6991
+ });
6992
+ } else if (block.type === "redacted_thinking") {
6993
+ assistantContent.push({
6994
+ type: "redacted_thinking",
6995
+ data: block.data
6996
+ });
6997
+ }
6998
+ }
6945
6999
  if (textContent) {
6946
7000
  assistantContent.push({ type: "text", text: textContent });
6947
7001
  }
@@ -7265,6 +7319,30 @@ function toOpenAITools(tools) {
7265
7319
  function agentTemperatureForProfile(profile) {
7266
7320
  return profile === "compact" ? 0.2 : void 0;
7267
7321
  }
7322
+ function modelLikelySupportsOpenAIReasoningEffort(model) {
7323
+ return /^(?:o\d|o[1-9]|gpt-5|codex|computer-use)/i.test(model.trim());
7324
+ }
7325
+ function toOpenAIReasoningEffort(effort, providerId, model) {
7326
+ const supportsReasoningParam = providerId === "openrouter" || providerId === "custom" || providerId === "openai" && modelLikelySupportsOpenAIReasoningEffort(model);
7327
+ if (!supportsReasoningParam) return void 0;
7328
+ switch (effort) {
7329
+ case "off":
7330
+ if (providerId === "openai" && !/^gpt-5\.1/i.test(model.trim())) {
7331
+ return void 0;
7332
+ }
7333
+ return "none";
7334
+ case "low":
7335
+ return "low";
7336
+ case "medium":
7337
+ return "medium";
7338
+ case "high":
7339
+ return "high";
7340
+ case "max":
7341
+ return "xhigh";
7342
+ default:
7343
+ return void 0;
7344
+ }
7345
+ }
7268
7346
  function followUpReminderForProfile(profile, userMessage, assistantText, latestToolResultPreview) {
7269
7347
  if (profile !== "compact") return null;
7270
7348
  const phaseReminder = buildPhaseReminder(userMessage, assistantText || "");
@@ -7875,6 +7953,7 @@ class OpenAICompatProvider {
7875
7953
  client;
7876
7954
  model;
7877
7955
  providerId;
7956
+ reasoningEffort;
7878
7957
  abortController = null;
7879
7958
  constructor(config) {
7880
7959
  const meta = PROVIDERS[config.id];
@@ -7892,6 +7971,7 @@ class OpenAICompatProvider {
7892
7971
  });
7893
7972
  this.providerId = config.id;
7894
7973
  this.model = config.model || meta?.defaultModel || "gpt-4o";
7974
+ this.reasoningEffort = config.reasoningEffort ?? "off";
7895
7975
  this.agentToolProfile = resolveAgentToolProfile(config);
7896
7976
  }
7897
7977
  async streamQuery(systemPrompt, userMessage, onChunk, onEnd, history) {
@@ -7901,13 +7981,19 @@ class OpenAICompatProvider {
7901
7981
  ...(history ?? []).map((m) => ({ role: m.role, content: m.content })),
7902
7982
  { role: "user", content: userMessage }
7903
7983
  ];
7984
+ const reasoningEffort = toOpenAIReasoningEffort(
7985
+ this.reasoningEffort,
7986
+ this.providerId,
7987
+ this.model
7988
+ );
7904
7989
  try {
7905
7990
  const stream = await this.client.chat.completions.create(
7906
7991
  {
7907
7992
  model: this.model,
7908
7993
  max_tokens: 4096,
7909
7994
  stream: true,
7910
- messages
7995
+ messages,
7996
+ ...reasoningEffort ? { reasoning_effort: reasoningEffort } : {}
7911
7997
  },
7912
7998
  { signal: this.abortController.signal }
7913
7999
  );
@@ -7945,6 +8031,11 @@ class OpenAICompatProvider {
7945
8031
  ...(history ?? []).map((m) => ({ role: m.role, content: m.content })),
7946
8032
  { role: "user", content: userMessage }
7947
8033
  ];
8034
+ const reasoningEffort = toOpenAIReasoningEffort(
8035
+ this.reasoningEffort,
8036
+ this.providerId,
8037
+ this.model
8038
+ );
7948
8039
  try {
7949
8040
  const maxIterations = getEffectiveMaxIterations();
7950
8041
  let iterationsUsed = 0;
@@ -7972,7 +8063,8 @@ class OpenAICompatProvider {
7972
8063
  messages,
7973
8064
  tools: openAITools,
7974
8065
  tool_choice: "auto",
7975
- temperature: agentTemperatureForProfile(this.agentToolProfile)
8066
+ temperature: agentTemperatureForProfile(this.agentToolProfile),
8067
+ ...reasoningEffort ? { reasoning_effort: reasoningEffort } : {}
7976
8068
  },
7977
8069
  { signal: this.abortController.signal }
7978
8070
  );
@@ -8267,9 +8359,13 @@ function sanitizeProviderConfig(config) {
8267
8359
  ...config,
8268
8360
  apiKey: config.apiKey.trim(),
8269
8361
  model: config.model.trim(),
8270
- baseUrl: config.baseUrl?.trim() || void 0
8362
+ baseUrl: config.baseUrl?.trim() || void 0,
8363
+ reasoningEffort: sanitizeReasoningEffortLevel(config.reasoningEffort)
8271
8364
  };
8272
8365
  }
8366
+ function sanitizeReasoningEffortLevel(value) {
8367
+ return value === "low" || value === "medium" || value === "high" || value === "max" || value === "off" ? value : "off";
8368
+ }
8273
8369
  function validateProviderConfig(config) {
8274
8370
  return validateProviderConnection(config, { requireModel: true });
8275
8371
  }
@@ -8371,7 +8467,11 @@ function createProvider(config) {
8371
8467
  throw new Error(error);
8372
8468
  }
8373
8469
  if (normalized.id === "anthropic") {
8374
- return new AnthropicProvider(normalized.apiKey, normalized.model);
8470
+ return new AnthropicProvider(
8471
+ normalized.apiKey,
8472
+ normalized.model,
8473
+ normalized.reasoningEffort
8474
+ );
8375
8475
  }
8376
8476
  return new OpenAICompatProvider(normalized);
8377
8477
  }
@@ -11759,6 +11859,41 @@ function exportBookmarksHtml(options = {}) {
11759
11859
  return `${lines.join("\n")}
11760
11860
  `;
11761
11861
  }
11862
+ function exportBookmarkFolderHtml(folderId, options = {}) {
11863
+ const current = getState();
11864
+ const folder = current.folders.find((item) => item.id === folderId);
11865
+ if (!folder) return null;
11866
+ const resolvedOptions = {
11867
+ includeNotes: options.includeNotes ?? false
11868
+ };
11869
+ const now = Math.floor(Date.now() / 1e3);
11870
+ const items = current.bookmarks.filter(
11871
+ (bookmark) => bookmark.folderId === folder.id
11872
+ );
11873
+ const addDate = toNetscapeTimestamp(folder.createdAt) || now;
11874
+ const lines = [
11875
+ NETSCAPE_BOOKMARKS_DOCTYPE,
11876
+ '<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">',
11877
+ `<TITLE>${escapeBookmarkHtml(folder.name)}</TITLE>`,
11878
+ `<H1>${escapeBookmarkHtml(folder.name)}</H1>`,
11879
+ "<DL><p>",
11880
+ ` <DT><H3 ADD_DATE="${addDate}" LAST_MODIFIED="${now}">${escapeBookmarkHtml(folder.name)}</H3>`
11881
+ ];
11882
+ if (resolvedOptions.includeNotes && folder.summary) {
11883
+ lines.push(` <DD>${escapeBookmarkHtml(folder.summary)}`);
11884
+ }
11885
+ lines.push(" <DL><p>");
11886
+ for (const bookmark of items) {
11887
+ appendBookmarkHtml(lines, bookmark, resolvedOptions, " ");
11888
+ }
11889
+ lines.push(" </DL><p>", "</DL><p>");
11890
+ return {
11891
+ content: `${lines.join("\n")}
11892
+ `,
11893
+ count: items.length,
11894
+ folderName: folder.name
11895
+ };
11896
+ }
11762
11897
  function exportBookmarksJson() {
11763
11898
  return `${JSON.stringify(getState(), null, 2)}
11764
11899
  `;
@@ -24750,6 +24885,10 @@ function createSecondaryWindow() {
24750
24885
  win.show();
24751
24886
  return state2;
24752
24887
  }
24888
+ function getSafeBookmarkExportName(name) {
24889
+ const safeName = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
24890
+ return safeName || "folder";
24891
+ }
24753
24892
  function registerBookmarkHandlers() {
24754
24893
  electron.ipcMain.handle(Channels.BOOKMARKS_GET, () => {
24755
24894
  return getState();
@@ -24828,6 +24967,29 @@ function registerBookmarkHandlers() {
24828
24967
  count: getState().bookmarks.length
24829
24968
  };
24830
24969
  });
24970
+ electron.ipcMain.handle(
24971
+ Channels.FOLDER_EXPORT_HTML,
24972
+ async (_, folderId, options) => {
24973
+ const folder = getFolder(folderId);
24974
+ if (!folder) return null;
24975
+ const { canceled, filePath } = await electron.dialog.showSaveDialog({
24976
+ title: `Export ${folder.name}`,
24977
+ defaultPath: `vessel-bookmarks-${getSafeBookmarkExportName(folder.name)}.html`,
24978
+ filters: [{ name: "HTML Bookmarks", extensions: ["html"] }]
24979
+ });
24980
+ if (canceled || !filePath) return null;
24981
+ const result = exportBookmarkFolderHtml(folderId, {
24982
+ includeNotes: options?.includeNotes ?? true
24983
+ });
24984
+ if (!result) return null;
24985
+ await fs.promises.writeFile(filePath, result.content, "utf-8");
24986
+ trackBookmarkAction("export");
24987
+ return {
24988
+ filePath,
24989
+ count: result.count
24990
+ };
24991
+ }
24992
+ );
24831
24993
  electron.ipcMain.handle(Channels.BOOKMARKS_IMPORT_HTML, async () => {
24832
24994
  const { canceled, filePaths } = await electron.dialog.showOpenDialog({
24833
24995
  title: "Import Bookmarks",
@@ -67,6 +67,7 @@ const Channels = {
67
67
  FOLDER_CREATE: "bookmarks:folder-create",
68
68
  FOLDER_REMOVE: "bookmarks:folder-remove",
69
69
  FOLDER_RENAME: "bookmarks:folder-rename",
70
+ FOLDER_EXPORT_HTML: "bookmarks:folder-export-html",
70
71
  // Highlights
71
72
  HIGHLIGHT_CAPTURE: "highlights:capture",
72
73
  HIGHLIGHT_CAPTURE_RESULT: "highlights:capture-result",
@@ -359,6 +360,7 @@ const api = {
359
360
  removeBookmark: (id) => electron.ipcRenderer.invoke(Channels.BOOKMARK_REMOVE, id),
360
361
  exportHtml: (options) => electron.ipcRenderer.invoke(Channels.BOOKMARKS_EXPORT_HTML, options),
361
362
  exportJson: () => electron.ipcRenderer.invoke(Channels.BOOKMARKS_EXPORT_JSON),
363
+ exportFolderHtml: (folderId, options) => electron.ipcRenderer.invoke(Channels.FOLDER_EXPORT_HTML, folderId, options),
362
364
  importHtml: () => electron.ipcRenderer.invoke(Channels.BOOKMARKS_IMPORT_HTML),
363
365
  importJson: () => electron.ipcRenderer.invoke(Channels.BOOKMARKS_IMPORT_JSON),
364
366
  createFolder: (name) => electron.ipcRenderer.invoke(Channels.FOLDER_CREATE, name),