@quanta-intellect/vessel-browser 0.1.136 → 0.1.138
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 +4 -4
- package/out/main/index.js +1464 -810
- package/out/preload/content-script.js +18 -0
- package/out/preload/index.js +4 -0
- package/out/renderer/assets/{index-D13TOsuR.js → index-Cjl9fej2.js} +1095 -684
- package/out/renderer/assets/{index-CWy6khUL.css → index-DMcjRzqj.css} +271 -19
- package/out/renderer/index.html +2 -2
- package/package.json +2 -2
package/out/main/index.js
CHANGED
|
@@ -10,6 +10,8 @@ const OpenAI = require("openai");
|
|
|
10
10
|
const crypto$2 = require("node:crypto");
|
|
11
11
|
const http = require("http");
|
|
12
12
|
const path$1 = require("node:path");
|
|
13
|
+
const readability = require("@mozilla/readability");
|
|
14
|
+
const linkedom = require("linkedom");
|
|
13
15
|
const node_module = require("node:module");
|
|
14
16
|
const http$1 = require("node:http");
|
|
15
17
|
const os = require("node:os");
|
|
@@ -494,6 +496,10 @@ function assertSafeURL(url) {
|
|
|
494
496
|
}
|
|
495
497
|
function assertPermittedNavigationURL(url) {
|
|
496
498
|
assertSafeURL(url);
|
|
499
|
+
const airGapError = getAirGapBlockReason(url);
|
|
500
|
+
if (airGapError) {
|
|
501
|
+
throw new Error(airGapError);
|
|
502
|
+
}
|
|
497
503
|
const policyError = checkDomainPolicy(url);
|
|
498
504
|
if (policyError) {
|
|
499
505
|
throw new Error(policyError);
|
|
@@ -589,11 +595,11 @@ class Tab {
|
|
|
589
595
|
return null;
|
|
590
596
|
}
|
|
591
597
|
try {
|
|
592
|
-
|
|
598
|
+
assertPermittedNavigationURL(url);
|
|
593
599
|
} catch (error) {
|
|
594
600
|
return error instanceof Error ? error.message : "Blocked unsafe navigation";
|
|
595
601
|
}
|
|
596
|
-
return
|
|
602
|
+
return null;
|
|
597
603
|
}
|
|
598
604
|
guardedLoadURL(url, options) {
|
|
599
605
|
const blockReason = this.getNavigationBlockReason(url);
|
|
@@ -3705,6 +3711,8 @@ const AutofillChannels = {
|
|
|
3705
3711
|
const AutomationChannels = {
|
|
3706
3712
|
AUTOMATION_GET_INSTALLED: "automation:get-installed",
|
|
3707
3713
|
AUTOMATION_INSTALL_FROM_FILE: "automation:install-from-file",
|
|
3714
|
+
AUTOMATION_CREATE_FROM_TEXT: "automation:create-from-text",
|
|
3715
|
+
AUTOMATION_UPDATE_FROM_TEXT: "automation:update-from-text",
|
|
3708
3716
|
AUTOMATION_UNINSTALL: "automation:uninstall",
|
|
3709
3717
|
AUTOMATION_ACTIVITY_START: "automation:activity-start",
|
|
3710
3718
|
AUTOMATION_ACTIVITY_CHUNK: "automation:activity-chunk",
|
|
@@ -4251,10 +4259,28 @@ function buildPageSnapshotKey(rawUrl) {
|
|
|
4251
4259
|
return normalizePageUrl(rawUrl);
|
|
4252
4260
|
}
|
|
4253
4261
|
}
|
|
4262
|
+
function isDocumentViewerUrl(rawUrl) {
|
|
4263
|
+
try {
|
|
4264
|
+
const url = new URL(rawUrl);
|
|
4265
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
|
4266
|
+
const pathname = decodeURIComponent(url.pathname).toLowerCase();
|
|
4267
|
+
if (/\.(pdf|epub|mobi|cbz|cbr)(?:$|[?#])/.test(pathname)) {
|
|
4268
|
+
return true;
|
|
4269
|
+
}
|
|
4270
|
+
const host = url.hostname.toLowerCase().replace(/^www\./, "");
|
|
4271
|
+
if (host === "archive.org") {
|
|
4272
|
+
return /^\/(details|stream|download)\//.test(pathname);
|
|
4273
|
+
}
|
|
4274
|
+
return false;
|
|
4275
|
+
} catch {
|
|
4276
|
+
return false;
|
|
4277
|
+
}
|
|
4278
|
+
}
|
|
4254
4279
|
function isTrackablePageUrl(rawUrl) {
|
|
4255
4280
|
try {
|
|
4256
4281
|
const url = new URL(rawUrl);
|
|
4257
|
-
|
|
4282
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
|
4283
|
+
return !isDocumentViewerUrl(rawUrl);
|
|
4258
4284
|
} catch {
|
|
4259
4285
|
return false;
|
|
4260
4286
|
}
|
|
@@ -4951,6 +4977,11 @@ const PREMIUM_TOOLS = /* @__PURE__ */ new Set([
|
|
|
4951
4977
|
"human_vault_list",
|
|
4952
4978
|
"human_vault_fill",
|
|
4953
4979
|
"human_vault_remove",
|
|
4980
|
+
"memory_note_create",
|
|
4981
|
+
"memory_note_append",
|
|
4982
|
+
"memory_note_list",
|
|
4983
|
+
"memory_note_search",
|
|
4984
|
+
"memory_page_capture",
|
|
4954
4985
|
"research_confirm_brief",
|
|
4955
4986
|
"research_approve_objectives",
|
|
4956
4987
|
"research_export_report"
|
|
@@ -6663,7 +6694,7 @@ async function extractContentInner(webContents) {
|
|
|
6663
6694
|
webContents
|
|
6664
6695
|
);
|
|
6665
6696
|
}
|
|
6666
|
-
async function extractContent
|
|
6697
|
+
async function extractContent(webContents) {
|
|
6667
6698
|
const cacheKey = `${webContents.id}:${webContents.getURL() || ""}`;
|
|
6668
6699
|
const cached = extractionCache.get(cacheKey);
|
|
6669
6700
|
if (cached) {
|
|
@@ -6881,7 +6912,7 @@ async function capturePageSnapshot(url, wc, sendToRendererViews) {
|
|
|
6881
6912
|
if (!shouldTrackSnapshotUrl(url)) return;
|
|
6882
6913
|
const key2 = normalizeUrl(url);
|
|
6883
6914
|
const oldSnap = getSnapshot(key2);
|
|
6884
|
-
const content = await extractContent
|
|
6915
|
+
const content = await extractContent(wc);
|
|
6885
6916
|
const textContent = content.content || "";
|
|
6886
6917
|
const title = content.title || "";
|
|
6887
6918
|
const headings = content.headings || [];
|
|
@@ -12113,7 +12144,7 @@ async function resolveSelector(wc, index, selector) {
|
|
|
12113
12144
|
if (typeof fallbackSelector === "string" && fallbackSelector) {
|
|
12114
12145
|
return fallbackSelector;
|
|
12115
12146
|
}
|
|
12116
|
-
const page = await extractContent
|
|
12147
|
+
const page = await extractContent(wc);
|
|
12117
12148
|
const extractedSelector = findSelectorByIndex(page, index);
|
|
12118
12149
|
if (extractedSelector) return extractedSelector;
|
|
12119
12150
|
return null;
|
|
@@ -12186,6 +12217,15 @@ async function validateLinkDestination(url, timeoutMs = 3500) {
|
|
|
12186
12217
|
detail: "Non-HTTP URL"
|
|
12187
12218
|
};
|
|
12188
12219
|
}
|
|
12220
|
+
try {
|
|
12221
|
+
assertPermittedNavigationURL(url);
|
|
12222
|
+
} catch (error) {
|
|
12223
|
+
return {
|
|
12224
|
+
status: "unknown",
|
|
12225
|
+
checkedUrl: url,
|
|
12226
|
+
detail: error instanceof Error ? error.message : "Navigation policy blocked URL"
|
|
12227
|
+
};
|
|
12228
|
+
}
|
|
12189
12229
|
try {
|
|
12190
12230
|
const headResponse = await requestUrl(url, "HEAD", timeoutMs);
|
|
12191
12231
|
if (!HEAD_FALLBACK_STATUS_CODES.has(headResponse.status)) {
|
|
@@ -14849,6 +14889,22 @@ function getGlanceExtractScript() {
|
|
|
14849
14889
|
};
|
|
14850
14890
|
})()`;
|
|
14851
14891
|
}
|
|
14892
|
+
function cleanArticleText(value) {
|
|
14893
|
+
return value.replace(/\u00a0/g, " ").replace(/[ \t]+/g, " ").replace(/\n[ \t]+/g, "\n").replace(/(\n\s*){3,}/g, "\n\n").trim();
|
|
14894
|
+
}
|
|
14895
|
+
function articleTextResultToOutput(result, mode, source, elapsedMs) {
|
|
14896
|
+
const sections = [
|
|
14897
|
+
`# ${result.title || "(untitled)"}`,
|
|
14898
|
+
`URL: ${result.url}`,
|
|
14899
|
+
"",
|
|
14900
|
+
`[read_page mode=${mode} — ${source}, ${elapsedMs}ms]`
|
|
14901
|
+
];
|
|
14902
|
+
if (result.headings.length > 0) {
|
|
14903
|
+
sections.push("", "## Headings", ...result.headings);
|
|
14904
|
+
}
|
|
14905
|
+
sections.push("", "## Article Text", "", result.text);
|
|
14906
|
+
return sections.join("\n");
|
|
14907
|
+
}
|
|
14852
14908
|
async function glanceExtract(wc) {
|
|
14853
14909
|
const startMs = Date.now();
|
|
14854
14910
|
const result = await executePageScript(wc, getGlanceExtractScript(), { timeoutMs: 2500, label: "glance-extract" });
|
|
@@ -14896,6 +14952,168 @@ async function glanceExtract(wc) {
|
|
|
14896
14952
|
}
|
|
14897
14953
|
return sections.join("\n");
|
|
14898
14954
|
}
|
|
14955
|
+
async function fastArticleTextExtract(wc, mode) {
|
|
14956
|
+
const startMs = Date.now();
|
|
14957
|
+
const result = await executePageScript(
|
|
14958
|
+
wc,
|
|
14959
|
+
`(function() {
|
|
14960
|
+
function clean(value) {
|
|
14961
|
+
return String(value || '').replace(/[ \\t]+/g, ' ').replace(/(\\n\\s*){3,}/g, '\\n\\n').trim();
|
|
14962
|
+
}
|
|
14963
|
+
|
|
14964
|
+
var rootSelectors = [
|
|
14965
|
+
'#mw-content-text .mw-parser-output',
|
|
14966
|
+
'#mw-content-text',
|
|
14967
|
+
'main article',
|
|
14968
|
+
'article',
|
|
14969
|
+
'main',
|
|
14970
|
+
'[role="main"]',
|
|
14971
|
+
'#content'
|
|
14972
|
+
];
|
|
14973
|
+
var root = null;
|
|
14974
|
+
for (var i = 0; i < rootSelectors.length; i++) {
|
|
14975
|
+
var candidate = document.querySelector(rootSelectors[i]);
|
|
14976
|
+
if (candidate && clean(candidate.textContent).length > 300) {
|
|
14977
|
+
root = candidate;
|
|
14978
|
+
break;
|
|
14979
|
+
}
|
|
14980
|
+
}
|
|
14981
|
+
if (!root) return null;
|
|
14982
|
+
|
|
14983
|
+
var unwantedSelector = [
|
|
14984
|
+
'script',
|
|
14985
|
+
'style',
|
|
14986
|
+
'noscript',
|
|
14987
|
+
'nav',
|
|
14988
|
+
'header',
|
|
14989
|
+
'footer',
|
|
14990
|
+
'aside',
|
|
14991
|
+
'.mw-editsection',
|
|
14992
|
+
'.reference',
|
|
14993
|
+
'.reflist',
|
|
14994
|
+
'.navbox',
|
|
14995
|
+
'.infobox',
|
|
14996
|
+
'.metadata',
|
|
14997
|
+
'.ambox',
|
|
14998
|
+
'.toc',
|
|
14999
|
+
'#toc'
|
|
15000
|
+
].join(',');
|
|
15001
|
+
|
|
15002
|
+
var headings = [];
|
|
15003
|
+
var parts = [];
|
|
15004
|
+
var nodes = root.querySelectorAll('h1, h2, h3, p, li');
|
|
15005
|
+
for (var j = 0; j < nodes.length && parts.length < 180; j++) {
|
|
15006
|
+
var node = nodes[j];
|
|
15007
|
+
if (node.closest && node.closest(unwantedSelector)) continue;
|
|
15008
|
+
var tag = String(node.tagName || '').toLowerCase();
|
|
15009
|
+
var text = clean(node.textContent);
|
|
15010
|
+
if (!text) continue;
|
|
15011
|
+
if (/^h[1-3]$/.test(tag)) {
|
|
15012
|
+
if (text.length < 180) headings.push(tag + ': ' + text);
|
|
15013
|
+
parts.push('\\n## ' + text);
|
|
15014
|
+
continue;
|
|
15015
|
+
}
|
|
15016
|
+
if (text.length < 40) continue;
|
|
15017
|
+
parts.push(text);
|
|
15018
|
+
}
|
|
15019
|
+
|
|
15020
|
+
var articleText = clean(parts.join('\\n\\n'));
|
|
15021
|
+
if (articleText.length < 300) {
|
|
15022
|
+
articleText = clean(root.textContent).slice(0, 12000);
|
|
15023
|
+
}
|
|
15024
|
+
if (articleText.length < 300) return null;
|
|
15025
|
+
|
|
15026
|
+
return {
|
|
15027
|
+
title: document.title || '',
|
|
15028
|
+
url: location.href,
|
|
15029
|
+
headings: headings.slice(0, 18),
|
|
15030
|
+
text: articleText.slice(0, ${mode === "summary" ? 9e3 : 14e3}),
|
|
15031
|
+
};
|
|
15032
|
+
})()`,
|
|
15033
|
+
{
|
|
15034
|
+
timeoutMs: 1800,
|
|
15035
|
+
label: "fast article text"
|
|
15036
|
+
}
|
|
15037
|
+
);
|
|
15038
|
+
if (!result || result === PAGE_SCRIPT_TIMEOUT || !result.text.trim()) {
|
|
15039
|
+
return null;
|
|
15040
|
+
}
|
|
15041
|
+
return articleTextResultToOutput(
|
|
15042
|
+
{
|
|
15043
|
+
title: result.title || wc.getTitle() || "(untitled)",
|
|
15044
|
+
url: result.url || wc.getURL(),
|
|
15045
|
+
headings: result.headings,
|
|
15046
|
+
text: result.text
|
|
15047
|
+
},
|
|
15048
|
+
mode,
|
|
15049
|
+
"fast article text",
|
|
15050
|
+
Date.now() - startMs
|
|
15051
|
+
);
|
|
15052
|
+
}
|
|
15053
|
+
async function fetchArticleTextExtract(wc, mode) {
|
|
15054
|
+
const startMs = Date.now();
|
|
15055
|
+
const url = wc.getURL();
|
|
15056
|
+
try {
|
|
15057
|
+
assertSafeURL(url);
|
|
15058
|
+
} catch {
|
|
15059
|
+
return null;
|
|
15060
|
+
}
|
|
15061
|
+
const controller = new AbortController();
|
|
15062
|
+
const timeout = setTimeout(() => controller.abort(), 4500);
|
|
15063
|
+
try {
|
|
15064
|
+
const response = await fetch(url, {
|
|
15065
|
+
signal: controller.signal,
|
|
15066
|
+
headers: {
|
|
15067
|
+
Accept: "text/html,application/xhtml+xml",
|
|
15068
|
+
"User-Agent": "VesselBrowser/0.1 read-page-fallback"
|
|
15069
|
+
}
|
|
15070
|
+
});
|
|
15071
|
+
if (!response.ok) return null;
|
|
15072
|
+
const contentType = response.headers.get("content-type") || "";
|
|
15073
|
+
if (contentType && !/text\/html|application\/xhtml\+xml/i.test(contentType)) {
|
|
15074
|
+
return null;
|
|
15075
|
+
}
|
|
15076
|
+
const contentLength = Number(response.headers.get("content-length") || "0");
|
|
15077
|
+
if (contentLength > 5e6) return null;
|
|
15078
|
+
const html = await response.text();
|
|
15079
|
+
if (html.trim().length < 300) return null;
|
|
15080
|
+
const { document } = linkedom.parseHTML(html);
|
|
15081
|
+
const readable = new readability.Readability(document, {
|
|
15082
|
+
charThreshold: 300
|
|
15083
|
+
}).parse();
|
|
15084
|
+
const title = cleanArticleText(readable?.title || document.title || wc.getTitle()) || wc.getTitle() || "(untitled)";
|
|
15085
|
+
const headings = Array.from(document.querySelectorAll("h1, h2, h3")).map((node) => {
|
|
15086
|
+
const tag = String(node.tagName || "").toLowerCase();
|
|
15087
|
+
const text2 = cleanArticleText(node.textContent || "");
|
|
15088
|
+
return text2.length > 0 && text2.length < 180 ? `${tag}: ${text2}` : "";
|
|
15089
|
+
}).filter(Boolean).slice(0, 18);
|
|
15090
|
+
const fallbackRoot = document.querySelector("article") || document.querySelector("main") || document.querySelector('[role="main"]') || document.body;
|
|
15091
|
+
const text = cleanArticleText(
|
|
15092
|
+
readable?.textContent || fallbackRoot?.textContent || ""
|
|
15093
|
+
);
|
|
15094
|
+
if (text.length < 300) return null;
|
|
15095
|
+
return articleTextResultToOutput(
|
|
15096
|
+
{
|
|
15097
|
+
title,
|
|
15098
|
+
url,
|
|
15099
|
+
headings,
|
|
15100
|
+
text: text.slice(0, mode === "summary" ? 9e3 : 14e3)
|
|
15101
|
+
},
|
|
15102
|
+
mode,
|
|
15103
|
+
"network article fallback",
|
|
15104
|
+
Date.now() - startMs
|
|
15105
|
+
);
|
|
15106
|
+
} catch (err) {
|
|
15107
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
15108
|
+
logger$k.warn("Network article fallback timed out:", url);
|
|
15109
|
+
} else {
|
|
15110
|
+
logger$k.warn("Network article fallback failed:", err);
|
|
15111
|
+
}
|
|
15112
|
+
return null;
|
|
15113
|
+
} finally {
|
|
15114
|
+
clearTimeout(timeout);
|
|
15115
|
+
}
|
|
15116
|
+
}
|
|
14899
15117
|
function normalizeReadPageMode(mode, pageContent) {
|
|
14900
15118
|
if (typeof mode === "string") {
|
|
14901
15119
|
const normalized = mode.trim().toLowerCase();
|
|
@@ -14967,7 +15185,7 @@ async function getPostSearchSummary(wc) {
|
|
|
14967
15185
|
await waitForLoad(wc, 2e3);
|
|
14968
15186
|
try {
|
|
14969
15187
|
const content = await Promise.race([
|
|
14970
|
-
extractContent
|
|
15188
|
+
extractContent(wc),
|
|
14971
15189
|
new Promise((resolve) => setTimeout(() => resolve(null), 2500))
|
|
14972
15190
|
]);
|
|
14973
15191
|
if (content && content.content.length > 0) {
|
|
@@ -14989,7 +15207,7 @@ Search results snapshot unavailable. Use read_page(mode="results_only") if neede
|
|
|
14989
15207
|
async function getPostClickNavSummary(wc, toolProfile) {
|
|
14990
15208
|
try {
|
|
14991
15209
|
const content = await Promise.race([
|
|
14992
|
-
extractContent
|
|
15210
|
+
extractContent(wc),
|
|
14993
15211
|
new Promise((resolve) => setTimeout(() => resolve(null), 3e3))
|
|
14994
15212
|
]);
|
|
14995
15213
|
if (content && content.content.length > 0) {
|
|
@@ -16715,8 +16933,11 @@ async function tryAcceptCookiesQuickly(wc) {
|
|
|
16715
16933
|
const dismissed = await executePageScript(
|
|
16716
16934
|
wc,
|
|
16717
16935
|
`
|
|
16718
|
-
(function() {
|
|
16719
|
-
var
|
|
16936
|
+
(async function() {
|
|
16937
|
+
var delay = function(ms) {
|
|
16938
|
+
return new Promise(function(resolve) { setTimeout(resolve, ms); });
|
|
16939
|
+
};
|
|
16940
|
+
var selectorTargets = [
|
|
16720
16941
|
'#onetrust-accept-btn-handler',
|
|
16721
16942
|
'#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll',
|
|
16722
16943
|
'[data-cookiefirst-action="accept"]',
|
|
@@ -16736,60 +16957,237 @@ async function tryAcceptCookiesQuickly(wc) {
|
|
|
16736
16957
|
'.message-component.message-button.no-children.focusable.sp_choice_type_11',
|
|
16737
16958
|
'[class*="truste"] [class*="accept"]',
|
|
16738
16959
|
'[id*="consent-accept"]',
|
|
16739
|
-
'[class*="cmp-accept"]'
|
|
16960
|
+
'[class*="cmp-accept"]'
|
|
16740
16961
|
];
|
|
16741
|
-
var
|
|
16742
|
-
'
|
|
16743
|
-
'
|
|
16744
|
-
'
|
|
16745
|
-
'
|
|
16746
|
-
'
|
|
16747
|
-
'
|
|
16748
|
-
'
|
|
16749
|
-
'i
|
|
16750
|
-
'i
|
|
16751
|
-
'
|
|
16752
|
-
'
|
|
16753
|
-
'
|
|
16754
|
-
'
|
|
16962
|
+
var surfaceSelectors = [
|
|
16963
|
+
'#onetrust-consent-sdk',
|
|
16964
|
+
'#CybotCookiebotDialog',
|
|
16965
|
+
'[id*="cookie" i]',
|
|
16966
|
+
'[id*="consent" i]',
|
|
16967
|
+
'[id*="cmp" i]',
|
|
16968
|
+
'[id*="sp_message" i]',
|
|
16969
|
+
'[class*="cookie" i]',
|
|
16970
|
+
'[class*="consent" i]',
|
|
16971
|
+
'[class*="cmp" i]',
|
|
16972
|
+
'[class*="sp_message" i]',
|
|
16973
|
+
'[class*="truste" i]',
|
|
16974
|
+
'[class*="didomi" i]',
|
|
16975
|
+
'[data-testid*="cookie" i]',
|
|
16976
|
+
'[data-testid*="consent" i]',
|
|
16977
|
+
'[aria-label*="cookie" i]',
|
|
16978
|
+
'[aria-label*="consent" i]',
|
|
16979
|
+
'.fc-consent-root'
|
|
16755
16980
|
];
|
|
16756
|
-
|
|
16757
|
-
|
|
16758
|
-
|
|
16759
|
-
|
|
16760
|
-
|
|
16981
|
+
var actionSelector = [
|
|
16982
|
+
'button',
|
|
16983
|
+
'[role="button"]',
|
|
16984
|
+
'a[role="button"]',
|
|
16985
|
+
'a.message-component',
|
|
16986
|
+
'input[type="button"]',
|
|
16987
|
+
'input[type="submit"]'
|
|
16988
|
+
].join(',');
|
|
16989
|
+
var seen = [];
|
|
16990
|
+
|
|
16991
|
+
function normalize(text) {
|
|
16992
|
+
return String(text || '').replace(/\\s+/g, ' ').trim();
|
|
16993
|
+
}
|
|
16994
|
+
|
|
16995
|
+
function lower(text) {
|
|
16996
|
+
return normalize(text).toLowerCase();
|
|
16997
|
+
}
|
|
16998
|
+
|
|
16999
|
+
function isElementVisible(el) {
|
|
17000
|
+
if (!(el instanceof HTMLElement)) return false;
|
|
17001
|
+
var style = window.getComputedStyle(el);
|
|
17002
|
+
if (style.display === 'none' || style.visibility === 'hidden') return false;
|
|
17003
|
+
if (Number(style.opacity || '1') < 0.05) return false;
|
|
17004
|
+
var rect = el.getBoundingClientRect();
|
|
17005
|
+
return rect.width > 2 &&
|
|
17006
|
+
rect.height > 2 &&
|
|
17007
|
+
rect.bottom > 0 &&
|
|
17008
|
+
rect.right > 0 &&
|
|
17009
|
+
rect.top < window.innerHeight &&
|
|
17010
|
+
rect.left < window.innerWidth;
|
|
17011
|
+
}
|
|
17012
|
+
|
|
17013
|
+
function elementText(el) {
|
|
17014
|
+
if (!(el instanceof HTMLElement)) return '';
|
|
17015
|
+
return normalize([
|
|
17016
|
+
el.getAttribute('aria-label'),
|
|
17017
|
+
el.getAttribute('title'),
|
|
17018
|
+
el.getAttribute('value'),
|
|
17019
|
+
el.textContent
|
|
17020
|
+
].filter(Boolean).join(' '));
|
|
17021
|
+
}
|
|
17022
|
+
|
|
17023
|
+
function looksLikeCookieSurface(el) {
|
|
17024
|
+
if (!isElementVisible(el)) return false;
|
|
17025
|
+
var text = lower([
|
|
17026
|
+
el.getAttribute('aria-label'),
|
|
17027
|
+
el.getAttribute('id'),
|
|
17028
|
+
el.getAttribute('class'),
|
|
17029
|
+
el.textContent
|
|
17030
|
+
].filter(Boolean).join(' ')).slice(0, 1600);
|
|
17031
|
+
if (!/(cookie|consent|privacy|tracking|personalise|personalize|advertis|data choices|your choices|cmp|onetrust|truste|didomi)/.test(text)) {
|
|
17032
|
+
return false;
|
|
16761
17033
|
}
|
|
17034
|
+
|
|
17035
|
+
var rect = el.getBoundingClientRect();
|
|
17036
|
+
var style = window.getComputedStyle(el);
|
|
17037
|
+
var fixedLike = style.position === 'fixed' || style.position === 'sticky';
|
|
17038
|
+
var sizeable = rect.width >= Math.min(window.innerWidth * 0.35, 360) && rect.height >= 48;
|
|
17039
|
+
var bottomBanner = fixedLike && rect.bottom > window.innerHeight * 0.58 && rect.height >= 64;
|
|
17040
|
+
var dialog = el.getAttribute('role') === 'dialog' || el.getAttribute('aria-modal') === 'true';
|
|
17041
|
+
var namedSurface = /(cookie|consent|cmp|onetrust|truste|didomi|sp_message)/.test(lower([
|
|
17042
|
+
el.getAttribute('id'),
|
|
17043
|
+
el.getAttribute('class'),
|
|
17044
|
+
el.getAttribute('data-testid')
|
|
17045
|
+
].filter(Boolean).join(' ')));
|
|
17046
|
+
|
|
17047
|
+
return sizeable && (namedSurface || bottomBanner || dialog);
|
|
16762
17048
|
}
|
|
16763
|
-
|
|
16764
|
-
|
|
16765
|
-
var
|
|
16766
|
-
|
|
16767
|
-
|
|
16768
|
-
|
|
16769
|
-
|
|
16770
|
-
|
|
17049
|
+
|
|
17050
|
+
function cookieSurfaces() {
|
|
17051
|
+
var surfaces = [];
|
|
17052
|
+
function addSurface(el) {
|
|
17053
|
+
if (surfaces.indexOf(el) === -1 && looksLikeCookieSurface(el)) {
|
|
17054
|
+
surfaces.push(el);
|
|
17055
|
+
}
|
|
17056
|
+
}
|
|
17057
|
+
|
|
17058
|
+
for (var i = 0; i < surfaceSelectors.length; i++) {
|
|
17059
|
+
try {
|
|
17060
|
+
document.querySelectorAll(surfaceSelectors[i]).forEach(addSurface);
|
|
17061
|
+
} catch {
|
|
17062
|
+
// Ignore unsupported selectors in older page engines.
|
|
17063
|
+
}
|
|
17064
|
+
}
|
|
17065
|
+
|
|
17066
|
+
document.querySelectorAll('div, section, aside, footer, form, [role="dialog"]').forEach(function(el) {
|
|
17067
|
+
if (!(el instanceof HTMLElement)) return;
|
|
17068
|
+
var style = window.getComputedStyle(el);
|
|
17069
|
+
if (style.position === 'fixed' || style.position === 'sticky' || el.getAttribute('role') === 'dialog') {
|
|
17070
|
+
addSurface(el);
|
|
16771
17071
|
}
|
|
17072
|
+
});
|
|
17073
|
+
|
|
17074
|
+
return surfaces;
|
|
17075
|
+
}
|
|
17076
|
+
|
|
17077
|
+
function hasCookieSurface() {
|
|
17078
|
+
return cookieSurfaces().length > 0;
|
|
17079
|
+
}
|
|
17080
|
+
|
|
17081
|
+
function labelScore(label) {
|
|
17082
|
+
var text = lower(label);
|
|
17083
|
+
if (!text) return 0;
|
|
17084
|
+
if (/^(accept all|accept cookies|allow all|allow cookies|accept and continue|accept & continue|agree and continue)$/.test(text)) {
|
|
17085
|
+
return 220;
|
|
17086
|
+
}
|
|
17087
|
+
if (/^(i agree|i accept|agree|accept|allow|ok|okay|got it|continue|yes)$/.test(text)) {
|
|
17088
|
+
return 190;
|
|
17089
|
+
}
|
|
17090
|
+
if (/^(reject all|decline|deny|necessary only|essential only|save preferences|confirm choices|submit preferences)$/.test(text)) {
|
|
17091
|
+
return 170;
|
|
17092
|
+
}
|
|
17093
|
+
if (/\\b(accept all|allow all|accept cookies|allow cookies|accept and continue|accept & continue|agree and continue)\\b/.test(text)) {
|
|
17094
|
+
return 160;
|
|
17095
|
+
}
|
|
17096
|
+
if (/\\b(reject all|save preferences|confirm choices|necessary only|essential only)\\b/.test(text)) {
|
|
17097
|
+
return 145;
|
|
17098
|
+
}
|
|
17099
|
+
if (/^(consent|cookie consent|cookies|privacy|privacy policy|cookie policy|learn more|more information|settings|preferences|manage options|customize|customise)$/.test(text)) {
|
|
17100
|
+
return 0;
|
|
17101
|
+
}
|
|
17102
|
+
return 0;
|
|
17103
|
+
}
|
|
17104
|
+
|
|
17105
|
+
function addCandidate(el, source, baseScore) {
|
|
17106
|
+
if (!(el instanceof HTMLElement) || seen.indexOf(el) !== -1 || !isElementVisible(el)) return;
|
|
17107
|
+
var label = elementText(el);
|
|
17108
|
+
var score = labelScore(label);
|
|
17109
|
+
if (score <= 0 && baseScore < 160) return;
|
|
17110
|
+
seen.push(el);
|
|
17111
|
+
candidates.push({
|
|
17112
|
+
el: el,
|
|
17113
|
+
label: label || source,
|
|
17114
|
+
source: source,
|
|
17115
|
+
score: baseScore + score
|
|
17116
|
+
});
|
|
17117
|
+
}
|
|
17118
|
+
|
|
17119
|
+
var beforeHadSurface = hasCookieSurface();
|
|
17120
|
+
var candidates = [];
|
|
17121
|
+
|
|
17122
|
+
for (var i = 0; i < selectorTargets.length; i++) {
|
|
17123
|
+
try {
|
|
17124
|
+
document.querySelectorAll(selectorTargets[i]).forEach(function(el) {
|
|
17125
|
+
addCandidate(el, selectorTargets[i], 160);
|
|
17126
|
+
});
|
|
17127
|
+
} catch {
|
|
17128
|
+
// Ignore unsupported selectors in older page engines.
|
|
17129
|
+
}
|
|
17130
|
+
}
|
|
17131
|
+
|
|
17132
|
+
var surfaces = cookieSurfaces();
|
|
17133
|
+
surfaces.forEach(function(surface) {
|
|
17134
|
+
surface.querySelectorAll(actionSelector).forEach(function(el) {
|
|
17135
|
+
addCandidate(el, 'cookie surface', 120);
|
|
17136
|
+
});
|
|
17137
|
+
});
|
|
17138
|
+
|
|
17139
|
+
if (beforeHadSurface) {
|
|
17140
|
+
document.querySelectorAll(actionSelector).forEach(function(el) {
|
|
17141
|
+
addCandidate(el, 'page action', 40);
|
|
17142
|
+
});
|
|
17143
|
+
}
|
|
17144
|
+
|
|
17145
|
+
candidates.sort(function(a, b) { return b.score - a.score; });
|
|
17146
|
+
|
|
17147
|
+
var tried = 0;
|
|
17148
|
+
for (var j = 0; j < Math.min(candidates.length, 8); j++) {
|
|
17149
|
+
var candidate = candidates[j];
|
|
17150
|
+
tried += 1;
|
|
17151
|
+
candidate.el.click();
|
|
17152
|
+
await delay(220);
|
|
17153
|
+
if (!hasCookieSurface()) {
|
|
17154
|
+
return {
|
|
17155
|
+
status: 'dismissed',
|
|
17156
|
+
message: 'Dismissed cookie banner via: ' + candidate.label.slice(0, 80)
|
|
17157
|
+
};
|
|
16772
17158
|
}
|
|
16773
17159
|
}
|
|
17160
|
+
|
|
17161
|
+
if (beforeHadSurface || tried > 0) {
|
|
17162
|
+
return {
|
|
17163
|
+
status: 'still_visible',
|
|
17164
|
+
message: tried > 0
|
|
17165
|
+
? 'Cookie consent banner is still visible after trying ' + tried + ' candidate control(s). Try clear_overlays or dismiss_popup.'
|
|
17166
|
+
: 'Cookie consent banner appears visible, but no reliable accept/reject button was found. Try clear_overlays or dismiss_popup.'
|
|
17167
|
+
};
|
|
17168
|
+
}
|
|
17169
|
+
|
|
16774
17170
|
return null;
|
|
16775
17171
|
})()
|
|
16776
17172
|
`,
|
|
16777
17173
|
{
|
|
16778
17174
|
label: "accept cookies",
|
|
16779
|
-
timeoutMs:
|
|
17175
|
+
timeoutMs: 2200,
|
|
17176
|
+
userGesture: true
|
|
16780
17177
|
}
|
|
16781
17178
|
);
|
|
16782
17179
|
if (dismissed) return dismissed;
|
|
16783
|
-
|
|
17180
|
+
const iframeDismissed = await tryDismissConsentIframe(wc);
|
|
17181
|
+
return iframeDismissed ? { status: "dismissed", message: iframeDismissed } : null;
|
|
16784
17182
|
}
|
|
16785
17183
|
async function clearOverlays(wc, strategy = "auto") {
|
|
16786
17184
|
const quickCookieResult = await tryAcceptCookiesQuickly(wc);
|
|
16787
17185
|
if (quickCookieResult === PAGE_SCRIPT_TIMEOUT) {
|
|
16788
17186
|
return pageBusyError("clear_overlays");
|
|
16789
17187
|
}
|
|
16790
|
-
if (quickCookieResult) {
|
|
17188
|
+
if (quickCookieResult?.status === "dismissed") {
|
|
16791
17189
|
return [
|
|
16792
|
-
quickCookieResult,
|
|
17190
|
+
quickCookieResult.message,
|
|
16793
17191
|
"Stopped after a lightweight consent pass to keep the page responsive. Re-run only if the banner is still blocking the page."
|
|
16794
17192
|
].join("\n");
|
|
16795
17193
|
}
|
|
@@ -17738,6 +18136,94 @@ async function locateSearchTarget(wc, explicitSelector) {
|
|
|
17738
18136
|
}
|
|
17739
18137
|
);
|
|
17740
18138
|
}
|
|
18139
|
+
async function locateImplicitTextTarget(wc) {
|
|
18140
|
+
return executePageScript(
|
|
18141
|
+
wc,
|
|
18142
|
+
`
|
|
18143
|
+
(function() {
|
|
18144
|
+
function normalize(value) {
|
|
18145
|
+
return value == null ? "" : String(value).trim().toLowerCase();
|
|
18146
|
+
}
|
|
18147
|
+
|
|
18148
|
+
function isVisible(el) {
|
|
18149
|
+
if (!(el instanceof HTMLElement)) return true;
|
|
18150
|
+
const style = window.getComputedStyle(el);
|
|
18151
|
+
if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") return false;
|
|
18152
|
+
if (el.hasAttribute("hidden") || el.getAttribute("aria-hidden") === "true") return false;
|
|
18153
|
+
const rect = el.getBoundingClientRect();
|
|
18154
|
+
return rect.width > 0 && rect.height > 0;
|
|
18155
|
+
}
|
|
18156
|
+
|
|
18157
|
+
function inViewport(el) {
|
|
18158
|
+
if (!(el instanceof HTMLElement)) return true;
|
|
18159
|
+
const rect = el.getBoundingClientRect();
|
|
18160
|
+
const vw = window.innerWidth || document.documentElement?.clientWidth || 0;
|
|
18161
|
+
const vh = window.innerHeight || document.documentElement?.clientHeight || 0;
|
|
18162
|
+
return rect.bottom > 0 && rect.right > 0 && rect.top < vh && rect.left < vw;
|
|
18163
|
+
}
|
|
18164
|
+
|
|
18165
|
+
function isFillable(el) {
|
|
18166
|
+
if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) return false;
|
|
18167
|
+
if (el.disabled || el.readOnly || el.getAttribute("aria-disabled") === "true") return false;
|
|
18168
|
+
const type = el instanceof HTMLTextAreaElement ? "text" : normalize(el.getAttribute("type") || el.type || "text");
|
|
18169
|
+
return ["", "search", "text", "email", "url", "tel", "number", "password"].includes(type);
|
|
18170
|
+
}
|
|
18171
|
+
|
|
18172
|
+
function nearestSearchScope(input) {
|
|
18173
|
+
return input.closest('[role="search"], form, header, nav, [class*="search" i], [id*="search" i]');
|
|
18174
|
+
}
|
|
18175
|
+
|
|
18176
|
+
${selectorHelpersJS(["data-testid", "name", "form", "aria-label", "placeholder"])}
|
|
18177
|
+
|
|
18178
|
+
const active = document.activeElement;
|
|
18179
|
+
if (active && isFillable(active) && isVisible(active) && inViewport(active)) {
|
|
18180
|
+
return selectorFor(active);
|
|
18181
|
+
}
|
|
18182
|
+
|
|
18183
|
+
const candidates = Array.from(
|
|
18184
|
+
document.querySelectorAll('input:not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="image"]), textarea')
|
|
18185
|
+
).filter((el) => isFillable(el) && isVisible(el));
|
|
18186
|
+
|
|
18187
|
+
let best = null;
|
|
18188
|
+
let bestScore = -1;
|
|
18189
|
+
for (const el of candidates) {
|
|
18190
|
+
let score = 0;
|
|
18191
|
+
if (inViewport(el)) score += 100;
|
|
18192
|
+
const rect = el.getBoundingClientRect();
|
|
18193
|
+
score += Math.max(0, 36 - Math.min(36, Math.floor(Math.max(0, rect.top) / 22)));
|
|
18194
|
+
|
|
18195
|
+
const type = el instanceof HTMLTextAreaElement ? "text" : normalize(el.getAttribute("type") || el.type);
|
|
18196
|
+
const name = normalize(el.getAttribute("name"));
|
|
18197
|
+
const placeholder = normalize(el.getAttribute("placeholder"));
|
|
18198
|
+
const aria = normalize(el.getAttribute("aria-label"));
|
|
18199
|
+
const role = normalize(el.getAttribute("role"));
|
|
18200
|
+
const id = normalize(el.getAttribute("id"));
|
|
18201
|
+
|
|
18202
|
+
if (type === "search") score += 80;
|
|
18203
|
+
if (role === "searchbox") score += 70;
|
|
18204
|
+
if (name === "q" || name === "query" || name === "search") score += 65;
|
|
18205
|
+
if (placeholder.includes("search")) score += 55;
|
|
18206
|
+
if (aria.includes("search")) score += 55;
|
|
18207
|
+
if (id.includes("search")) score += 35;
|
|
18208
|
+
|
|
18209
|
+
const scope = nearestSearchScope(el);
|
|
18210
|
+
if (scope) score += 35;
|
|
18211
|
+
|
|
18212
|
+
if (score > bestScore) {
|
|
18213
|
+
best = el;
|
|
18214
|
+
bestScore = score;
|
|
18215
|
+
}
|
|
18216
|
+
}
|
|
18217
|
+
|
|
18218
|
+
return best ? selectorFor(best) : null;
|
|
18219
|
+
})()
|
|
18220
|
+
`,
|
|
18221
|
+
{
|
|
18222
|
+
timeoutMs: 2200,
|
|
18223
|
+
label: "find implicit text input"
|
|
18224
|
+
}
|
|
18225
|
+
);
|
|
18226
|
+
}
|
|
17741
18227
|
async function searchPage(wc, args) {
|
|
17742
18228
|
const query = String(args.query || "");
|
|
17743
18229
|
if (!query) return "Error: No search query provided.";
|
|
@@ -18227,8 +18713,19 @@ async function executeAction(name, args, ctx) {
|
|
|
18227
18713
|
}
|
|
18228
18714
|
case "type_text": {
|
|
18229
18715
|
if (!wc) return "Error: No active tab";
|
|
18230
|
-
|
|
18231
|
-
if (
|
|
18716
|
+
let selector = await resolveSelector(wc, args.index, args.selector);
|
|
18717
|
+
if (selector === PAGE_SCRIPT_TIMEOUT) {
|
|
18718
|
+
return pageBusyError("type_text");
|
|
18719
|
+
}
|
|
18720
|
+
if (!selector) {
|
|
18721
|
+
selector = await locateImplicitTextTarget(wc);
|
|
18722
|
+
}
|
|
18723
|
+
if (selector === PAGE_SCRIPT_TIMEOUT) {
|
|
18724
|
+
return pageBusyError("type_text");
|
|
18725
|
+
}
|
|
18726
|
+
if (!selector) {
|
|
18727
|
+
return "Error: No element index or selector provided, and no focused or visible text input could be found.";
|
|
18728
|
+
}
|
|
18232
18729
|
const mode = typeof args.mode === "string" ? args.mode : "default";
|
|
18233
18730
|
if (mode === "keystroke") {
|
|
18234
18731
|
return typeKeystroke(wc, selector, String(args.text || ""));
|
|
@@ -18324,6 +18821,23 @@ async function executeAction(name, args, ctx) {
|
|
|
18324
18821
|
if (requestedGlance) {
|
|
18325
18822
|
return glanceExtract(wc);
|
|
18326
18823
|
}
|
|
18824
|
+
const requestedTextMode = typeof args.mode === "string" ? args.mode.trim().toLowerCase() : "";
|
|
18825
|
+
if (requestedTextMode === "summary" || requestedTextMode === "text_only") {
|
|
18826
|
+
const fastArticleText = await fastArticleTextExtract(
|
|
18827
|
+
wc,
|
|
18828
|
+
requestedTextMode
|
|
18829
|
+
);
|
|
18830
|
+
if (fastArticleText) {
|
|
18831
|
+
return fastArticleText;
|
|
18832
|
+
}
|
|
18833
|
+
const fetchedArticleText = await fetchArticleTextExtract(
|
|
18834
|
+
wc,
|
|
18835
|
+
requestedTextMode
|
|
18836
|
+
);
|
|
18837
|
+
if (fetchedArticleText) {
|
|
18838
|
+
return fetchedArticleText;
|
|
18839
|
+
}
|
|
18840
|
+
}
|
|
18327
18841
|
let content = null;
|
|
18328
18842
|
try {
|
|
18329
18843
|
content = await Promise.race([
|
|
@@ -19020,7 +19534,7 @@ ${steps.join("\n")}`;
|
|
|
19020
19534
|
if (dismissed === PAGE_SCRIPT_TIMEOUT) {
|
|
19021
19535
|
return pageBusyError("accept_cookies");
|
|
19022
19536
|
}
|
|
19023
|
-
if (dismissed) return dismissed;
|
|
19537
|
+
if (dismissed) return dismissed.message;
|
|
19024
19538
|
return "No cookie consent banner detected. Try dismiss_popup for other overlays.";
|
|
19025
19539
|
}
|
|
19026
19540
|
case "extract_table": {
|
|
@@ -20272,13 +20786,31 @@ async function showDownloadInFolder(id) {
|
|
|
20272
20786
|
}
|
|
20273
20787
|
const defaultDownloadViews = /* @__PURE__ */ new Set();
|
|
20274
20788
|
let defaultDownloadHandlerInstalled = false;
|
|
20789
|
+
function sanitizeDownloadFilename(filename) {
|
|
20790
|
+
const normalized = filename.replace(/\\/g, "/");
|
|
20791
|
+
const basename = path.posix.basename(normalized).trim();
|
|
20792
|
+
const safeName = basename.replace(/[\0-\x1f\x7f]/g, "_");
|
|
20793
|
+
if (!safeName || safeName === "." || safeName === "..") {
|
|
20794
|
+
return "download";
|
|
20795
|
+
}
|
|
20796
|
+
return safeName;
|
|
20797
|
+
}
|
|
20798
|
+
function isPathInside(parentDir, candidatePath) {
|
|
20799
|
+
const relative = path.relative(parentDir, candidatePath);
|
|
20800
|
+
return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
20801
|
+
}
|
|
20275
20802
|
function resolveDownloadPath(downloadDir, filename) {
|
|
20276
20803
|
fs$1.mkdirSync(downloadDir, { recursive: true });
|
|
20277
|
-
const
|
|
20804
|
+
const rootDir = path.resolve(downloadDir);
|
|
20805
|
+
const safeFilename = sanitizeDownloadFilename(filename);
|
|
20806
|
+
const parsed = path.parse(safeFilename);
|
|
20278
20807
|
let attempt = 0;
|
|
20279
20808
|
while (true) {
|
|
20280
|
-
const candidateName = attempt === 0 ?
|
|
20281
|
-
const candidatePath = path.
|
|
20809
|
+
const candidateName = attempt === 0 ? safeFilename : `${parsed.name} (${attempt})${parsed.ext}`;
|
|
20810
|
+
const candidatePath = path.resolve(rootDir, candidateName);
|
|
20811
|
+
if (!isPathInside(rootDir, candidatePath)) {
|
|
20812
|
+
throw new Error("Blocked unsafe download filename");
|
|
20813
|
+
}
|
|
20282
20814
|
if (!fs$1.existsSync(candidatePath)) {
|
|
20283
20815
|
return candidatePath;
|
|
20284
20816
|
}
|
|
@@ -21455,7 +21987,7 @@ async function handleAIQuery(query, provider, activeWebContents, onChunk, onEnd,
|
|
|
21455
21987
|
const isSummarize = lowerQuery.startsWith("summarize") || lowerQuery.startsWith("tldr") || lowerQuery === "summary";
|
|
21456
21988
|
if (provider.streamAgentQuery && tabManager && activeWebContents && runtime2) {
|
|
21457
21989
|
try {
|
|
21458
|
-
const pageContent = await extractContent
|
|
21990
|
+
const pageContent = await extractContent(activeWebContents);
|
|
21459
21991
|
const pageType = detectPageType(pageContent);
|
|
21460
21992
|
const defaultReadMode = chooseAgentReadMode(pageContent);
|
|
21461
21993
|
if (provider.agentToolProfile === "compact") {
|
|
@@ -21556,7 +22088,7 @@ ${trackerCtx}`;
|
|
|
21556
22088
|
let prompt;
|
|
21557
22089
|
if (activeWebContents) {
|
|
21558
22090
|
try {
|
|
21559
|
-
const pageContent = await extractContent
|
|
22091
|
+
const pageContent = await extractContent(activeWebContents);
|
|
21560
22092
|
if (isSummarize) {
|
|
21561
22093
|
prompt = buildSummarizePrompt(pageContent);
|
|
21562
22094
|
} else {
|
|
@@ -21837,7 +22369,7 @@ function registerContentHandlers(windowState2) {
|
|
|
21837
22369
|
assertTrustedIpcSender(event);
|
|
21838
22370
|
const activeTab = tabManager.getActiveTab();
|
|
21839
22371
|
if (!activeTab) return null;
|
|
21840
|
-
return extractContent
|
|
22372
|
+
return extractContent(activeTab.view.webContents);
|
|
21841
22373
|
});
|
|
21842
22374
|
electron.ipcMain.handle(Channels.READER_MODE_TOGGLE, async (event) => {
|
|
21843
22375
|
assertTrustedIpcSender(event);
|
|
@@ -21851,7 +22383,7 @@ function registerContentHandlers(windowState2) {
|
|
|
21851
22383
|
}
|
|
21852
22384
|
} else {
|
|
21853
22385
|
const originalUrl = activeTab.state.url;
|
|
21854
|
-
const content = await extractContent
|
|
22386
|
+
const content = await extractContent(activeTab.view.webContents);
|
|
21855
22387
|
const html = generateReaderHTML(content);
|
|
21856
22388
|
activeTab.setReaderMode(true, originalUrl);
|
|
21857
22389
|
void loadInternalDataURL(
|
|
@@ -22147,532 +22679,182 @@ function registerAgentRuntimeHandlers(runtime2, chromeView, sidebarView, sendToR
|
|
|
22147
22679
|
}
|
|
22148
22680
|
);
|
|
22149
22681
|
}
|
|
22150
|
-
|
|
22151
|
-
|
|
22152
|
-
const DEFAULT_BOOKMARK_FOLDER = "Vessel/Bookmarks";
|
|
22153
|
-
const PAGE_CONTENT_LIMIT = 6e3;
|
|
22154
|
-
const DEFAULT_LIST_LIMIT = 50;
|
|
22155
|
-
const DEFAULT_SEARCH_LIMIT = 20;
|
|
22156
|
-
function getVaultRoot() {
|
|
22157
|
-
const configured = loadSettings().obsidianVaultPath.trim();
|
|
22158
|
-
if (!configured) {
|
|
22159
|
-
throw new Error(
|
|
22160
|
-
"Obsidian not configured. Set vault path in Vessel settings to use memory capture."
|
|
22161
|
-
);
|
|
22162
|
-
}
|
|
22163
|
-
return path$1.resolve(configured);
|
|
22682
|
+
function asTextResponse$1(text) {
|
|
22683
|
+
return { content: [{ type: "text", text }] };
|
|
22164
22684
|
}
|
|
22165
|
-
|
|
22166
|
-
|
|
22167
|
-
|
|
22168
|
-
|
|
22169
|
-
|
|
22170
|
-
|
|
22171
|
-
|
|
22685
|
+
const DANGEROUS_DEVTOOLS_ACTIONS = /* @__PURE__ */ new Set([
|
|
22686
|
+
"devtools_execute_js",
|
|
22687
|
+
"devtools_modify_dom",
|
|
22688
|
+
"devtools_set_storage"
|
|
22689
|
+
]);
|
|
22690
|
+
let stateListener = null;
|
|
22691
|
+
const activityLog = [];
|
|
22692
|
+
const MAX_ACTIVITY_ENTRIES = 100;
|
|
22693
|
+
let activityCounter = 0;
|
|
22694
|
+
function setDevToolsPanelListener(listener) {
|
|
22695
|
+
stateListener = listener;
|
|
22172
22696
|
}
|
|
22173
|
-
function
|
|
22174
|
-
const
|
|
22175
|
-
|
|
22176
|
-
|
|
22177
|
-
|
|
22178
|
-
|
|
22179
|
-
|
|
22180
|
-
|
|
22181
|
-
throw new Error("Vault note folders cannot traverse outside the vault.");
|
|
22182
|
-
}
|
|
22183
|
-
return segments.join(path$1.sep);
|
|
22697
|
+
function getDevToolsPanelState(tabId) {
|
|
22698
|
+
const session = tabId ? getSession(tabId) : void 0;
|
|
22699
|
+
return {
|
|
22700
|
+
console: session?.getConsoleLogs() ?? [],
|
|
22701
|
+
network: session?.getNetworkLog() ?? [],
|
|
22702
|
+
errors: session?.getErrors() ?? [],
|
|
22703
|
+
activity: activityLog
|
|
22704
|
+
};
|
|
22184
22705
|
}
|
|
22185
|
-
function
|
|
22186
|
-
|
|
22187
|
-
|
|
22188
|
-
|
|
22706
|
+
function broadcastState(tabManager) {
|
|
22707
|
+
if (!stateListener) return;
|
|
22708
|
+
const tabId = tabManager.getActiveTabId();
|
|
22709
|
+
stateListener(getDevToolsPanelState(tabId));
|
|
22710
|
+
}
|
|
22711
|
+
async function withDevToolsAction(runtime2, tabManager, name, args, executor) {
|
|
22712
|
+
try {
|
|
22713
|
+
assertFeatureUnlocked("devtools", "DevTools");
|
|
22714
|
+
} catch (error) {
|
|
22715
|
+
return asTextResponse$1(
|
|
22716
|
+
`Error: ${error instanceof Error ? error.message : "DevTools require Vessel Premium."}`
|
|
22717
|
+
);
|
|
22189
22718
|
}
|
|
22190
|
-
|
|
22191
|
-
|
|
22719
|
+
const activityEntry = {
|
|
22720
|
+
id: ++activityCounter,
|
|
22721
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
22722
|
+
tool: name,
|
|
22723
|
+
args: JSON.stringify(args).slice(0, 200),
|
|
22724
|
+
result: "",
|
|
22725
|
+
durationMs: 0,
|
|
22726
|
+
status: "running"
|
|
22727
|
+
};
|
|
22728
|
+
activityLog.push(activityEntry);
|
|
22729
|
+
if (activityLog.length > MAX_ACTIVITY_ENTRIES) {
|
|
22730
|
+
activityLog.splice(0, activityLog.length - MAX_ACTIVITY_ENTRIES);
|
|
22192
22731
|
}
|
|
22193
|
-
|
|
22194
|
-
|
|
22195
|
-
|
|
22732
|
+
broadcastState(tabManager);
|
|
22733
|
+
const startTime = Date.now();
|
|
22734
|
+
try {
|
|
22735
|
+
const result = await runtime2.runControlledAction({
|
|
22736
|
+
source: "mcp",
|
|
22737
|
+
name,
|
|
22738
|
+
args,
|
|
22739
|
+
tabId: tabManager.getActiveTabId(),
|
|
22740
|
+
dangerous: DANGEROUS_DEVTOOLS_ACTIONS.has(name),
|
|
22741
|
+
executor
|
|
22742
|
+
});
|
|
22743
|
+
activityEntry.status = "completed";
|
|
22744
|
+
activityEntry.result = result.slice(0, 200);
|
|
22745
|
+
activityEntry.durationMs = Date.now() - startTime;
|
|
22746
|
+
broadcastState(tabManager);
|
|
22747
|
+
return asTextResponse$1(result);
|
|
22748
|
+
} catch (error) {
|
|
22749
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
22750
|
+
activityEntry.status = "failed";
|
|
22751
|
+
activityEntry.result = message.slice(0, 200);
|
|
22752
|
+
activityEntry.durationMs = Date.now() - startTime;
|
|
22753
|
+
broadcastState(tabManager);
|
|
22754
|
+
return asTextResponse$1(`Error: ${message}`);
|
|
22196
22755
|
}
|
|
22197
|
-
const normalized = segments.join(path$1.sep);
|
|
22198
|
-
return normalized.endsWith(".md") ? normalized : `${normalized}.md`;
|
|
22199
|
-
}
|
|
22200
|
-
function escapeYaml(value) {
|
|
22201
|
-
return JSON.stringify(value);
|
|
22202
22756
|
}
|
|
22203
|
-
function
|
|
22204
|
-
|
|
22205
|
-
|
|
22206
|
-
|
|
22207
|
-
|
|
22208
|
-
|
|
22209
|
-
|
|
22210
|
-
|
|
22211
|
-
|
|
22757
|
+
function registerDevTools(server, tabManager, runtime2) {
|
|
22758
|
+
server.registerTool(
|
|
22759
|
+
"vessel_devtools_console_logs",
|
|
22760
|
+
{
|
|
22761
|
+
title: "DevTools: Get Console Logs",
|
|
22762
|
+
description: "Get console log entries captured from the active tab. Returns a rolling buffer of recent console.log, console.warn, console.error, etc. calls. Automatically starts capturing on first use.",
|
|
22763
|
+
inputSchema: {
|
|
22764
|
+
level: zod.z.enum(["log", "warning", "error", "info", "debug", "verbose"]).optional().describe("Filter by log level"),
|
|
22765
|
+
limit: zod.z.number().optional().describe("Maximum number of entries to return (default: all)"),
|
|
22766
|
+
search: zod.z.string().optional().describe("Filter entries containing this text (case-insensitive)")
|
|
22212
22767
|
}
|
|
22213
|
-
|
|
22768
|
+
},
|
|
22769
|
+
async ({ level, limit, search: search2 }) => {
|
|
22770
|
+
return withDevToolsAction(
|
|
22771
|
+
runtime2,
|
|
22772
|
+
tabManager,
|
|
22773
|
+
"devtools_console_logs",
|
|
22774
|
+
{ level, limit, search: search2 },
|
|
22775
|
+
async () => {
|
|
22776
|
+
const session = getOrCreateSession(tabManager);
|
|
22777
|
+
await session.ensureConsoleDomain();
|
|
22778
|
+
const entries = session.getConsoleLogs({ level, limit, search: search2 });
|
|
22779
|
+
if (entries.length === 0) {
|
|
22780
|
+
return "No console entries captured yet. Console monitoring is now active — new entries will be captured as they occur.";
|
|
22781
|
+
}
|
|
22782
|
+
return JSON.stringify(entries, null, 2);
|
|
22783
|
+
}
|
|
22784
|
+
);
|
|
22214
22785
|
}
|
|
22215
|
-
|
|
22216
|
-
|
|
22217
|
-
|
|
22218
|
-
|
|
22219
|
-
|
|
22220
|
-
|
|
22221
|
-
|
|
22222
|
-
|
|
22223
|
-
|
|
22224
|
-
|
|
22225
|
-
|
|
22226
|
-
|
|
22227
|
-
|
|
22228
|
-
|
|
22229
|
-
|
|
22230
|
-
|
|
22231
|
-
|
|
22232
|
-
|
|
22233
|
-
}
|
|
22234
|
-
return path$1.join(dir, candidate);
|
|
22235
|
-
}
|
|
22236
|
-
function trimContent(content, limit = PAGE_CONTENT_LIMIT) {
|
|
22237
|
-
const cleaned = content.trim();
|
|
22238
|
-
if (cleaned.length <= limit) return cleaned;
|
|
22239
|
-
return `${cleaned.slice(0, limit)}
|
|
22240
|
-
|
|
22241
|
-
[Truncated]`;
|
|
22242
|
-
}
|
|
22243
|
-
function parseFrontmatter(content) {
|
|
22244
|
-
if (!content.startsWith("---\n")) {
|
|
22245
|
-
return { body: content, tags: [] };
|
|
22246
|
-
}
|
|
22247
|
-
const closingIndex = content.indexOf("\n---\n", 4);
|
|
22248
|
-
if (closingIndex === -1) {
|
|
22249
|
-
return { body: content, tags: [] };
|
|
22250
|
-
}
|
|
22251
|
-
const raw = content.slice(4, closingIndex);
|
|
22252
|
-
const body = content.slice(closingIndex + 5);
|
|
22253
|
-
const lines = raw.split("\n");
|
|
22254
|
-
const result = { tags: [] };
|
|
22255
|
-
let activeArrayKey = "";
|
|
22256
|
-
for (const line of lines) {
|
|
22257
|
-
const trimmed = line.trim();
|
|
22258
|
-
if (!trimmed) continue;
|
|
22259
|
-
if (trimmed.startsWith("- ") && activeArrayKey === "tags") {
|
|
22260
|
-
result.tags.push(
|
|
22261
|
-
trimmed.slice(2).trim().replace(/^["']|["']$/g, "")
|
|
22786
|
+
);
|
|
22787
|
+
server.registerTool(
|
|
22788
|
+
"vessel_devtools_console_clear",
|
|
22789
|
+
{
|
|
22790
|
+
title: "DevTools: Clear Console Logs",
|
|
22791
|
+
description: "Clear the captured console log buffer for the active tab."
|
|
22792
|
+
},
|
|
22793
|
+
async () => {
|
|
22794
|
+
return withDevToolsAction(
|
|
22795
|
+
runtime2,
|
|
22796
|
+
tabManager,
|
|
22797
|
+
"devtools_console_clear",
|
|
22798
|
+
{},
|
|
22799
|
+
async () => {
|
|
22800
|
+
const session = getOrCreateSession(tabManager);
|
|
22801
|
+
const count = session.clearConsoleLogs();
|
|
22802
|
+
return `Cleared ${count} console entries.`;
|
|
22803
|
+
}
|
|
22262
22804
|
);
|
|
22263
|
-
continue;
|
|
22264
22805
|
}
|
|
22265
|
-
|
|
22266
|
-
|
|
22267
|
-
|
|
22268
|
-
|
|
22269
|
-
|
|
22270
|
-
|
|
22271
|
-
|
|
22272
|
-
|
|
22273
|
-
|
|
22274
|
-
|
|
22275
|
-
|
|
22276
|
-
|
|
22277
|
-
activeArrayKey = "";
|
|
22806
|
+
);
|
|
22807
|
+
server.registerTool(
|
|
22808
|
+
"vessel_devtools_network_log",
|
|
22809
|
+
{
|
|
22810
|
+
title: "DevTools: Get Network Log",
|
|
22811
|
+
description: "Get captured network requests/responses from the active tab. Returns method, URL, status, timing, headers, and size. Automatically starts capturing on first use.",
|
|
22812
|
+
inputSchema: {
|
|
22813
|
+
url_pattern: zod.z.string().optional().describe("Filter by URL pattern (regex or substring match)"),
|
|
22814
|
+
method: zod.z.string().optional().describe("Filter by HTTP method (GET, POST, etc.)"),
|
|
22815
|
+
status_min: zod.z.number().optional().describe("Minimum HTTP status code (e.g., 400 for errors)"),
|
|
22816
|
+
status_max: zod.z.number().optional().describe("Maximum HTTP status code"),
|
|
22817
|
+
limit: zod.z.number().optional().describe("Maximum number of entries to return (default: all)")
|
|
22278
22818
|
}
|
|
22819
|
+
},
|
|
22820
|
+
async ({ url_pattern, method, status_min, status_max, limit }) => {
|
|
22821
|
+
return withDevToolsAction(
|
|
22822
|
+
runtime2,
|
|
22823
|
+
tabManager,
|
|
22824
|
+
"devtools_network_log",
|
|
22825
|
+
{ url_pattern, method, status_min, status_max, limit },
|
|
22826
|
+
async () => {
|
|
22827
|
+
const session = getOrCreateSession(tabManager);
|
|
22828
|
+
await session.ensureNetworkDomain();
|
|
22829
|
+
const entries = session.getNetworkLog({
|
|
22830
|
+
urlPattern: url_pattern,
|
|
22831
|
+
method,
|
|
22832
|
+
statusRange: status_min != null || status_max != null ? { min: status_min, max: status_max } : void 0,
|
|
22833
|
+
limit
|
|
22834
|
+
});
|
|
22835
|
+
if (entries.length === 0) {
|
|
22836
|
+
return "No network requests captured yet. Network monitoring is now active — new requests will be captured as they occur.";
|
|
22837
|
+
}
|
|
22838
|
+
return JSON.stringify(entries, null, 2);
|
|
22839
|
+
}
|
|
22840
|
+
);
|
|
22279
22841
|
}
|
|
22280
|
-
}
|
|
22281
|
-
return { body, title: result.title, tags: result.tags };
|
|
22282
|
-
}
|
|
22283
|
-
function collectMarkdownFiles(dir) {
|
|
22284
|
-
const entries = fs$1.readdirSync(dir, { withFileTypes: true });
|
|
22285
|
-
const files = [];
|
|
22286
|
-
for (const entry of entries) {
|
|
22287
|
-
const absolutePath = path$1.join(dir, entry.name);
|
|
22288
|
-
if (entry.isDirectory()) {
|
|
22289
|
-
files.push(...collectMarkdownFiles(absolutePath));
|
|
22290
|
-
continue;
|
|
22291
|
-
}
|
|
22292
|
-
if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
|
|
22293
|
-
files.push(absolutePath);
|
|
22294
|
-
}
|
|
22295
|
-
}
|
|
22296
|
-
return files;
|
|
22297
|
-
}
|
|
22298
|
-
function toSummary(absolutePath, vaultRoot) {
|
|
22299
|
-
const stats = fs$1.statSync(absolutePath);
|
|
22300
|
-
const relativePath = path$1.relative(vaultRoot, absolutePath).split(path$1.sep).join("/");
|
|
22301
|
-
const raw = fs$1.readFileSync(absolutePath, "utf-8");
|
|
22302
|
-
const parsed = parseFrontmatter(raw);
|
|
22303
|
-
const headingMatch = parsed.body.match(/^#\s+(.+)$/m);
|
|
22304
|
-
const title = parsed.title || headingMatch?.[1]?.trim() || path$1.basename(absolutePath, ".md");
|
|
22305
|
-
return {
|
|
22306
|
-
title,
|
|
22307
|
-
absolutePath,
|
|
22308
|
-
relativePath,
|
|
22309
|
-
modifiedAt: stats.mtime.toISOString(),
|
|
22310
|
-
tags: parsed.tags
|
|
22311
|
-
};
|
|
22312
|
-
}
|
|
22313
|
-
function renderBookmarkLinkBlock(bookmark, note) {
|
|
22314
|
-
const lines = [
|
|
22315
|
-
"## Linked Bookmark",
|
|
22316
|
-
"",
|
|
22317
|
-
`- Bookmark ID: \`${bookmark.id}\``,
|
|
22318
|
-
`- Title: ${bookmark.title || bookmark.url}`,
|
|
22319
|
-
`- URL: [${bookmark.url}](${bookmark.url})`,
|
|
22320
|
-
`- Saved At: ${bookmark.savedAt}`
|
|
22321
|
-
];
|
|
22322
|
-
if (note?.trim()) {
|
|
22323
|
-
lines.push("", "### Context", "", note.trim());
|
|
22324
|
-
}
|
|
22325
|
-
return `${lines.join("\n")}
|
|
22326
|
-
`;
|
|
22327
|
-
}
|
|
22328
|
-
function writeMemoryNote({
|
|
22329
|
-
title,
|
|
22330
|
-
body,
|
|
22331
|
-
folder,
|
|
22332
|
-
tags = [],
|
|
22333
|
-
frontmatter = {}
|
|
22334
|
-
}) {
|
|
22335
|
-
const vaultRoot = getVaultRoot();
|
|
22336
|
-
const relativeFolder = normalizeFolder(folder, DEFAULT_NOTE_FOLDER);
|
|
22337
|
-
const targetDir = path$1.join(vaultRoot, relativeFolder);
|
|
22338
|
-
fs$1.mkdirSync(targetDir, { recursive: true });
|
|
22339
|
-
const absolutePath = buildUniqueNotePath(targetDir, title);
|
|
22340
|
-
const relativePath = path$1.relative(vaultRoot, absolutePath);
|
|
22341
|
-
const content = [
|
|
22342
|
-
renderFrontmatter({
|
|
22343
|
-
title,
|
|
22344
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
22345
|
-
tags,
|
|
22346
|
-
...frontmatter
|
|
22347
|
-
}),
|
|
22348
|
-
body.trim(),
|
|
22349
|
-
""
|
|
22350
|
-
].join("\n");
|
|
22351
|
-
fs$1.writeFileSync(absolutePath, content, "utf-8");
|
|
22352
|
-
return {
|
|
22353
|
-
title,
|
|
22354
|
-
absolutePath,
|
|
22355
|
-
relativePath: relativePath.split(path$1.sep).join("/")
|
|
22356
|
-
};
|
|
22357
|
-
}
|
|
22358
|
-
function appendToMemoryNote({
|
|
22359
|
-
notePath,
|
|
22360
|
-
content,
|
|
22361
|
-
heading
|
|
22362
|
-
}) {
|
|
22363
|
-
const vaultRoot = getVaultRoot();
|
|
22364
|
-
const relativePath = normalizeNotePath(notePath);
|
|
22365
|
-
const absolutePath = assertInsideVault(
|
|
22366
|
-
path$1.join(vaultRoot, relativePath),
|
|
22367
|
-
vaultRoot
|
|
22368
22842
|
);
|
|
22369
|
-
if (!fs$1.existsSync(absolutePath)) {
|
|
22370
|
-
throw new Error(
|
|
22371
|
-
`Memory note not found: ${relativePath.split(path$1.sep).join("/")}`
|
|
22372
|
-
);
|
|
22373
|
-
}
|
|
22374
|
-
const current = fs$1.readFileSync(absolutePath, "utf-8").trimEnd();
|
|
22375
|
-
const nextParts = [current, ""];
|
|
22376
|
-
if (heading?.trim()) {
|
|
22377
|
-
nextParts.push(`## ${heading.trim()}`, "");
|
|
22378
|
-
}
|
|
22379
|
-
nextParts.push(content.trim(), "");
|
|
22380
|
-
fs$1.writeFileSync(absolutePath, nextParts.join("\n"), "utf-8");
|
|
22381
|
-
return {
|
|
22382
|
-
title: path$1.basename(absolutePath, ".md"),
|
|
22383
|
-
absolutePath,
|
|
22384
|
-
relativePath: relativePath.split(path$1.sep).join("/")
|
|
22385
|
-
};
|
|
22386
|
-
}
|
|
22387
|
-
function listMemoryNotes({
|
|
22388
|
-
folder,
|
|
22389
|
-
limit = DEFAULT_LIST_LIMIT
|
|
22390
|
-
} = {}) {
|
|
22391
|
-
const vaultRoot = getVaultRoot();
|
|
22392
|
-
const relativeFolder = normalizeFolder(folder, "");
|
|
22393
|
-
const targetDir = relativeFolder ? path$1.join(vaultRoot, relativeFolder) : vaultRoot;
|
|
22394
|
-
if (!fs$1.existsSync(targetDir)) {
|
|
22395
|
-
return [];
|
|
22396
|
-
}
|
|
22397
|
-
return collectMarkdownFiles(targetDir).map((absolutePath) => toSummary(absolutePath, vaultRoot)).sort((a, b) => b.modifiedAt.localeCompare(a.modifiedAt)).slice(0, Math.max(1, limit));
|
|
22398
|
-
}
|
|
22399
|
-
function searchMemoryNotes({
|
|
22400
|
-
query,
|
|
22401
|
-
folder,
|
|
22402
|
-
tags = [],
|
|
22403
|
-
limit = DEFAULT_SEARCH_LIMIT
|
|
22404
|
-
}) {
|
|
22405
|
-
const loweredQuery = query.trim().toLowerCase();
|
|
22406
|
-
if (!loweredQuery) {
|
|
22407
|
-
throw new Error("A non-empty memory search query is required.");
|
|
22408
|
-
}
|
|
22409
|
-
const vaultRoot = getVaultRoot();
|
|
22410
|
-
const relativeFolder = normalizeFolder(folder, "");
|
|
22411
|
-
const targetDir = relativeFolder ? path$1.join(vaultRoot, relativeFolder) : vaultRoot;
|
|
22412
|
-
if (!fs$1.existsSync(targetDir)) {
|
|
22413
|
-
return [];
|
|
22414
|
-
}
|
|
22415
|
-
const loweredTags = tags.map((tag) => tag.trim().toLowerCase()).filter(Boolean);
|
|
22416
|
-
return collectMarkdownFiles(targetDir).map((absolutePath) => {
|
|
22417
|
-
const raw = fs$1.readFileSync(absolutePath, "utf-8");
|
|
22418
|
-
const parsed = parseFrontmatter(raw);
|
|
22419
|
-
const summary = toSummary(absolutePath, vaultRoot);
|
|
22420
|
-
const haystack = `${summary.title}
|
|
22421
|
-
${summary.relativePath}
|
|
22422
|
-
${parsed.body}`.toLowerCase();
|
|
22423
|
-
const hasQuery = haystack.includes(loweredQuery);
|
|
22424
|
-
const hasTags = loweredTags.length === 0 || loweredTags.every(
|
|
22425
|
-
(tag) => summary.tags.some((noteTag) => noteTag.toLowerCase() === tag)
|
|
22426
|
-
);
|
|
22427
|
-
return hasQuery && hasTags ? summary : null;
|
|
22428
|
-
}).filter((item) => item !== null).sort((a, b) => b.modifiedAt.localeCompare(a.modifiedAt)).slice(0, Math.max(1, limit));
|
|
22429
|
-
}
|
|
22430
|
-
function capturePageToVault({
|
|
22431
|
-
page,
|
|
22432
|
-
title,
|
|
22433
|
-
folder,
|
|
22434
|
-
summary,
|
|
22435
|
-
note,
|
|
22436
|
-
tags = []
|
|
22437
|
-
}) {
|
|
22438
|
-
const noteTitle = title?.trim() || page.title.trim() || page.url;
|
|
22439
|
-
const bodyLines = [
|
|
22440
|
-
`# ${noteTitle}`,
|
|
22441
|
-
"",
|
|
22442
|
-
`Source: [${page.title || page.url}](${page.url})`,
|
|
22443
|
-
`Captured: ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
22444
|
-
];
|
|
22445
|
-
if (page.byline) {
|
|
22446
|
-
bodyLines.push(`Byline: ${page.byline}`);
|
|
22447
|
-
}
|
|
22448
|
-
bodyLines.push("");
|
|
22449
|
-
if (summary?.trim()) {
|
|
22450
|
-
bodyLines.push("## Summary", "", summary.trim(), "");
|
|
22451
|
-
}
|
|
22452
|
-
if (note?.trim()) {
|
|
22453
|
-
bodyLines.push("## Research Note", "", note.trim(), "");
|
|
22454
|
-
}
|
|
22455
|
-
if (page.excerpt.trim()) {
|
|
22456
|
-
bodyLines.push("## Excerpt", "", page.excerpt.trim(), "");
|
|
22457
|
-
}
|
|
22458
|
-
const snapshot2 = trimContent(page.content);
|
|
22459
|
-
if (snapshot2) {
|
|
22460
|
-
bodyLines.push("## Page Snapshot", "", snapshot2, "");
|
|
22461
|
-
}
|
|
22462
|
-
return writeMemoryNote({
|
|
22463
|
-
title: noteTitle,
|
|
22464
|
-
body: bodyLines.join("\n"),
|
|
22465
|
-
folder: folder || DEFAULT_PAGE_FOLDER,
|
|
22466
|
-
tags,
|
|
22467
|
-
frontmatter: {
|
|
22468
|
-
source_url: page.url,
|
|
22469
|
-
source_title: page.title || page.url
|
|
22470
|
-
}
|
|
22471
|
-
});
|
|
22472
|
-
}
|
|
22473
|
-
function linkBookmarkToMemory({
|
|
22474
|
-
bookmark,
|
|
22475
|
-
notePath,
|
|
22476
|
-
title,
|
|
22477
|
-
folder,
|
|
22478
|
-
note,
|
|
22479
|
-
tags = []
|
|
22480
|
-
}) {
|
|
22481
|
-
if (notePath?.trim()) {
|
|
22482
|
-
return appendToMemoryNote({
|
|
22483
|
-
notePath,
|
|
22484
|
-
heading: "Linked Bookmark",
|
|
22485
|
-
content: [
|
|
22486
|
-
`- Bookmark ID: \`${bookmark.id}\``,
|
|
22487
|
-
`- Title: ${bookmark.title || bookmark.url}`,
|
|
22488
|
-
`- URL: [${bookmark.url}](${bookmark.url})`,
|
|
22489
|
-
`- Saved At: ${bookmark.savedAt}`,
|
|
22490
|
-
note?.trim() ? `- Note: ${note.trim()}` : ""
|
|
22491
|
-
].filter(Boolean).join("\n")
|
|
22492
|
-
});
|
|
22493
|
-
}
|
|
22494
|
-
const noteTitle = title?.trim() || bookmark.title || bookmark.url;
|
|
22495
|
-
return writeMemoryNote({
|
|
22496
|
-
title: noteTitle,
|
|
22497
|
-
body: renderBookmarkLinkBlock(bookmark, note),
|
|
22498
|
-
folder: folder || DEFAULT_BOOKMARK_FOLDER,
|
|
22499
|
-
tags,
|
|
22500
|
-
frontmatter: {
|
|
22501
|
-
bookmark_id: bookmark.id,
|
|
22502
|
-
source_url: bookmark.url,
|
|
22503
|
-
source_title: bookmark.title || bookmark.url
|
|
22504
|
-
}
|
|
22505
|
-
});
|
|
22506
|
-
}
|
|
22507
|
-
function asTextResponse$1(text) {
|
|
22508
|
-
return { content: [{ type: "text", text }] };
|
|
22509
|
-
}
|
|
22510
|
-
const DANGEROUS_DEVTOOLS_ACTIONS = /* @__PURE__ */ new Set([
|
|
22511
|
-
"devtools_execute_js",
|
|
22512
|
-
"devtools_modify_dom",
|
|
22513
|
-
"devtools_set_storage"
|
|
22514
|
-
]);
|
|
22515
|
-
let stateListener = null;
|
|
22516
|
-
const activityLog = [];
|
|
22517
|
-
const MAX_ACTIVITY_ENTRIES = 100;
|
|
22518
|
-
let activityCounter = 0;
|
|
22519
|
-
function setDevToolsPanelListener(listener) {
|
|
22520
|
-
stateListener = listener;
|
|
22521
|
-
}
|
|
22522
|
-
function getDevToolsPanelState(tabId) {
|
|
22523
|
-
const session = tabId ? getSession(tabId) : void 0;
|
|
22524
|
-
return {
|
|
22525
|
-
console: session?.getConsoleLogs() ?? [],
|
|
22526
|
-
network: session?.getNetworkLog() ?? [],
|
|
22527
|
-
errors: session?.getErrors() ?? [],
|
|
22528
|
-
activity: activityLog
|
|
22529
|
-
};
|
|
22530
|
-
}
|
|
22531
|
-
function broadcastState(tabManager) {
|
|
22532
|
-
if (!stateListener) return;
|
|
22533
|
-
const tabId = tabManager.getActiveTabId();
|
|
22534
|
-
stateListener(getDevToolsPanelState(tabId));
|
|
22535
|
-
}
|
|
22536
|
-
async function withDevToolsAction(runtime2, tabManager, name, args, executor) {
|
|
22537
|
-
const activityEntry = {
|
|
22538
|
-
id: ++activityCounter,
|
|
22539
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
22540
|
-
tool: name,
|
|
22541
|
-
args: JSON.stringify(args).slice(0, 200),
|
|
22542
|
-
result: "",
|
|
22543
|
-
durationMs: 0,
|
|
22544
|
-
status: "running"
|
|
22545
|
-
};
|
|
22546
|
-
activityLog.push(activityEntry);
|
|
22547
|
-
if (activityLog.length > MAX_ACTIVITY_ENTRIES) {
|
|
22548
|
-
activityLog.splice(0, activityLog.length - MAX_ACTIVITY_ENTRIES);
|
|
22549
|
-
}
|
|
22550
|
-
broadcastState(tabManager);
|
|
22551
|
-
const startTime = Date.now();
|
|
22552
|
-
try {
|
|
22553
|
-
const result = await runtime2.runControlledAction({
|
|
22554
|
-
source: "mcp",
|
|
22555
|
-
name,
|
|
22556
|
-
args,
|
|
22557
|
-
tabId: tabManager.getActiveTabId(),
|
|
22558
|
-
dangerous: DANGEROUS_DEVTOOLS_ACTIONS.has(name),
|
|
22559
|
-
executor
|
|
22560
|
-
});
|
|
22561
|
-
activityEntry.status = "completed";
|
|
22562
|
-
activityEntry.result = result.slice(0, 200);
|
|
22563
|
-
activityEntry.durationMs = Date.now() - startTime;
|
|
22564
|
-
broadcastState(tabManager);
|
|
22565
|
-
return asTextResponse$1(result);
|
|
22566
|
-
} catch (error) {
|
|
22567
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
22568
|
-
activityEntry.status = "failed";
|
|
22569
|
-
activityEntry.result = message.slice(0, 200);
|
|
22570
|
-
activityEntry.durationMs = Date.now() - startTime;
|
|
22571
|
-
broadcastState(tabManager);
|
|
22572
|
-
return asTextResponse$1(`Error: ${message}`);
|
|
22573
|
-
}
|
|
22574
|
-
}
|
|
22575
|
-
function registerDevTools(server, tabManager, runtime2) {
|
|
22576
22843
|
server.registerTool(
|
|
22577
|
-
"
|
|
22844
|
+
"vessel_devtools_network_response_body",
|
|
22578
22845
|
{
|
|
22579
|
-
title: "DevTools: Get
|
|
22580
|
-
description: "Get
|
|
22846
|
+
title: "DevTools: Get Network Response Body",
|
|
22847
|
+
description: "Get the response body for a specific network request by its request ID. Use vessel_devtools_network_log first to find the request ID.",
|
|
22581
22848
|
inputSchema: {
|
|
22582
|
-
|
|
22583
|
-
limit: zod.z.number().optional().describe("Maximum number of entries to return (default: all)"),
|
|
22584
|
-
search: zod.z.string().optional().describe("Filter entries containing this text (case-insensitive)")
|
|
22849
|
+
request_id: zod.z.string().describe("The requestId from a network log entry")
|
|
22585
22850
|
}
|
|
22586
22851
|
},
|
|
22587
|
-
async ({
|
|
22852
|
+
async ({ request_id }) => {
|
|
22588
22853
|
return withDevToolsAction(
|
|
22589
22854
|
runtime2,
|
|
22590
22855
|
tabManager,
|
|
22591
|
-
"
|
|
22592
|
-
{
|
|
22593
|
-
async () => {
|
|
22594
|
-
const session = getOrCreateSession(tabManager);
|
|
22595
|
-
await session.ensureConsoleDomain();
|
|
22596
|
-
const entries = session.getConsoleLogs({ level, limit, search: search2 });
|
|
22597
|
-
if (entries.length === 0) {
|
|
22598
|
-
return "No console entries captured yet. Console monitoring is now active — new entries will be captured as they occur.";
|
|
22599
|
-
}
|
|
22600
|
-
return JSON.stringify(entries, null, 2);
|
|
22601
|
-
}
|
|
22602
|
-
);
|
|
22603
|
-
}
|
|
22604
|
-
);
|
|
22605
|
-
server.registerTool(
|
|
22606
|
-
"vessel_devtools_console_clear",
|
|
22607
|
-
{
|
|
22608
|
-
title: "DevTools: Clear Console Logs",
|
|
22609
|
-
description: "Clear the captured console log buffer for the active tab."
|
|
22610
|
-
},
|
|
22611
|
-
async () => {
|
|
22612
|
-
return withDevToolsAction(
|
|
22613
|
-
runtime2,
|
|
22614
|
-
tabManager,
|
|
22615
|
-
"devtools_console_clear",
|
|
22616
|
-
{},
|
|
22617
|
-
async () => {
|
|
22618
|
-
const session = getOrCreateSession(tabManager);
|
|
22619
|
-
const count = session.clearConsoleLogs();
|
|
22620
|
-
return `Cleared ${count} console entries.`;
|
|
22621
|
-
}
|
|
22622
|
-
);
|
|
22623
|
-
}
|
|
22624
|
-
);
|
|
22625
|
-
server.registerTool(
|
|
22626
|
-
"vessel_devtools_network_log",
|
|
22627
|
-
{
|
|
22628
|
-
title: "DevTools: Get Network Log",
|
|
22629
|
-
description: "Get captured network requests/responses from the active tab. Returns method, URL, status, timing, headers, and size. Automatically starts capturing on first use.",
|
|
22630
|
-
inputSchema: {
|
|
22631
|
-
url_pattern: zod.z.string().optional().describe("Filter by URL pattern (regex or substring match)"),
|
|
22632
|
-
method: zod.z.string().optional().describe("Filter by HTTP method (GET, POST, etc.)"),
|
|
22633
|
-
status_min: zod.z.number().optional().describe("Minimum HTTP status code (e.g., 400 for errors)"),
|
|
22634
|
-
status_max: zod.z.number().optional().describe("Maximum HTTP status code"),
|
|
22635
|
-
limit: zod.z.number().optional().describe("Maximum number of entries to return (default: all)")
|
|
22636
|
-
}
|
|
22637
|
-
},
|
|
22638
|
-
async ({ url_pattern, method, status_min, status_max, limit }) => {
|
|
22639
|
-
return withDevToolsAction(
|
|
22640
|
-
runtime2,
|
|
22641
|
-
tabManager,
|
|
22642
|
-
"devtools_network_log",
|
|
22643
|
-
{ url_pattern, method, status_min, status_max, limit },
|
|
22644
|
-
async () => {
|
|
22645
|
-
const session = getOrCreateSession(tabManager);
|
|
22646
|
-
await session.ensureNetworkDomain();
|
|
22647
|
-
const entries = session.getNetworkLog({
|
|
22648
|
-
urlPattern: url_pattern,
|
|
22649
|
-
method,
|
|
22650
|
-
statusRange: status_min != null || status_max != null ? { min: status_min, max: status_max } : void 0,
|
|
22651
|
-
limit
|
|
22652
|
-
});
|
|
22653
|
-
if (entries.length === 0) {
|
|
22654
|
-
return "No network requests captured yet. Network monitoring is now active — new requests will be captured as they occur.";
|
|
22655
|
-
}
|
|
22656
|
-
return JSON.stringify(entries, null, 2);
|
|
22657
|
-
}
|
|
22658
|
-
);
|
|
22659
|
-
}
|
|
22660
|
-
);
|
|
22661
|
-
server.registerTool(
|
|
22662
|
-
"vessel_devtools_network_response_body",
|
|
22663
|
-
{
|
|
22664
|
-
title: "DevTools: Get Network Response Body",
|
|
22665
|
-
description: "Get the response body for a specific network request by its request ID. Use vessel_devtools_network_log first to find the request ID.",
|
|
22666
|
-
inputSchema: {
|
|
22667
|
-
request_id: zod.z.string().describe("The requestId from a network log entry")
|
|
22668
|
-
}
|
|
22669
|
-
},
|
|
22670
|
-
async ({ request_id }) => {
|
|
22671
|
-
return withDevToolsAction(
|
|
22672
|
-
runtime2,
|
|
22673
|
-
tabManager,
|
|
22674
|
-
"devtools_network_response_body",
|
|
22675
|
-
{ request_id },
|
|
22856
|
+
"devtools_network_response_body",
|
|
22857
|
+
{ request_id },
|
|
22676
22858
|
async () => {
|
|
22677
22859
|
const session = getOrCreateSession(tabManager);
|
|
22678
22860
|
const result = await session.getNetworkResponseBody(request_id);
|
|
@@ -22914,28 +23096,403 @@ Exception: ${result.exceptionDetails}`);
|
|
|
22914
23096
|
return JSON.stringify(entries, null, 2);
|
|
22915
23097
|
}
|
|
22916
23098
|
);
|
|
22917
|
-
}
|
|
23099
|
+
}
|
|
23100
|
+
);
|
|
23101
|
+
server.registerTool(
|
|
23102
|
+
"vessel_devtools_clear_errors",
|
|
23103
|
+
{
|
|
23104
|
+
title: "DevTools: Clear Errors",
|
|
23105
|
+
description: "Clear the captured error buffer for the active tab."
|
|
23106
|
+
},
|
|
23107
|
+
async () => {
|
|
23108
|
+
return withDevToolsAction(
|
|
23109
|
+
runtime2,
|
|
23110
|
+
tabManager,
|
|
23111
|
+
"devtools_clear_errors",
|
|
23112
|
+
{},
|
|
23113
|
+
async () => {
|
|
23114
|
+
const session = getOrCreateSession(tabManager);
|
|
23115
|
+
const count = session.clearErrors();
|
|
23116
|
+
return `Cleared ${count} error entries.`;
|
|
23117
|
+
}
|
|
23118
|
+
);
|
|
23119
|
+
}
|
|
23120
|
+
);
|
|
23121
|
+
}
|
|
23122
|
+
const DEFAULT_PAGE_FOLDER = "Vessel/Pages";
|
|
23123
|
+
const DEFAULT_NOTE_FOLDER = "Vessel/Research";
|
|
23124
|
+
const DEFAULT_BOOKMARK_FOLDER = "Vessel/Bookmarks";
|
|
23125
|
+
const PAGE_CONTENT_LIMIT = 6e3;
|
|
23126
|
+
const DEFAULT_LIST_LIMIT = 50;
|
|
23127
|
+
const DEFAULT_SEARCH_LIMIT = 20;
|
|
23128
|
+
const { access: access$1, mkdir: mkdir$1, readFile: readFile$1, readdir: readdir$1, stat, writeFile: writeFile$1 } = fs$1.promises;
|
|
23129
|
+
function getVaultRoot() {
|
|
23130
|
+
const configured = loadSettings().obsidianVaultPath.trim();
|
|
23131
|
+
if (!configured) {
|
|
23132
|
+
throw new Error(
|
|
23133
|
+
"Obsidian not configured. Set vault path in Vessel settings to use memory capture."
|
|
23134
|
+
);
|
|
23135
|
+
}
|
|
23136
|
+
return path$1.resolve(configured);
|
|
23137
|
+
}
|
|
23138
|
+
function assertInsideVault(targetPath, vaultRoot) {
|
|
23139
|
+
const resolved = path$1.resolve(targetPath);
|
|
23140
|
+
const relative = path$1.relative(vaultRoot, resolved);
|
|
23141
|
+
if (relative.startsWith("..") || path$1.isAbsolute(relative)) {
|
|
23142
|
+
throw new Error("Resolved note path is outside the configured vault.");
|
|
23143
|
+
}
|
|
23144
|
+
return resolved;
|
|
23145
|
+
}
|
|
23146
|
+
function normalizeFolder(folder, fallback) {
|
|
23147
|
+
const raw = (folder?.trim() || fallback).replace(/\\/g, "/");
|
|
23148
|
+
if (!raw) return fallback;
|
|
23149
|
+
if (path$1.isAbsolute(raw)) {
|
|
23150
|
+
throw new Error("Vault note folders must be relative to the vault root.");
|
|
23151
|
+
}
|
|
23152
|
+
const segments = raw.split("/").filter(Boolean);
|
|
23153
|
+
if (segments.some((segment) => segment === "." || segment === "..")) {
|
|
23154
|
+
throw new Error("Vault note folders cannot traverse outside the vault.");
|
|
23155
|
+
}
|
|
23156
|
+
return segments.join(path$1.sep);
|
|
23157
|
+
}
|
|
23158
|
+
function normalizeNotePath(notePath) {
|
|
23159
|
+
const raw = notePath.trim().replace(/\\/g, "/");
|
|
23160
|
+
if (!raw) {
|
|
23161
|
+
throw new Error("A note path is required.");
|
|
23162
|
+
}
|
|
23163
|
+
if (path$1.isAbsolute(raw)) {
|
|
23164
|
+
throw new Error("Note paths must be relative to the vault root.");
|
|
23165
|
+
}
|
|
23166
|
+
const segments = raw.split("/").filter(Boolean);
|
|
23167
|
+
if (segments.some((segment) => segment === "." || segment === "..")) {
|
|
23168
|
+
throw new Error("Note paths cannot traverse outside the vault.");
|
|
23169
|
+
}
|
|
23170
|
+
const normalized = segments.join(path$1.sep);
|
|
23171
|
+
return normalized.endsWith(".md") ? normalized : `${normalized}.md`;
|
|
23172
|
+
}
|
|
23173
|
+
function escapeYaml(value) {
|
|
23174
|
+
return JSON.stringify(value);
|
|
23175
|
+
}
|
|
23176
|
+
function renderFrontmatter(data) {
|
|
23177
|
+
const lines = ["---"];
|
|
23178
|
+
for (const [key2, value] of Object.entries(data)) {
|
|
23179
|
+
if (value == null) continue;
|
|
23180
|
+
if (Array.isArray(value)) {
|
|
23181
|
+
if (value.length === 0) continue;
|
|
23182
|
+
lines.push(`${key2}:`);
|
|
23183
|
+
for (const item of value) {
|
|
23184
|
+
lines.push(` - ${escapeYaml(item)}`);
|
|
23185
|
+
}
|
|
23186
|
+
continue;
|
|
23187
|
+
}
|
|
23188
|
+
lines.push(`${key2}: ${escapeYaml(value)}`);
|
|
23189
|
+
}
|
|
23190
|
+
lines.push("---", "");
|
|
23191
|
+
return lines.join("\n");
|
|
23192
|
+
}
|
|
23193
|
+
function slugify(value) {
|
|
23194
|
+
const normalized = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
23195
|
+
return normalized || "note";
|
|
23196
|
+
}
|
|
23197
|
+
async function pathExists$1(filePath2) {
|
|
23198
|
+
try {
|
|
23199
|
+
await access$1(filePath2);
|
|
23200
|
+
return true;
|
|
23201
|
+
} catch {
|
|
23202
|
+
return false;
|
|
23203
|
+
}
|
|
23204
|
+
}
|
|
23205
|
+
async function buildUniqueNotePath(dir, title) {
|
|
23206
|
+
const datePrefix = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
23207
|
+
const slug = slugify(title);
|
|
23208
|
+
const base = `${datePrefix}-${slug}`;
|
|
23209
|
+
let candidate = `${base}.md`;
|
|
23210
|
+
let counter = 2;
|
|
23211
|
+
while (await pathExists$1(path$1.join(dir, candidate))) {
|
|
23212
|
+
candidate = `${base}-${counter}.md`;
|
|
23213
|
+
counter += 1;
|
|
23214
|
+
}
|
|
23215
|
+
return path$1.join(dir, candidate);
|
|
23216
|
+
}
|
|
23217
|
+
function trimContent(content, limit = PAGE_CONTENT_LIMIT) {
|
|
23218
|
+
const cleaned = content.trim();
|
|
23219
|
+
if (cleaned.length <= limit) return cleaned;
|
|
23220
|
+
return `${cleaned.slice(0, limit)}
|
|
23221
|
+
|
|
23222
|
+
[Truncated]`;
|
|
23223
|
+
}
|
|
23224
|
+
function parseFrontmatter(content) {
|
|
23225
|
+
if (!content.startsWith("---\n")) {
|
|
23226
|
+
return { body: content, tags: [] };
|
|
23227
|
+
}
|
|
23228
|
+
const closingIndex = content.indexOf("\n---\n", 4);
|
|
23229
|
+
if (closingIndex === -1) {
|
|
23230
|
+
return { body: content, tags: [] };
|
|
23231
|
+
}
|
|
23232
|
+
const raw = content.slice(4, closingIndex);
|
|
23233
|
+
const body = content.slice(closingIndex + 5);
|
|
23234
|
+
const lines = raw.split("\n");
|
|
23235
|
+
const result = { tags: [] };
|
|
23236
|
+
let activeArrayKey = "";
|
|
23237
|
+
for (const line of lines) {
|
|
23238
|
+
const trimmed = line.trim();
|
|
23239
|
+
if (!trimmed) continue;
|
|
23240
|
+
if (trimmed.startsWith("- ") && activeArrayKey === "tags") {
|
|
23241
|
+
result.tags.push(
|
|
23242
|
+
trimmed.slice(2).trim().replace(/^["']|["']$/g, "")
|
|
23243
|
+
);
|
|
23244
|
+
continue;
|
|
23245
|
+
}
|
|
23246
|
+
activeArrayKey = "";
|
|
23247
|
+
const separatorIndex = trimmed.indexOf(":");
|
|
23248
|
+
if (separatorIndex === -1) continue;
|
|
23249
|
+
const key2 = trimmed.slice(0, separatorIndex).trim();
|
|
23250
|
+
const value = trimmed.slice(separatorIndex + 1).trim();
|
|
23251
|
+
if (key2 === "title" && value) {
|
|
23252
|
+
result.title = value.replace(/^["']|["']$/g, "");
|
|
23253
|
+
} else if (key2 === "tags") {
|
|
23254
|
+
activeArrayKey = "tags";
|
|
23255
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
23256
|
+
const inline = value.slice(1, -1).split(",").map((item) => item.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
|
|
23257
|
+
result.tags.push(...inline);
|
|
23258
|
+
activeArrayKey = "";
|
|
23259
|
+
}
|
|
23260
|
+
}
|
|
23261
|
+
}
|
|
23262
|
+
return { body, title: result.title, tags: result.tags };
|
|
23263
|
+
}
|
|
23264
|
+
async function collectMarkdownFiles(dir) {
|
|
23265
|
+
const entries = await readdir$1(dir, { withFileTypes: true });
|
|
23266
|
+
const nestedFiles = await Promise.all(
|
|
23267
|
+
entries.map(async (entry) => {
|
|
23268
|
+
const absolutePath = path$1.join(dir, entry.name);
|
|
23269
|
+
if (entry.isDirectory()) {
|
|
23270
|
+
return collectMarkdownFiles(absolutePath);
|
|
23271
|
+
}
|
|
23272
|
+
if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
|
|
23273
|
+
return [absolutePath];
|
|
23274
|
+
}
|
|
23275
|
+
return [];
|
|
23276
|
+
})
|
|
23277
|
+
);
|
|
23278
|
+
return nestedFiles.flat();
|
|
23279
|
+
}
|
|
23280
|
+
async function toSummary(absolutePath, vaultRoot, raw) {
|
|
23281
|
+
const stats = await stat(absolutePath);
|
|
23282
|
+
const relativePath = path$1.relative(vaultRoot, absolutePath).split(path$1.sep).join("/");
|
|
23283
|
+
const noteContent = raw ?? await readFile$1(absolutePath, "utf-8");
|
|
23284
|
+
const parsed = parseFrontmatter(noteContent);
|
|
23285
|
+
const headingMatch = parsed.body.match(/^#\s+(.+)$/m);
|
|
23286
|
+
const title = parsed.title || headingMatch?.[1]?.trim() || path$1.basename(absolutePath, ".md");
|
|
23287
|
+
return {
|
|
23288
|
+
title,
|
|
23289
|
+
absolutePath,
|
|
23290
|
+
relativePath,
|
|
23291
|
+
modifiedAt: stats.mtime.toISOString(),
|
|
23292
|
+
tags: parsed.tags
|
|
23293
|
+
};
|
|
23294
|
+
}
|
|
23295
|
+
function renderBookmarkLinkBlock(bookmark, note) {
|
|
23296
|
+
const lines = [
|
|
23297
|
+
"## Linked Bookmark",
|
|
23298
|
+
"",
|
|
23299
|
+
`- Bookmark ID: \`${bookmark.id}\``,
|
|
23300
|
+
`- Title: ${bookmark.title || bookmark.url}`,
|
|
23301
|
+
`- URL: [${bookmark.url}](${bookmark.url})`,
|
|
23302
|
+
`- Saved At: ${bookmark.savedAt}`
|
|
23303
|
+
];
|
|
23304
|
+
if (note?.trim()) {
|
|
23305
|
+
lines.push("", "### Context", "", note.trim());
|
|
23306
|
+
}
|
|
23307
|
+
return `${lines.join("\n")}
|
|
23308
|
+
`;
|
|
23309
|
+
}
|
|
23310
|
+
async function writeMemoryNote({
|
|
23311
|
+
title,
|
|
23312
|
+
body,
|
|
23313
|
+
folder,
|
|
23314
|
+
tags = [],
|
|
23315
|
+
frontmatter = {}
|
|
23316
|
+
}) {
|
|
23317
|
+
const vaultRoot = getVaultRoot();
|
|
23318
|
+
const relativeFolder = normalizeFolder(folder, DEFAULT_NOTE_FOLDER);
|
|
23319
|
+
const targetDir = path$1.join(vaultRoot, relativeFolder);
|
|
23320
|
+
await mkdir$1(targetDir, { recursive: true });
|
|
23321
|
+
const absolutePath = await buildUniqueNotePath(targetDir, title);
|
|
23322
|
+
const relativePath = path$1.relative(vaultRoot, absolutePath);
|
|
23323
|
+
const content = [
|
|
23324
|
+
renderFrontmatter({
|
|
23325
|
+
title,
|
|
23326
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
23327
|
+
tags,
|
|
23328
|
+
...frontmatter
|
|
23329
|
+
}),
|
|
23330
|
+
body.trim(),
|
|
23331
|
+
""
|
|
23332
|
+
].join("\n");
|
|
23333
|
+
await writeFile$1(absolutePath, content, "utf-8");
|
|
23334
|
+
return {
|
|
23335
|
+
title,
|
|
23336
|
+
absolutePath,
|
|
23337
|
+
relativePath: relativePath.split(path$1.sep).join("/")
|
|
23338
|
+
};
|
|
23339
|
+
}
|
|
23340
|
+
async function appendToMemoryNote({
|
|
23341
|
+
notePath,
|
|
23342
|
+
content,
|
|
23343
|
+
heading
|
|
23344
|
+
}) {
|
|
23345
|
+
const vaultRoot = getVaultRoot();
|
|
23346
|
+
const relativePath = normalizeNotePath(notePath);
|
|
23347
|
+
const absolutePath = assertInsideVault(
|
|
23348
|
+
path$1.join(vaultRoot, relativePath),
|
|
23349
|
+
vaultRoot
|
|
23350
|
+
);
|
|
23351
|
+
if (!await pathExists$1(absolutePath)) {
|
|
23352
|
+
throw new Error(
|
|
23353
|
+
`Memory note not found: ${relativePath.split(path$1.sep).join("/")}`
|
|
23354
|
+
);
|
|
23355
|
+
}
|
|
23356
|
+
const current = (await readFile$1(absolutePath, "utf-8")).trimEnd();
|
|
23357
|
+
const nextParts = [current, ""];
|
|
23358
|
+
if (heading?.trim()) {
|
|
23359
|
+
nextParts.push(`## ${heading.trim()}`, "");
|
|
23360
|
+
}
|
|
23361
|
+
nextParts.push(content.trim(), "");
|
|
23362
|
+
await writeFile$1(absolutePath, nextParts.join("\n"), "utf-8");
|
|
23363
|
+
return {
|
|
23364
|
+
title: path$1.basename(absolutePath, ".md"),
|
|
23365
|
+
absolutePath,
|
|
23366
|
+
relativePath: relativePath.split(path$1.sep).join("/")
|
|
23367
|
+
};
|
|
23368
|
+
}
|
|
23369
|
+
async function listMemoryNotes({
|
|
23370
|
+
folder,
|
|
23371
|
+
limit = DEFAULT_LIST_LIMIT
|
|
23372
|
+
} = {}) {
|
|
23373
|
+
const vaultRoot = getVaultRoot();
|
|
23374
|
+
const relativeFolder = normalizeFolder(folder, "");
|
|
23375
|
+
const targetDir = relativeFolder ? path$1.join(vaultRoot, relativeFolder) : vaultRoot;
|
|
23376
|
+
if (!await pathExists$1(targetDir)) {
|
|
23377
|
+
return [];
|
|
23378
|
+
}
|
|
23379
|
+
const notes = await Promise.all(
|
|
23380
|
+
(await collectMarkdownFiles(targetDir)).map(
|
|
23381
|
+
(absolutePath) => toSummary(absolutePath, vaultRoot)
|
|
23382
|
+
)
|
|
23383
|
+
);
|
|
23384
|
+
return notes.sort((a, b) => b.modifiedAt.localeCompare(a.modifiedAt)).slice(0, Math.max(1, limit));
|
|
23385
|
+
}
|
|
23386
|
+
async function searchMemoryNotes({
|
|
23387
|
+
query,
|
|
23388
|
+
folder,
|
|
23389
|
+
tags = [],
|
|
23390
|
+
limit = DEFAULT_SEARCH_LIMIT
|
|
23391
|
+
}) {
|
|
23392
|
+
const loweredQuery = query.trim().toLowerCase();
|
|
23393
|
+
if (!loweredQuery) {
|
|
23394
|
+
throw new Error("A non-empty memory search query is required.");
|
|
23395
|
+
}
|
|
23396
|
+
const vaultRoot = getVaultRoot();
|
|
23397
|
+
const relativeFolder = normalizeFolder(folder, "");
|
|
23398
|
+
const targetDir = relativeFolder ? path$1.join(vaultRoot, relativeFolder) : vaultRoot;
|
|
23399
|
+
if (!await pathExists$1(targetDir)) {
|
|
23400
|
+
return [];
|
|
23401
|
+
}
|
|
23402
|
+
const loweredTags = tags.map((tag) => tag.trim().toLowerCase()).filter(Boolean);
|
|
23403
|
+
const matches = await Promise.all(
|
|
23404
|
+
(await collectMarkdownFiles(targetDir)).map(async (absolutePath) => {
|
|
23405
|
+
const raw = await readFile$1(absolutePath, "utf-8");
|
|
23406
|
+
const parsed = parseFrontmatter(raw);
|
|
23407
|
+
const summary = await toSummary(absolutePath, vaultRoot, raw);
|
|
23408
|
+
const haystack = `${summary.title}
|
|
23409
|
+
${summary.relativePath}
|
|
23410
|
+
${parsed.body}`.toLowerCase();
|
|
23411
|
+
const hasQuery = haystack.includes(loweredQuery);
|
|
23412
|
+
const hasTags = loweredTags.length === 0 || loweredTags.every(
|
|
23413
|
+
(tag) => summary.tags.some((noteTag) => noteTag.toLowerCase() === tag)
|
|
23414
|
+
);
|
|
23415
|
+
return hasQuery && hasTags ? summary : null;
|
|
23416
|
+
})
|
|
22918
23417
|
);
|
|
22919
|
-
|
|
22920
|
-
|
|
22921
|
-
|
|
22922
|
-
|
|
22923
|
-
|
|
22924
|
-
|
|
22925
|
-
|
|
22926
|
-
|
|
22927
|
-
|
|
22928
|
-
|
|
22929
|
-
|
|
22930
|
-
|
|
22931
|
-
|
|
22932
|
-
|
|
22933
|
-
|
|
22934
|
-
|
|
22935
|
-
|
|
22936
|
-
|
|
23418
|
+
return matches.filter((item) => item !== null).sort((a, b) => b.modifiedAt.localeCompare(a.modifiedAt)).slice(0, Math.max(1, limit));
|
|
23419
|
+
}
|
|
23420
|
+
async function capturePageToVault({
|
|
23421
|
+
page,
|
|
23422
|
+
title,
|
|
23423
|
+
folder,
|
|
23424
|
+
summary,
|
|
23425
|
+
note,
|
|
23426
|
+
tags = []
|
|
23427
|
+
}) {
|
|
23428
|
+
const noteTitle = title?.trim() || page.title.trim() || page.url;
|
|
23429
|
+
const bodyLines = [
|
|
23430
|
+
`# ${noteTitle}`,
|
|
23431
|
+
"",
|
|
23432
|
+
`Source: [${page.title || page.url}](${page.url})`,
|
|
23433
|
+
`Captured: ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
23434
|
+
];
|
|
23435
|
+
if (page.byline) {
|
|
23436
|
+
bodyLines.push(`Byline: ${page.byline}`);
|
|
23437
|
+
}
|
|
23438
|
+
bodyLines.push("");
|
|
23439
|
+
if (summary?.trim()) {
|
|
23440
|
+
bodyLines.push("## Summary", "", summary.trim(), "");
|
|
23441
|
+
}
|
|
23442
|
+
if (note?.trim()) {
|
|
23443
|
+
bodyLines.push("## Research Note", "", note.trim(), "");
|
|
23444
|
+
}
|
|
23445
|
+
if (page.excerpt.trim()) {
|
|
23446
|
+
bodyLines.push("## Excerpt", "", page.excerpt.trim(), "");
|
|
23447
|
+
}
|
|
23448
|
+
const snapshot2 = trimContent(page.content);
|
|
23449
|
+
if (snapshot2) {
|
|
23450
|
+
bodyLines.push("## Page Snapshot", "", snapshot2, "");
|
|
23451
|
+
}
|
|
23452
|
+
return await writeMemoryNote({
|
|
23453
|
+
title: noteTitle,
|
|
23454
|
+
body: bodyLines.join("\n"),
|
|
23455
|
+
folder: folder || DEFAULT_PAGE_FOLDER,
|
|
23456
|
+
tags,
|
|
23457
|
+
frontmatter: {
|
|
23458
|
+
source_url: page.url,
|
|
23459
|
+
source_title: page.title || page.url
|
|
22937
23460
|
}
|
|
22938
|
-
);
|
|
23461
|
+
});
|
|
23462
|
+
}
|
|
23463
|
+
async function linkBookmarkToMemory({
|
|
23464
|
+
bookmark,
|
|
23465
|
+
notePath,
|
|
23466
|
+
title,
|
|
23467
|
+
folder,
|
|
23468
|
+
note,
|
|
23469
|
+
tags = []
|
|
23470
|
+
}) {
|
|
23471
|
+
if (notePath?.trim()) {
|
|
23472
|
+
return await appendToMemoryNote({
|
|
23473
|
+
notePath,
|
|
23474
|
+
heading: "Linked Bookmark",
|
|
23475
|
+
content: [
|
|
23476
|
+
`- Bookmark ID: \`${bookmark.id}\``,
|
|
23477
|
+
`- Title: ${bookmark.title || bookmark.url}`,
|
|
23478
|
+
`- URL: [${bookmark.url}](${bookmark.url})`,
|
|
23479
|
+
`- Saved At: ${bookmark.savedAt}`,
|
|
23480
|
+
note?.trim() ? `- Note: ${note.trim()}` : ""
|
|
23481
|
+
].filter(Boolean).join("\n")
|
|
23482
|
+
});
|
|
23483
|
+
}
|
|
23484
|
+
const noteTitle = title?.trim() || bookmark.title || bookmark.url;
|
|
23485
|
+
return await writeMemoryNote({
|
|
23486
|
+
title: noteTitle,
|
|
23487
|
+
body: renderBookmarkLinkBlock(bookmark, note),
|
|
23488
|
+
folder: folder || DEFAULT_BOOKMARK_FOLDER,
|
|
23489
|
+
tags,
|
|
23490
|
+
frontmatter: {
|
|
23491
|
+
bookmark_id: bookmark.id,
|
|
23492
|
+
source_url: bookmark.url,
|
|
23493
|
+
source_title: bookmark.title || bookmark.url
|
|
23494
|
+
}
|
|
23495
|
+
});
|
|
22939
23496
|
}
|
|
22940
23497
|
const logger$f = createLogger("MCP");
|
|
22941
23498
|
function asTextResponse(text) {
|
|
@@ -23016,7 +23573,7 @@ async function getPostActionState(tabManager, name) {
|
|
|
23016
23573
|
if (navActions.includes(name)) {
|
|
23017
23574
|
let warning = "";
|
|
23018
23575
|
try {
|
|
23019
|
-
const page = await extractContent
|
|
23576
|
+
const page = await extractContent(wc);
|
|
23020
23577
|
const issue = getRecoverableAccessIssue(page);
|
|
23021
23578
|
if (issue) {
|
|
23022
23579
|
const blockedUrl = wc.getURL();
|
|
@@ -23682,65 +24239,216 @@ function registerBookmarkTools(server, tabManager, runtime2) {
|
|
|
23682
24239
|
summary: zod.z.string().optional().describe("Optional one-sentence summary for the folder")
|
|
23683
24240
|
}
|
|
23684
24241
|
},
|
|
23685
|
-
async ({ folder_id, new_name, summary }) => {
|
|
24242
|
+
async ({ folder_id, new_name, summary }) => {
|
|
24243
|
+
return withAction(
|
|
24244
|
+
runtime2,
|
|
24245
|
+
tabManager,
|
|
24246
|
+
"rename_bookmark_folder",
|
|
24247
|
+
{ folder_id, new_name, summary },
|
|
24248
|
+
async () => {
|
|
24249
|
+
const existing = findFolderByName(new_name);
|
|
24250
|
+
if (existing && existing.id !== folder_id) {
|
|
24251
|
+
return composeFolderAwareResponse$1(
|
|
24252
|
+
`Folder "${existing.name}" already exists (id=${existing.id})`
|
|
24253
|
+
);
|
|
24254
|
+
}
|
|
24255
|
+
const folder = renameFolder(
|
|
24256
|
+
folder_id,
|
|
24257
|
+
new_name,
|
|
24258
|
+
summary
|
|
24259
|
+
);
|
|
24260
|
+
return folder ? composeFolderAwareResponse$1(`Renamed folder to "${folder.name}"`) : `Folder ${folder_id} not found`;
|
|
24261
|
+
}
|
|
24262
|
+
);
|
|
24263
|
+
}
|
|
24264
|
+
);
|
|
24265
|
+
server.registerTool(
|
|
24266
|
+
"memory_link_bookmark",
|
|
24267
|
+
{
|
|
24268
|
+
title: "Link Bookmark To Memory",
|
|
24269
|
+
description: "Create a note for a bookmark or append bookmark details into an existing memory note.",
|
|
24270
|
+
inputSchema: {
|
|
24271
|
+
bookmark_id: zod.z.string().describe("Bookmark ID to link"),
|
|
24272
|
+
note_path: zod.z.string().optional().describe("Existing relative note path to append into"),
|
|
24273
|
+
title: zod.z.string().optional().describe("Optional title when creating a new note"),
|
|
24274
|
+
folder: zod.z.string().optional().describe("Relative folder when creating a new note"),
|
|
24275
|
+
note: zod.z.string().optional().describe(
|
|
24276
|
+
"Optional rationale or breadcrumb to store with the bookmark"
|
|
24277
|
+
),
|
|
24278
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Optional tags when creating a new note")
|
|
24279
|
+
}
|
|
24280
|
+
},
|
|
24281
|
+
async ({ bookmark_id, note_path, title, folder, note, tags }) => {
|
|
24282
|
+
return withAction(
|
|
24283
|
+
runtime2,
|
|
24284
|
+
tabManager,
|
|
24285
|
+
"memory_link_bookmark",
|
|
24286
|
+
{ bookmark_id, note_path, title, folder, tags },
|
|
24287
|
+
async () => {
|
|
24288
|
+
const bookmark = getBookmark(bookmark_id);
|
|
24289
|
+
if (!bookmark) {
|
|
24290
|
+
return `Bookmark ${bookmark_id} not found`;
|
|
24291
|
+
}
|
|
24292
|
+
const saved = await linkBookmarkToMemory({
|
|
24293
|
+
bookmark,
|
|
24294
|
+
notePath: note_path,
|
|
24295
|
+
title,
|
|
24296
|
+
folder,
|
|
24297
|
+
note,
|
|
24298
|
+
tags
|
|
24299
|
+
});
|
|
24300
|
+
return `Linked bookmark "${bookmark.title}" to memory note ${saved.relativePath}`;
|
|
24301
|
+
}
|
|
24302
|
+
);
|
|
24303
|
+
}
|
|
24304
|
+
);
|
|
24305
|
+
}
|
|
24306
|
+
function registerMemoryTools(server, tabManager, runtime2) {
|
|
24307
|
+
server.registerTool(
|
|
24308
|
+
"memory_note_create",
|
|
24309
|
+
{
|
|
24310
|
+
title: "Create Memory Note",
|
|
24311
|
+
description: "Write a markdown note into the configured Obsidian vault for research notes, breadcrumbs, or synthesis.",
|
|
24312
|
+
inputSchema: {
|
|
24313
|
+
title: zod.z.string().describe("Title of the note"),
|
|
24314
|
+
body: zod.z.string().describe("Markdown body for the note"),
|
|
24315
|
+
folder: zod.z.string().optional().describe(
|
|
24316
|
+
"Relative folder inside the vault (default: Vessel/Research)"
|
|
24317
|
+
),
|
|
24318
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Optional tags to store in frontmatter")
|
|
24319
|
+
}
|
|
24320
|
+
},
|
|
24321
|
+
async ({ title, body, folder, tags }) => {
|
|
24322
|
+
return withAction(
|
|
24323
|
+
runtime2,
|
|
24324
|
+
tabManager,
|
|
24325
|
+
"memory_note_create",
|
|
24326
|
+
{ title, folder, tags },
|
|
24327
|
+
async () => {
|
|
24328
|
+
const saved = await writeMemoryNote({ title, body, folder, tags });
|
|
24329
|
+
return `Saved memory note "${saved.title}" to ${saved.relativePath}`;
|
|
24330
|
+
}
|
|
24331
|
+
);
|
|
24332
|
+
}
|
|
24333
|
+
);
|
|
24334
|
+
server.registerTool(
|
|
24335
|
+
"memory_append",
|
|
24336
|
+
{
|
|
24337
|
+
title: "Append Memory Note",
|
|
24338
|
+
description: "Append markdown content to an existing note in the configured Obsidian vault.",
|
|
24339
|
+
inputSchema: {
|
|
24340
|
+
note_path: zod.z.string().describe("Relative path to an existing note inside the vault"),
|
|
24341
|
+
content: zod.z.string().describe("Markdown content to append"),
|
|
24342
|
+
heading: zod.z.string().optional().describe("Optional section heading to add before the content")
|
|
24343
|
+
}
|
|
24344
|
+
},
|
|
24345
|
+
async ({ note_path, content, heading }) => {
|
|
24346
|
+
return withAction(
|
|
24347
|
+
runtime2,
|
|
24348
|
+
tabManager,
|
|
24349
|
+
"memory_note_append",
|
|
24350
|
+
{ note_path, heading },
|
|
24351
|
+
async () => {
|
|
24352
|
+
const saved = await appendToMemoryNote({
|
|
24353
|
+
notePath: note_path,
|
|
24354
|
+
content,
|
|
24355
|
+
heading
|
|
24356
|
+
});
|
|
24357
|
+
return `Appended memory note at ${saved.relativePath}`;
|
|
24358
|
+
}
|
|
24359
|
+
);
|
|
24360
|
+
}
|
|
24361
|
+
);
|
|
24362
|
+
server.registerTool(
|
|
24363
|
+
"memory_list",
|
|
24364
|
+
{
|
|
24365
|
+
title: "List Memory Notes",
|
|
24366
|
+
description: "List recent markdown notes in the configured Obsidian vault.",
|
|
24367
|
+
inputSchema: {
|
|
24368
|
+
folder: zod.z.string().optional().describe("Optional relative folder inside the vault"),
|
|
24369
|
+
limit: zod.z.number().int().positive().max(200).optional().describe("Maximum number of notes to return")
|
|
24370
|
+
}
|
|
24371
|
+
},
|
|
24372
|
+
async ({ folder, limit }) => {
|
|
24373
|
+
return withAction(
|
|
24374
|
+
runtime2,
|
|
24375
|
+
tabManager,
|
|
24376
|
+
"memory_note_list",
|
|
24377
|
+
{ folder, limit },
|
|
24378
|
+
async () => {
|
|
24379
|
+
const notes = await listMemoryNotes({ folder, limit });
|
|
24380
|
+
if (notes.length === 0) {
|
|
24381
|
+
return "No memory notes found.";
|
|
24382
|
+
}
|
|
24383
|
+
return notes.map(
|
|
24384
|
+
(note) => `- ${note.title} | path=${note.relativePath} | modified=${note.modifiedAt}${note.tags.length ? ` | tags=${note.tags.join(",")}` : ""}`
|
|
24385
|
+
).join("\n");
|
|
24386
|
+
}
|
|
24387
|
+
);
|
|
24388
|
+
}
|
|
24389
|
+
);
|
|
24390
|
+
server.registerTool(
|
|
24391
|
+
"memory_search",
|
|
24392
|
+
{
|
|
24393
|
+
title: "Search Memory Notes",
|
|
24394
|
+
description: "Search markdown notes in the configured Obsidian vault by title, path, body, and optional tags.",
|
|
24395
|
+
inputSchema: {
|
|
24396
|
+
query: zod.z.string().describe("Search query"),
|
|
24397
|
+
folder: zod.z.string().optional().describe("Optional relative folder inside the vault"),
|
|
24398
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Optional tags that matching notes must contain"),
|
|
24399
|
+
limit: zod.z.number().int().positive().max(100).optional().describe("Maximum number of matching notes to return")
|
|
24400
|
+
}
|
|
24401
|
+
},
|
|
24402
|
+
async ({ query, folder, tags, limit }) => {
|
|
23686
24403
|
return withAction(
|
|
23687
24404
|
runtime2,
|
|
23688
24405
|
tabManager,
|
|
23689
|
-
"
|
|
23690
|
-
{
|
|
24406
|
+
"memory_note_search",
|
|
24407
|
+
{ query, folder, tags, limit },
|
|
23691
24408
|
async () => {
|
|
23692
|
-
const
|
|
23693
|
-
if (
|
|
23694
|
-
return
|
|
23695
|
-
`Folder "${existing.name}" already exists (id=${existing.id})`
|
|
23696
|
-
);
|
|
24409
|
+
const notes = await searchMemoryNotes({ query, folder, tags, limit });
|
|
24410
|
+
if (notes.length === 0) {
|
|
24411
|
+
return `No memory notes matched "${query}".`;
|
|
23697
24412
|
}
|
|
23698
|
-
|
|
23699
|
-
|
|
23700
|
-
|
|
23701
|
-
summary
|
|
23702
|
-
);
|
|
23703
|
-
return folder ? composeFolderAwareResponse$1(`Renamed folder to "${folder.name}"`) : `Folder ${folder_id} not found`;
|
|
24413
|
+
return notes.map(
|
|
24414
|
+
(note) => `- ${note.title} | path=${note.relativePath} | modified=${note.modifiedAt}${note.tags.length ? ` | tags=${note.tags.join(",")}` : ""}`
|
|
24415
|
+
).join("\n");
|
|
23704
24416
|
}
|
|
23705
24417
|
);
|
|
23706
24418
|
}
|
|
23707
24419
|
);
|
|
23708
24420
|
server.registerTool(
|
|
23709
|
-
"
|
|
24421
|
+
"memory_page_capture",
|
|
23710
24422
|
{
|
|
23711
|
-
title: "
|
|
23712
|
-
description: "
|
|
24423
|
+
title: "Capture Page To Memory",
|
|
24424
|
+
description: "Capture the current page into the configured Obsidian vault as a markdown note with URL, excerpt, and content snapshot.",
|
|
23713
24425
|
inputSchema: {
|
|
23714
|
-
|
|
23715
|
-
|
|
23716
|
-
|
|
23717
|
-
|
|
23718
|
-
|
|
23719
|
-
"Optional rationale or breadcrumb to store with the bookmark"
|
|
23720
|
-
),
|
|
23721
|
-
tags: zod.z.array(zod.z.string()).optional().describe("Optional tags when creating a new note")
|
|
24426
|
+
title: zod.z.string().optional().describe("Optional note title override"),
|
|
24427
|
+
folder: zod.z.string().optional().describe("Relative folder inside the vault (default: Vessel/Pages)"),
|
|
24428
|
+
summary: zod.z.string().optional().describe("Optional summary written into the note"),
|
|
24429
|
+
note: zod.z.string().optional().describe("Optional research note or breadcrumb"),
|
|
24430
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Optional tags to store in frontmatter")
|
|
23722
24431
|
}
|
|
23723
24432
|
},
|
|
23724
|
-
async ({
|
|
24433
|
+
async ({ title, folder, summary, note, tags }) => {
|
|
24434
|
+
const tab = tabManager.getActiveTab();
|
|
24435
|
+
if (!tab) return asNoActiveTabResponse();
|
|
23725
24436
|
return withAction(
|
|
23726
24437
|
runtime2,
|
|
23727
24438
|
tabManager,
|
|
23728
|
-
"
|
|
23729
|
-
{
|
|
24439
|
+
"memory_page_capture",
|
|
24440
|
+
{ title, folder, tags },
|
|
23730
24441
|
async () => {
|
|
23731
|
-
const
|
|
23732
|
-
|
|
23733
|
-
|
|
23734
|
-
}
|
|
23735
|
-
const saved = linkBookmarkToMemory({
|
|
23736
|
-
bookmark,
|
|
23737
|
-
notePath: note_path,
|
|
24442
|
+
const page = await extractContent(tab.view.webContents);
|
|
24443
|
+
const saved = await capturePageToVault({
|
|
24444
|
+
page,
|
|
23738
24445
|
title,
|
|
23739
24446
|
folder,
|
|
24447
|
+
summary,
|
|
23740
24448
|
note,
|
|
23741
24449
|
tags
|
|
23742
24450
|
});
|
|
23743
|
-
return `
|
|
24451
|
+
return `Captured page "${saved.title}" to ${saved.relativePath}`;
|
|
23744
24452
|
}
|
|
23745
24453
|
);
|
|
23746
24454
|
}
|
|
@@ -24498,7 +25206,7 @@ function registerTools(server, tabManager, runtime2) {
|
|
|
24498
25206
|
const wc = activeTab.view.webContents;
|
|
24499
25207
|
pageUrl = wc.getURL();
|
|
24500
25208
|
pageTitle = wc.getTitle();
|
|
24501
|
-
const page = await extractContent
|
|
25209
|
+
const page = await extractContent(wc);
|
|
24502
25210
|
pageType = detectPageType(page);
|
|
24503
25211
|
} catch (err) {
|
|
24504
25212
|
logger$b.warn("Failed to detect page type for tool scoring, falling back to GENERAL:", err);
|
|
@@ -24664,7 +25372,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
24664
25372
|
const tab = tabManager.getActiveTab();
|
|
24665
25373
|
if (!tab) return asNoActiveTabResponse();
|
|
24666
25374
|
try {
|
|
24667
|
-
const pageContent = await extractContent
|
|
25375
|
+
const pageContent = await extractContent(tab.view.webContents);
|
|
24668
25376
|
const effectiveMode = mode || "full";
|
|
24669
25377
|
return asTextResponse(
|
|
24670
25378
|
await buildExtractResponse(
|
|
@@ -24696,7 +25404,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
24696
25404
|
const tab = tabManager.getActiveTab();
|
|
24697
25405
|
if (!tab) return asNoActiveTabResponse();
|
|
24698
25406
|
try {
|
|
24699
|
-
const pageContent = await extractContent
|
|
25407
|
+
const pageContent = await extractContent(tab.view.webContents);
|
|
24700
25408
|
const effectiveMode = mode || "full";
|
|
24701
25409
|
return asTextResponse(
|
|
24702
25410
|
await buildExtractResponse(
|
|
@@ -24831,7 +25539,7 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
24831
25539
|
const tab = tabManager.getActiveTab();
|
|
24832
25540
|
if (!tab) return asNoActiveTabResponse();
|
|
24833
25541
|
try {
|
|
24834
|
-
const pageContent = await extractContent
|
|
25542
|
+
const pageContent = await extractContent(tab.view.webContents);
|
|
24835
25543
|
const requestedType = typeof type === "string" && type.trim() ? type.trim().toLowerCase() : "";
|
|
24836
25544
|
const entities = (pageContent.structuredData ?? []).filter(
|
|
24837
25545
|
(entity) => requestedType ? entity.types.some(
|
|
@@ -25805,155 +26513,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
25805
26513
|
);
|
|
25806
26514
|
registerBookmarkTools(server, tabManager, runtime2);
|
|
25807
26515
|
registerSessionTools(server, tabManager, runtime2);
|
|
25808
|
-
server
|
|
25809
|
-
"memory_note_create",
|
|
25810
|
-
{
|
|
25811
|
-
title: "Create Memory Note",
|
|
25812
|
-
description: "Write a markdown note into the configured Obsidian vault for research notes, breadcrumbs, or synthesis.",
|
|
25813
|
-
inputSchema: {
|
|
25814
|
-
title: zod.z.string().describe("Title of the note"),
|
|
25815
|
-
body: zod.z.string().describe("Markdown body for the note"),
|
|
25816
|
-
folder: zod.z.string().optional().describe(
|
|
25817
|
-
"Relative folder inside the vault (default: Vessel/Research)"
|
|
25818
|
-
),
|
|
25819
|
-
tags: zod.z.array(zod.z.string()).optional().describe("Optional tags to store in frontmatter")
|
|
25820
|
-
}
|
|
25821
|
-
},
|
|
25822
|
-
async ({ title, body, folder, tags }) => {
|
|
25823
|
-
return withAction(
|
|
25824
|
-
runtime2,
|
|
25825
|
-
tabManager,
|
|
25826
|
-
"memory_note_create",
|
|
25827
|
-
{ title, folder, tags },
|
|
25828
|
-
async () => {
|
|
25829
|
-
const saved = writeMemoryNote({ title, body, folder, tags });
|
|
25830
|
-
return `Saved memory note "${saved.title}" to ${saved.relativePath}`;
|
|
25831
|
-
}
|
|
25832
|
-
);
|
|
25833
|
-
}
|
|
25834
|
-
);
|
|
25835
|
-
server.registerTool(
|
|
25836
|
-
"memory_append",
|
|
25837
|
-
{
|
|
25838
|
-
title: "Append Memory Note",
|
|
25839
|
-
description: "Append markdown content to an existing note in the configured Obsidian vault.",
|
|
25840
|
-
inputSchema: {
|
|
25841
|
-
note_path: zod.z.string().describe("Relative path to an existing note inside the vault"),
|
|
25842
|
-
content: zod.z.string().describe("Markdown content to append"),
|
|
25843
|
-
heading: zod.z.string().optional().describe("Optional section heading to add before the content")
|
|
25844
|
-
}
|
|
25845
|
-
},
|
|
25846
|
-
async ({ note_path, content, heading }) => {
|
|
25847
|
-
return withAction(
|
|
25848
|
-
runtime2,
|
|
25849
|
-
tabManager,
|
|
25850
|
-
"memory_note_append",
|
|
25851
|
-
{ note_path, heading },
|
|
25852
|
-
async () => {
|
|
25853
|
-
const saved = appendToMemoryNote({
|
|
25854
|
-
notePath: note_path,
|
|
25855
|
-
content,
|
|
25856
|
-
heading
|
|
25857
|
-
});
|
|
25858
|
-
return `Appended memory note at ${saved.relativePath}`;
|
|
25859
|
-
}
|
|
25860
|
-
);
|
|
25861
|
-
}
|
|
25862
|
-
);
|
|
25863
|
-
server.registerTool(
|
|
25864
|
-
"memory_list",
|
|
25865
|
-
{
|
|
25866
|
-
title: "List Memory Notes",
|
|
25867
|
-
description: "List recent markdown notes in the configured Obsidian vault.",
|
|
25868
|
-
inputSchema: {
|
|
25869
|
-
folder: zod.z.string().optional().describe("Optional relative folder inside the vault"),
|
|
25870
|
-
limit: zod.z.number().int().positive().max(200).optional().describe("Maximum number of notes to return")
|
|
25871
|
-
}
|
|
25872
|
-
},
|
|
25873
|
-
async ({ folder, limit }) => {
|
|
25874
|
-
return withAction(
|
|
25875
|
-
runtime2,
|
|
25876
|
-
tabManager,
|
|
25877
|
-
"memory_note_list",
|
|
25878
|
-
{ folder, limit },
|
|
25879
|
-
async () => {
|
|
25880
|
-
const notes = listMemoryNotes({ folder, limit });
|
|
25881
|
-
if (notes.length === 0) {
|
|
25882
|
-
return "No memory notes found.";
|
|
25883
|
-
}
|
|
25884
|
-
return notes.map(
|
|
25885
|
-
(note) => `- ${note.title} | path=${note.relativePath} | modified=${note.modifiedAt}${note.tags.length ? ` | tags=${note.tags.join(",")}` : ""}`
|
|
25886
|
-
).join("\n");
|
|
25887
|
-
}
|
|
25888
|
-
);
|
|
25889
|
-
}
|
|
25890
|
-
);
|
|
25891
|
-
server.registerTool(
|
|
25892
|
-
"memory_search",
|
|
25893
|
-
{
|
|
25894
|
-
title: "Search Memory Notes",
|
|
25895
|
-
description: "Search markdown notes in the configured Obsidian vault by title, path, body, and optional tags.",
|
|
25896
|
-
inputSchema: {
|
|
25897
|
-
query: zod.z.string().describe("Search query"),
|
|
25898
|
-
folder: zod.z.string().optional().describe("Optional relative folder inside the vault"),
|
|
25899
|
-
tags: zod.z.array(zod.z.string()).optional().describe("Optional tags that matching notes must contain"),
|
|
25900
|
-
limit: zod.z.number().int().positive().max(100).optional().describe("Maximum number of matching notes to return")
|
|
25901
|
-
}
|
|
25902
|
-
},
|
|
25903
|
-
async ({ query, folder, tags, limit }) => {
|
|
25904
|
-
return withAction(
|
|
25905
|
-
runtime2,
|
|
25906
|
-
tabManager,
|
|
25907
|
-
"memory_note_search",
|
|
25908
|
-
{ query, folder, tags, limit },
|
|
25909
|
-
async () => {
|
|
25910
|
-
const notes = searchMemoryNotes({ query, folder, tags, limit });
|
|
25911
|
-
if (notes.length === 0) {
|
|
25912
|
-
return `No memory notes matched "${query}".`;
|
|
25913
|
-
}
|
|
25914
|
-
return notes.map(
|
|
25915
|
-
(note) => `- ${note.title} | path=${note.relativePath} | modified=${note.modifiedAt}${note.tags.length ? ` | tags=${note.tags.join(",")}` : ""}`
|
|
25916
|
-
).join("\n");
|
|
25917
|
-
}
|
|
25918
|
-
);
|
|
25919
|
-
}
|
|
25920
|
-
);
|
|
25921
|
-
server.registerTool(
|
|
25922
|
-
"memory_page_capture",
|
|
25923
|
-
{
|
|
25924
|
-
title: "Capture Page To Memory",
|
|
25925
|
-
description: "Capture the current page into the configured Obsidian vault as a markdown note with URL, excerpt, and content snapshot.",
|
|
25926
|
-
inputSchema: {
|
|
25927
|
-
title: zod.z.string().optional().describe("Optional note title override"),
|
|
25928
|
-
folder: zod.z.string().optional().describe("Relative folder inside the vault (default: Vessel/Pages)"),
|
|
25929
|
-
summary: zod.z.string().optional().describe("Optional summary written into the note"),
|
|
25930
|
-
note: zod.z.string().optional().describe("Optional research note or breadcrumb"),
|
|
25931
|
-
tags: zod.z.array(zod.z.string()).optional().describe("Optional tags to store in frontmatter")
|
|
25932
|
-
}
|
|
25933
|
-
},
|
|
25934
|
-
async ({ title, folder, summary, note, tags }) => {
|
|
25935
|
-
const tab = tabManager.getActiveTab();
|
|
25936
|
-
if (!tab) return asNoActiveTabResponse();
|
|
25937
|
-
return withAction(
|
|
25938
|
-
runtime2,
|
|
25939
|
-
tabManager,
|
|
25940
|
-
"memory_page_capture",
|
|
25941
|
-
{ title, folder, tags },
|
|
25942
|
-
async () => {
|
|
25943
|
-
const page = await extractContent$1(tab.view.webContents);
|
|
25944
|
-
const saved = capturePageToVault({
|
|
25945
|
-
page,
|
|
25946
|
-
title,
|
|
25947
|
-
folder,
|
|
25948
|
-
summary,
|
|
25949
|
-
note,
|
|
25950
|
-
tags
|
|
25951
|
-
});
|
|
25952
|
-
return `Captured page "${saved.title}" to ${saved.relativePath}`;
|
|
25953
|
-
}
|
|
25954
|
-
);
|
|
25955
|
-
}
|
|
25956
|
-
);
|
|
26516
|
+
registerMemoryTools(server, tabManager, runtime2);
|
|
25957
26517
|
server.registerTool(
|
|
25958
26518
|
"flow_start",
|
|
25959
26519
|
{
|
|
@@ -26061,7 +26621,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
26061
26621
|
const wc = tab.view.webContents;
|
|
26062
26622
|
let page;
|
|
26063
26623
|
try {
|
|
26064
|
-
page = await extractContent
|
|
26624
|
+
page = await extractContent(wc);
|
|
26065
26625
|
} catch (err) {
|
|
26066
26626
|
logger$b.warn("Failed to extract page while generating suggestions:", err);
|
|
26067
26627
|
return asTextResponse(
|
|
@@ -27622,25 +28182,27 @@ const VALID_KIT_CATEGORIES = /* @__PURE__ */ new Set([
|
|
|
27622
28182
|
"productivity",
|
|
27623
28183
|
"forms"
|
|
27624
28184
|
]);
|
|
27625
|
-
const BUNDLED_KIT_IDS = /* @__PURE__ */ new Set(
|
|
27626
|
-
"research-collect",
|
|
27627
|
-
"price-scout",
|
|
27628
|
-
"form-filler"
|
|
27629
|
-
]);
|
|
28185
|
+
const BUNDLED_KIT_IDS = /* @__PURE__ */ new Set();
|
|
27630
28186
|
const KIT_ID_UNSAFE_CHAR_PATTERN = /[/\\\0]/;
|
|
27631
28187
|
function isSafeAutomationKitId(id) {
|
|
27632
28188
|
return id.length > 0 && !KIT_ID_UNSAFE_CHAR_PATTERN.test(id);
|
|
27633
28189
|
}
|
|
27634
28190
|
const logger$9 = createLogger("KitRegistry");
|
|
28191
|
+
const { access, mkdir, readFile, readdir, unlink, writeFile } = fs$1.promises;
|
|
27635
28192
|
function getUserKitsDir() {
|
|
27636
28193
|
return path$1.join(electron.app.getPath("userData"), "kits");
|
|
27637
28194
|
}
|
|
27638
|
-
function
|
|
27639
|
-
|
|
27640
|
-
|
|
27641
|
-
|
|
28195
|
+
async function pathExists(filePath2) {
|
|
28196
|
+
try {
|
|
28197
|
+
await access(filePath2);
|
|
28198
|
+
return true;
|
|
28199
|
+
} catch {
|
|
28200
|
+
return false;
|
|
27642
28201
|
}
|
|
27643
28202
|
}
|
|
28203
|
+
async function ensureKitsDir() {
|
|
28204
|
+
await mkdir(getUserKitsDir(), { recursive: true });
|
|
28205
|
+
}
|
|
27644
28206
|
function getKitFilePath(id) {
|
|
27645
28207
|
if (!isSafeAutomationKitId(id)) return null;
|
|
27646
28208
|
const kitsDir = path$1.resolve(getUserKitsDir());
|
|
@@ -27652,12 +28214,12 @@ function isValidKit(value) {
|
|
|
27652
28214
|
const k = value;
|
|
27653
28215
|
return typeof k.id === "string" && isSafeAutomationKitId(k.id) && typeof k.name === "string" && k.name.length > 0 && typeof k.description === "string" && typeof k.category === "string" && VALID_KIT_CATEGORIES.has(k.category) && typeof k.icon === "string" && typeof k.promptTemplate === "string" && k.promptTemplate.length > 0 && Array.isArray(k.inputs);
|
|
27654
28216
|
}
|
|
27655
|
-
function getInstalledKits() {
|
|
27656
|
-
ensureKitsDir();
|
|
28217
|
+
async function getInstalledKits() {
|
|
28218
|
+
await ensureKitsDir();
|
|
27657
28219
|
const dir = getUserKitsDir();
|
|
27658
28220
|
let files;
|
|
27659
28221
|
try {
|
|
27660
|
-
files =
|
|
28222
|
+
files = (await readdir(dir)).filter((f) => f.endsWith(".kit.json"));
|
|
27661
28223
|
} catch (err) {
|
|
27662
28224
|
logger$9.warn("Failed to read kit directory:", err);
|
|
27663
28225
|
return [];
|
|
@@ -27665,23 +28227,23 @@ function getInstalledKits() {
|
|
|
27665
28227
|
const kits = [];
|
|
27666
28228
|
for (const file of files) {
|
|
27667
28229
|
try {
|
|
27668
|
-
const raw =
|
|
28230
|
+
const raw = await readFile(path$1.join(dir, file), "utf-8");
|
|
27669
28231
|
const parsed = JSON.parse(raw);
|
|
27670
28232
|
if (isValidKit(parsed)) {
|
|
27671
28233
|
kits.push(parsed);
|
|
27672
28234
|
} else {
|
|
27673
|
-
logger$9.warn(`Skipping invalid
|
|
28235
|
+
logger$9.warn(`Skipping invalid skill file: ${file}`);
|
|
27674
28236
|
}
|
|
27675
28237
|
} catch (err) {
|
|
27676
|
-
logger$9.warn(`Failed to read
|
|
28238
|
+
logger$9.warn(`Failed to read skill file: ${file}`, err);
|
|
27677
28239
|
}
|
|
27678
28240
|
}
|
|
27679
28241
|
return kits;
|
|
27680
28242
|
}
|
|
27681
28243
|
async function installKitFromFile() {
|
|
27682
28244
|
const { canceled, filePaths } = await electron.dialog.showOpenDialog({
|
|
27683
|
-
title: "
|
|
27684
|
-
filters: [{ name: "
|
|
28245
|
+
title: "Import Skill",
|
|
28246
|
+
filters: [{ name: "Skills", extensions: ["skill.json", "json"] }],
|
|
27685
28247
|
properties: ["openFile"]
|
|
27686
28248
|
});
|
|
27687
28249
|
if (canceled || filePaths.length === 0) {
|
|
@@ -27689,64 +28251,130 @@ async function installKitFromFile() {
|
|
|
27689
28251
|
}
|
|
27690
28252
|
let raw;
|
|
27691
28253
|
try {
|
|
27692
|
-
raw =
|
|
28254
|
+
raw = await readFile(filePaths[0], "utf-8");
|
|
27693
28255
|
} catch (err) {
|
|
27694
|
-
logger$9.warn("Failed to read selected
|
|
28256
|
+
logger$9.warn("Failed to read selected skill file:", err);
|
|
27695
28257
|
return errorResult("Could not read the selected file.");
|
|
27696
28258
|
}
|
|
27697
28259
|
let parsed;
|
|
27698
28260
|
try {
|
|
27699
28261
|
parsed = JSON.parse(raw);
|
|
27700
28262
|
} catch (err) {
|
|
27701
|
-
logger$9.warn("Selected
|
|
28263
|
+
logger$9.warn("Selected skill file is not valid JSON:", err);
|
|
27702
28264
|
return errorResult("File is not valid JSON.");
|
|
27703
28265
|
}
|
|
27704
28266
|
if (!isValidKit(parsed)) {
|
|
27705
28267
|
return errorResult(
|
|
27706
|
-
"File is not a valid
|
|
28268
|
+
"File is not a valid skill. Required fields: id, name, description, icon, inputs, promptTemplate."
|
|
28269
|
+
);
|
|
28270
|
+
}
|
|
28271
|
+
if (BUNDLED_KIT_IDS.has(parsed.id)) {
|
|
28272
|
+
return errorResult(
|
|
28273
|
+
`Skill id "${parsed.id}" conflicts with a built-in skill and cannot be overwritten.`
|
|
28274
|
+
);
|
|
28275
|
+
}
|
|
28276
|
+
await ensureKitsDir();
|
|
28277
|
+
const dest = getKitFilePath(parsed.id);
|
|
28278
|
+
if (!dest) {
|
|
28279
|
+
return errorResult("Skill id contains unsupported characters.");
|
|
28280
|
+
}
|
|
28281
|
+
try {
|
|
28282
|
+
await writeFile(dest, JSON.stringify(parsed, null, 2), "utf-8");
|
|
28283
|
+
} catch (err) {
|
|
28284
|
+
logger$9.warn("Failed to save skill file:", err);
|
|
28285
|
+
return errorResult("Failed to save the skill file.");
|
|
28286
|
+
}
|
|
28287
|
+
return okResult({ kit: parsed });
|
|
28288
|
+
}
|
|
28289
|
+
async function createKitFromText(source) {
|
|
28290
|
+
let parsed;
|
|
28291
|
+
try {
|
|
28292
|
+
parsed = JSON.parse(source);
|
|
28293
|
+
} catch (err) {
|
|
28294
|
+
logger$9.warn("Created skill text is not valid JSON:", err);
|
|
28295
|
+
return errorResult("Skill text is not valid JSON.");
|
|
28296
|
+
}
|
|
28297
|
+
if (!isValidKit(parsed)) {
|
|
28298
|
+
return errorResult(
|
|
28299
|
+
"Text is not a valid skill. Required fields: id, name, description, icon, inputs, promptTemplate."
|
|
27707
28300
|
);
|
|
27708
28301
|
}
|
|
27709
28302
|
if (BUNDLED_KIT_IDS.has(parsed.id)) {
|
|
27710
28303
|
return errorResult(
|
|
27711
|
-
`
|
|
28304
|
+
`Skill id "${parsed.id}" conflicts with a built-in skill and cannot be overwritten.`
|
|
27712
28305
|
);
|
|
27713
28306
|
}
|
|
27714
|
-
ensureKitsDir();
|
|
28307
|
+
await ensureKitsDir();
|
|
27715
28308
|
const dest = getKitFilePath(parsed.id);
|
|
27716
28309
|
if (!dest) {
|
|
27717
|
-
return errorResult("
|
|
28310
|
+
return errorResult("Skill id contains unsupported characters.");
|
|
28311
|
+
}
|
|
28312
|
+
try {
|
|
28313
|
+
await writeFile(dest, JSON.stringify(parsed, null, 2), "utf-8");
|
|
28314
|
+
} catch (err) {
|
|
28315
|
+
logger$9.warn("Failed to save created skill:", err);
|
|
28316
|
+
return errorResult("Failed to save the skill.");
|
|
28317
|
+
}
|
|
28318
|
+
return okResult({ kit: parsed });
|
|
28319
|
+
}
|
|
28320
|
+
async function updateKitFromText(id, source) {
|
|
28321
|
+
if (BUNDLED_KIT_IDS.has(id)) {
|
|
28322
|
+
return errorResult("Built-in skills cannot be edited.");
|
|
28323
|
+
}
|
|
28324
|
+
const target = getKitFilePath(id);
|
|
28325
|
+
if (!target) {
|
|
28326
|
+
return errorResult("Skill id contains unsupported characters.");
|
|
28327
|
+
}
|
|
28328
|
+
let parsed;
|
|
28329
|
+
try {
|
|
28330
|
+
parsed = JSON.parse(source);
|
|
28331
|
+
} catch (err) {
|
|
28332
|
+
logger$9.warn("Updated skill text is not valid JSON:", err);
|
|
28333
|
+
return errorResult("Skill text is not valid JSON.");
|
|
28334
|
+
}
|
|
28335
|
+
if (!isValidKit(parsed)) {
|
|
28336
|
+
return errorResult(
|
|
28337
|
+
"Text is not a valid skill. Required fields: id, name, description, icon, inputs, promptTemplate."
|
|
28338
|
+
);
|
|
28339
|
+
}
|
|
28340
|
+
if (parsed.id !== id) {
|
|
28341
|
+
return errorResult("Skill id cannot be changed while editing.");
|
|
28342
|
+
}
|
|
28343
|
+
await ensureKitsDir();
|
|
28344
|
+
if (!await pathExists(target)) {
|
|
28345
|
+
return errorResult("Skill not found.");
|
|
27718
28346
|
}
|
|
27719
28347
|
try {
|
|
27720
|
-
|
|
28348
|
+
await writeFile(target, JSON.stringify(parsed, null, 2), "utf-8");
|
|
27721
28349
|
} catch (err) {
|
|
27722
|
-
logger$9.warn("Failed to
|
|
27723
|
-
return errorResult("Failed to
|
|
28350
|
+
logger$9.warn("Failed to update skill:", err);
|
|
28351
|
+
return errorResult("Failed to update the skill.");
|
|
27724
28352
|
}
|
|
27725
28353
|
return okResult({ kit: parsed });
|
|
27726
28354
|
}
|
|
27727
|
-
function uninstallKit(id, scheduledKitIds) {
|
|
28355
|
+
async function uninstallKit(id, scheduledKitIds) {
|
|
27728
28356
|
if (BUNDLED_KIT_IDS.has(id)) {
|
|
27729
|
-
return errorResult("Built-in
|
|
28357
|
+
return errorResult("Built-in skills cannot be removed.");
|
|
27730
28358
|
}
|
|
27731
28359
|
if (scheduledKitIds?.has(id)) {
|
|
27732
28360
|
return errorResult(
|
|
27733
|
-
"This
|
|
28361
|
+
"This skill has active scheduled jobs. Delete or reassign them first."
|
|
27734
28362
|
);
|
|
27735
28363
|
}
|
|
27736
|
-
ensureKitsDir();
|
|
28364
|
+
await ensureKitsDir();
|
|
27737
28365
|
const target = getKitFilePath(id);
|
|
27738
28366
|
if (!target) {
|
|
27739
|
-
return errorResult("
|
|
28367
|
+
return errorResult("Skill id contains unsupported characters.");
|
|
27740
28368
|
}
|
|
27741
|
-
if (!
|
|
27742
|
-
return errorResult("
|
|
28369
|
+
if (!await pathExists(target)) {
|
|
28370
|
+
return errorResult("Skill not found.");
|
|
27743
28371
|
}
|
|
27744
28372
|
try {
|
|
27745
|
-
|
|
28373
|
+
await unlink(target);
|
|
27746
28374
|
return okResult();
|
|
27747
28375
|
} catch (err) {
|
|
27748
|
-
logger$9.warn("Failed to remove
|
|
27749
|
-
return errorResult("Failed to remove the
|
|
28376
|
+
logger$9.warn("Failed to remove skill file:", err);
|
|
28377
|
+
return errorResult("Failed to remove the skill file.");
|
|
27750
28378
|
}
|
|
27751
28379
|
}
|
|
27752
28380
|
const logger$8 = createLogger("Scheduler");
|
|
@@ -27931,7 +28559,7 @@ async function fireJob(job, windowState2, runtime2) {
|
|
|
27931
28559
|
} catch (err) {
|
|
27932
28560
|
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
27933
28561
|
appendActivity(`
|
|
27934
|
-
[Scheduled
|
|
28562
|
+
[Scheduled Skill Error: ${msg}]`);
|
|
27935
28563
|
finishActivity("failed");
|
|
27936
28564
|
}
|
|
27937
28565
|
}
|
|
@@ -27989,10 +28617,12 @@ function registerScheduleHandlers(windowState2, runtime2, sendToAll) {
|
|
|
27989
28617
|
}, msToNextMinute);
|
|
27990
28618
|
electron.ipcMain.handle(Channels.SCHEDULE_GET_ALL, (event) => {
|
|
27991
28619
|
assertTrustedIpcSender(event);
|
|
28620
|
+
assertFeatureUnlocked("automation_kits", "Skills");
|
|
27992
28621
|
return jobs;
|
|
27993
28622
|
});
|
|
27994
28623
|
electron.ipcMain.handle(Channels.SCHEDULE_CREATE, (event, rawJob) => {
|
|
27995
28624
|
assertTrustedIpcSender(event);
|
|
28625
|
+
assertFeatureUnlocked("automation_kits", "Skills");
|
|
27996
28626
|
if (!isValidJobData(rawJob)) {
|
|
27997
28627
|
throw new Error(
|
|
27998
28628
|
"Invalid job data. Required: kitId, kitName, kitIcon, renderedPrompt, schedule, enabled."
|
|
@@ -28011,6 +28641,7 @@ function registerScheduleHandlers(windowState2, runtime2, sendToAll) {
|
|
|
28011
28641
|
});
|
|
28012
28642
|
electron.ipcMain.handle(Channels.SCHEDULE_UPDATE, (event, id, updates) => {
|
|
28013
28643
|
assertTrustedIpcSender(event);
|
|
28644
|
+
assertFeatureUnlocked("automation_kits", "Skills");
|
|
28014
28645
|
if (typeof id !== "string") throw new Error("id must be a string");
|
|
28015
28646
|
const job = jobs.find((j) => j.id === id);
|
|
28016
28647
|
if (!job) return null;
|
|
@@ -28038,6 +28669,7 @@ function registerScheduleHandlers(windowState2, runtime2, sendToAll) {
|
|
|
28038
28669
|
});
|
|
28039
28670
|
electron.ipcMain.handle(Channels.SCHEDULE_DELETE, (event, id) => {
|
|
28040
28671
|
assertTrustedIpcSender(event);
|
|
28672
|
+
assertFeatureUnlocked("automation_kits", "Skills");
|
|
28041
28673
|
if (typeof id !== "string") throw new Error("id must be a string");
|
|
28042
28674
|
const before = jobs.length;
|
|
28043
28675
|
jobs = jobs.filter((j) => j.id !== id);
|
|
@@ -28062,6 +28694,7 @@ function stopScheduler() {
|
|
|
28062
28694
|
}
|
|
28063
28695
|
}
|
|
28064
28696
|
const KitIdSchema = zod.z.string().min(1);
|
|
28697
|
+
const SkillSourceSchema = zod.z.string().min(1).max(1e5);
|
|
28065
28698
|
const OriginSchema = zod.z.string().min(1);
|
|
28066
28699
|
function registerSystemHandlers(windowState2, sendToRendererViews) {
|
|
28067
28700
|
const { tabManager } = windowState2;
|
|
@@ -28078,17 +28711,38 @@ function registerSystemHandlers(windowState2, sendToRendererViews) {
|
|
|
28078
28711
|
layoutViews(windowState2);
|
|
28079
28712
|
return clamped;
|
|
28080
28713
|
});
|
|
28081
|
-
electron.ipcMain.handle(Channels.AUTOMATION_GET_INSTALLED, (event) => {
|
|
28714
|
+
electron.ipcMain.handle(Channels.AUTOMATION_GET_INSTALLED, async (event) => {
|
|
28082
28715
|
assertTrustedIpcSender(event);
|
|
28083
|
-
|
|
28716
|
+
assertFeatureUnlocked("automation_kits", "Skills");
|
|
28717
|
+
return await getInstalledKits();
|
|
28084
28718
|
});
|
|
28085
28719
|
electron.ipcMain.handle(Channels.AUTOMATION_INSTALL_FROM_FILE, async (event) => {
|
|
28086
28720
|
assertTrustedIpcSender(event);
|
|
28721
|
+
assertFeatureUnlocked("automation_kits", "Skills");
|
|
28087
28722
|
return await installKitFromFile();
|
|
28088
28723
|
});
|
|
28089
|
-
electron.ipcMain.handle(Channels.
|
|
28724
|
+
electron.ipcMain.handle(Channels.AUTOMATION_CREATE_FROM_TEXT, async (event, source) => {
|
|
28725
|
+
assertTrustedIpcSender(event);
|
|
28726
|
+
assertFeatureUnlocked("automation_kits", "Skills");
|
|
28727
|
+
return await createKitFromText(
|
|
28728
|
+
parseIpc(SkillSourceSchema, source, "source")
|
|
28729
|
+
);
|
|
28730
|
+
});
|
|
28731
|
+
electron.ipcMain.handle(Channels.AUTOMATION_UPDATE_FROM_TEXT, async (event, id, source) => {
|
|
28090
28732
|
assertTrustedIpcSender(event);
|
|
28091
|
-
|
|
28733
|
+
assertFeatureUnlocked("automation_kits", "Skills");
|
|
28734
|
+
return await updateKitFromText(
|
|
28735
|
+
parseIpc(KitIdSchema, id, "id"),
|
|
28736
|
+
parseIpc(SkillSourceSchema, source, "source")
|
|
28737
|
+
);
|
|
28738
|
+
});
|
|
28739
|
+
electron.ipcMain.handle(Channels.AUTOMATION_UNINSTALL, async (event, id) => {
|
|
28740
|
+
assertTrustedIpcSender(event);
|
|
28741
|
+
assertFeatureUnlocked("automation_kits", "Skills");
|
|
28742
|
+
return await uninstallKit(
|
|
28743
|
+
parseIpc(KitIdSchema, id, "id"),
|
|
28744
|
+
getScheduledKitIds()
|
|
28745
|
+
);
|
|
28092
28746
|
});
|
|
28093
28747
|
electron.ipcMain.handle(Channels.CLEAR_BROWSING_DATA, async (event, options) => {
|
|
28094
28748
|
assertTrustedIpcSender(event);
|
|
@@ -29547,7 +30201,7 @@ function registerAutofillHandlers(windowState2) {
|
|
|
29547
30201
|
const activeTab = windowState2.tabManager.getActiveTab();
|
|
29548
30202
|
const wc = activeTab?.view.webContents;
|
|
29549
30203
|
if (!wc) throw new Error("No active tab");
|
|
29550
|
-
const content = await extractContent
|
|
30204
|
+
const content = await extractContent(wc);
|
|
29551
30205
|
const elements = content.interactiveElements || [];
|
|
29552
30206
|
const matches = matchFields(elements, profile);
|
|
29553
30207
|
if (matches.length === 0) {
|