@quanta-intellect/vessel-browser 0.1.12 → 0.1.13

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.
@@ -48,6 +48,7 @@ const Channels = {
48
48
  BOOKMARKS_UPDATE: "bookmarks:update",
49
49
  BOOKMARK_SAVE: "bookmarks:save",
50
50
  BOOKMARK_REMOVE: "bookmarks:remove",
51
+ BOOKMARK_ADD_CONTEXT_TO_CHAT: "bookmarks:add-context-to-chat",
51
52
  FOLDER_CREATE: "bookmarks:folder-create",
52
53
  FOLDER_REMOVE: "bookmarks:folder-remove",
53
54
  FOLDER_RENAME: "bookmarks:folder-rename",
@@ -168,6 +169,14 @@ const api = {
168
169
  createFolderWithSummary: (name, summary) => electron.ipcRenderer.invoke(Channels.FOLDER_CREATE, name, summary),
169
170
  removeFolder: (id) => electron.ipcRenderer.invoke(Channels.FOLDER_REMOVE, id),
170
171
  renameFolder: (id, newName, summary) => electron.ipcRenderer.invoke(Channels.FOLDER_RENAME, id, newName, summary),
172
+ onAddContextToChat: (cb) => {
173
+ const handler = (_, bookmarkId) => cb(bookmarkId);
174
+ electron.ipcRenderer.on(Channels.BOOKMARK_ADD_CONTEXT_TO_CHAT, handler);
175
+ return () => electron.ipcRenderer.removeListener(
176
+ Channels.BOOKMARK_ADD_CONTEXT_TO_CHAT,
177
+ handler
178
+ );
179
+ },
171
180
  onUpdate: (cb) => {
172
181
  const handler = (_, state) => cb(state);
173
182
  electron.ipcRenderer.on(Channels.BOOKMARKS_UPDATE, handler);
@@ -1587,6 +1587,157 @@ function useBookmarks() {
1587
1587
  renameFolder: (id, newName, summary) => window.vessel.bookmarks.renameFolder(id, newName, summary)
1588
1588
  };
1589
1589
  }
1590
+ const MEMORY_STORAGE_KEY = "vessel.bookmark-context.memories";
1591
+ const MAX_MEMORY_LINES = 4;
1592
+ const MAX_MEMORY_CHARS = 420;
1593
+ function collapseWhitespace(value) {
1594
+ return value.replace(/\s+/g, " ").trim();
1595
+ }
1596
+ function cleanSnippet(value, maxLength = 140) {
1597
+ const cleaned = collapseWhitespace(
1598
+ value.replace(/`+/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
1599
+ );
1600
+ if (cleaned.length <= maxLength) return cleaned;
1601
+ return `${cleaned.slice(0, maxLength - 1).trimEnd()}...`;
1602
+ }
1603
+ function dedupe(values) {
1604
+ const seen = /* @__PURE__ */ new Set();
1605
+ const result = [];
1606
+ for (const value of values) {
1607
+ const normalized = value.trim().toLowerCase();
1608
+ if (!normalized || seen.has(normalized)) continue;
1609
+ seen.add(normalized);
1610
+ result.push(value.trim());
1611
+ }
1612
+ return result;
1613
+ }
1614
+ function bookmarkMemoryKey(url) {
1615
+ try {
1616
+ return new URL(url).hostname.replace(/^www\./i, "").toLowerCase();
1617
+ } catch {
1618
+ return url.trim().toLowerCase();
1619
+ }
1620
+ }
1621
+ function tokenize(value) {
1622
+ return dedupe(
1623
+ value.toLowerCase().split(/[^a-z0-9]+/i).filter((token) => token.length >= 4).slice(0, 8)
1624
+ );
1625
+ }
1626
+ function collectBookmarkConversationCues(bookmark, messages2) {
1627
+ const host = bookmarkMemoryKey(bookmark.url);
1628
+ const hostTokens = dedupe(
1629
+ host.split(".").filter((token) => token.length >= 4)
1630
+ );
1631
+ const titleTokens = tokenize(bookmark.title);
1632
+ const noteTokens = tokenize(bookmark.note || "").slice(0, 4);
1633
+ const urlLower = bookmark.url.toLowerCase();
1634
+ const cues = [];
1635
+ for (let index = messages2.length - 1; index >= 0; index -= 1) {
1636
+ const message = messages2[index];
1637
+ const content = collapseWhitespace(message.content);
1638
+ if (!content) continue;
1639
+ const lowered = content.toLowerCase();
1640
+ const matchedTokens = [...titleTokens, ...noteTokens].filter(
1641
+ (token) => lowered.includes(token)
1642
+ );
1643
+ const matchesBookmark = lowered.includes(host) || hostTokens.some((token) => lowered.includes(token)) || lowered.includes(urlLower) || matchedTokens.length >= 2 || matchedTokens.length >= 1 && titleTokens.length <= 1;
1644
+ if (!matchesBookmark) continue;
1645
+ const prefix = message.role === "user" ? "You" : "Assistant";
1646
+ cues.push(`${prefix}: ${cleanSnippet(content)}`);
1647
+ if (cues.length >= MAX_MEMORY_LINES) break;
1648
+ }
1649
+ return dedupe(cues);
1650
+ }
1651
+ function mergeBookmarkMemorySummary(existingSummary, cues) {
1652
+ const merged = dedupe([
1653
+ ...existingSummary ? existingSummary.split(" • ").map((item) => cleanSnippet(item, 160)) : [],
1654
+ ...cues
1655
+ ]).slice(0, MAX_MEMORY_LINES);
1656
+ if (merged.length === 0) return void 0;
1657
+ let summary = merged.join(" • ");
1658
+ if (summary.length > MAX_MEMORY_CHARS) {
1659
+ summary = `${summary.slice(0, MAX_MEMORY_CHARS - 3).trimEnd()}...`;
1660
+ }
1661
+ return summary;
1662
+ }
1663
+ function getStorage(storage) {
1664
+ if (storage) return storage;
1665
+ if (typeof window !== "undefined" && window.localStorage) {
1666
+ return window.localStorage;
1667
+ }
1668
+ return null;
1669
+ }
1670
+ function readMemoryMap(storage) {
1671
+ const target = getStorage(storage);
1672
+ if (!target) return {};
1673
+ try {
1674
+ const raw = target.getItem(MEMORY_STORAGE_KEY);
1675
+ if (!raw) return {};
1676
+ const parsed = JSON.parse(raw);
1677
+ return parsed && typeof parsed === "object" ? parsed : {};
1678
+ } catch {
1679
+ return {};
1680
+ }
1681
+ }
1682
+ function writeMemoryMap(map, storage) {
1683
+ const target = getStorage(storage);
1684
+ if (!target) return;
1685
+ try {
1686
+ target.setItem(MEMORY_STORAGE_KEY, JSON.stringify(map));
1687
+ } catch {
1688
+ }
1689
+ }
1690
+ function rememberBookmarkContext(args) {
1691
+ const key = bookmarkMemoryKey(args.bookmark.url);
1692
+ const map = readMemoryMap(args.storage);
1693
+ const existing = map[key];
1694
+ const cues = collectBookmarkConversationCues(args.bookmark, args.messages);
1695
+ const summary = mergeBookmarkMemorySummary(existing?.summary, cues);
1696
+ if (!summary) {
1697
+ return existing ?? null;
1698
+ }
1699
+ const entry = {
1700
+ summary,
1701
+ title: args.bookmark.title || args.bookmark.url,
1702
+ url: args.bookmark.url,
1703
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1704
+ };
1705
+ map[key] = entry;
1706
+ writeMemoryMap(map, args.storage);
1707
+ return entry;
1708
+ }
1709
+ function buildBookmarkContextDraft(args) {
1710
+ const lines = [
1711
+ "Saved bookmark context for the next step:",
1712
+ `- Title: ${args.bookmark.title || args.bookmark.url}`,
1713
+ `- URL: ${args.bookmark.url}`
1714
+ ];
1715
+ if (args.folder?.name) {
1716
+ lines.push(`- Folder: ${args.folder.name}`);
1717
+ }
1718
+ if (args.folder?.summary) {
1719
+ lines.push(`- Folder summary: ${cleanSnippet(args.folder.summary, 180)}`);
1720
+ }
1721
+ if (args.bookmark.note) {
1722
+ lines.push(`- Saved note: ${cleanSnippet(args.bookmark.note, 180)}`);
1723
+ }
1724
+ if (args.rememberedSummary) {
1725
+ lines.push(`- Remembered site context: ${args.rememberedSummary}`);
1726
+ }
1727
+ return lines.join("\n");
1728
+ }
1729
+ function buildAndRememberBookmarkContext(args) {
1730
+ const remembered = rememberBookmarkContext({
1731
+ bookmark: args.bookmark,
1732
+ messages: args.messages,
1733
+ storage: args.storage
1734
+ });
1735
+ return buildBookmarkContextDraft({
1736
+ bookmark: args.bookmark,
1737
+ folder: args.folder,
1738
+ rememberedSummary: remembered?.summary ?? null
1739
+ });
1740
+ }
1590
1741
  const {
1591
1742
  entries,
1592
1743
  setPrototypeOf,
@@ -3104,6 +3255,28 @@ const Sidebar = (props) => {
3104
3255
  });
3105
3256
  onCleanup(unsubscribe);
3106
3257
  });
3258
+ createEffect(() => {
3259
+ const unsubscribe = window.vessel.bookmarks.onAddContextToChat((bookmarkId) => {
3260
+ const bookmark = bookmarksState2().bookmarks.find((item) => item.id === bookmarkId);
3261
+ if (!bookmark) return;
3262
+ const folder = bookmark.folderId === UNSORTED_FOLDER.id ? UNSORTED_FOLDER : bookmarksState2().folders.find((item) => item.id === bookmark.folderId) ?? null;
3263
+ const contextBlock = buildAndRememberBookmarkContext({
3264
+ bookmark,
3265
+ folder,
3266
+ messages: messages2()
3267
+ });
3268
+ setSidebarTab("chat");
3269
+ setChatInput((current) => current.trim() ? `${current.trim()}
3270
+
3271
+ ${contextBlock}` : contextBlock);
3272
+ queueMicrotask(() => {
3273
+ chatInputRef?.focus();
3274
+ const length = chatInputRef?.value.length ?? 0;
3275
+ chatInputRef?.setSelectionRange(length, length);
3276
+ });
3277
+ });
3278
+ onCleanup(unsubscribe);
3279
+ });
3107
3280
  const handleChatSend = async () => {
3108
3281
  const prompt = chatInput().trim();
3109
3282
  if (!prompt || isStreaming2()) return;
@@ -3136,6 +3309,7 @@ const Sidebar = (props) => {
3136
3309
  const [elapsedSeconds, setElapsedSeconds] = createSignal(0);
3137
3310
  let messagesContainerRef;
3138
3311
  let messagesEndRef;
3312
+ let chatInputRef;
3139
3313
  let hasInitializedMessageScroll = false;
3140
3314
  const recentActions = createMemo(() => runtimeState2().actions.slice(-8).reverse());
3141
3315
  const recentCheckpoints = createMemo(() => runtimeState2().checkpoints.slice(-5).reverse());
@@ -3621,6 +3795,7 @@ const Sidebar = (props) => {
3621
3795
  }), _el$138);
3622
3796
  insert(_el$139, () => formatBookmarkDate(bookmark.savedAt));
3623
3797
  _el$140.$$click = () => void removeBookmark(bookmark.id);
3798
+ createRenderEffect(() => setAttribute(_el$133, "data-bookmark-id", bookmark.id));
3624
3799
  return _el$133;
3625
3800
  })()
3626
3801
  }));
@@ -3868,6 +4043,8 @@ const Sidebar = (props) => {
3868
4043
  }
3869
4044
  };
3870
4045
  _el$91.$$input = (e) => setChatInput(e.currentTarget.value);
4046
+ var _ref$3 = chatInputRef;
4047
+ typeof _ref$3 === "function" ? use(_ref$3, _el$91) : chatInputRef = _el$91;
3871
4048
  _el$92.$$click = () => void handleChatSend();
3872
4049
  createRenderEffect(() => _el$92.disabled = !chatInput().trim() || isStreaming2());
3873
4050
  createRenderEffect(() => _el$91.value = chatInput());
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Vessel</title>
7
- <script type="module" crossorigin src="./assets/index-Do3B3G1W.js"></script>
7
+ <script type="module" crossorigin src="./assets/index-DiB_DxLD.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="./assets/index-DMd-y6tm.css">
9
9
  </head>
10
10
  <body>
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.12",
4
+ "version": "0.1.13",
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": {