@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.
package/out/preload/index.js
CHANGED
|
@@ -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());
|
package/out/renderer/index.html
CHANGED
|
@@ -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-
|
|
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.
|
|
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": {
|