@quanta-intellect/vessel-browser 0.1.20 → 0.1.24
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 +60 -12
- package/out/main/index.js +961 -108
- package/out/preload/index.js +62 -0
- package/out/renderer/assets/{index-CKOT_IZt.js → index-32axMD1q.js} +2444 -397
- package/out/renderer/assets/{index-DwRZftNk.css → index-BynCvURs.css} +831 -0
- package/out/renderer/index.html +2 -2
- package/package.json +6 -5
package/out/main/index.js
CHANGED
|
@@ -3,11 +3,12 @@ const electron = require("electron");
|
|
|
3
3
|
const fs$1 = require("node:fs");
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const fs = require("fs");
|
|
6
|
-
const crypto = require("crypto");
|
|
7
|
-
const crypto$
|
|
6
|
+
const crypto$1 = require("crypto");
|
|
7
|
+
const crypto$2 = require("node:crypto");
|
|
8
8
|
const path$1 = require("node:path");
|
|
9
9
|
const Anthropic = require("@anthropic-ai/sdk");
|
|
10
10
|
const OpenAI = require("openai");
|
|
11
|
+
const node_module = require("node:module");
|
|
11
12
|
const zod = require("zod");
|
|
12
13
|
const http = require("node:http");
|
|
13
14
|
const os = require("node:os");
|
|
@@ -16,7 +17,7 @@ const streamableHttp_js = require("@modelcontextprotocol/sdk/server/streamableHt
|
|
|
16
17
|
const defaults = {
|
|
17
18
|
defaultUrl: "https://start.duckduckgo.com",
|
|
18
19
|
theme: "dark",
|
|
19
|
-
sidebarWidth:
|
|
20
|
+
sidebarWidth: 400,
|
|
20
21
|
mcpPort: 3100,
|
|
21
22
|
autoRestoreSession: true,
|
|
22
23
|
clearBookmarksOnLaunch: false,
|
|
@@ -36,11 +37,20 @@ const defaults = {
|
|
|
36
37
|
expiresAt: ""
|
|
37
38
|
}
|
|
38
39
|
};
|
|
40
|
+
const SAVE_DEBOUNCE_MS$3 = 150;
|
|
39
41
|
const SETTABLE_KEYS = new Set(Object.keys(defaults));
|
|
40
42
|
let settings = null;
|
|
41
43
|
let settingsIssues = [];
|
|
44
|
+
let saveTimer$3 = null;
|
|
45
|
+
let saveDirty$3 = false;
|
|
46
|
+
function getUserDataPath() {
|
|
47
|
+
if (typeof electron.app?.getPath === "function") {
|
|
48
|
+
return electron.app.getPath("userData");
|
|
49
|
+
}
|
|
50
|
+
return path.join(process.cwd(), ".vessel-test-data");
|
|
51
|
+
}
|
|
42
52
|
function getSettingsPath() {
|
|
43
|
-
return path.join(
|
|
53
|
+
return path.join(getUserDataPath(), "vessel-settings.json");
|
|
44
54
|
}
|
|
45
55
|
function getSettingsLoadIssues() {
|
|
46
56
|
return settingsIssues.map((issue) => ({ ...issue }));
|
|
@@ -87,11 +97,26 @@ function loadSettings() {
|
|
|
87
97
|
}
|
|
88
98
|
return settings;
|
|
89
99
|
}
|
|
90
|
-
function
|
|
91
|
-
|
|
100
|
+
function persistNow$3() {
|
|
101
|
+
saveDirty$3 = false;
|
|
102
|
+
if (saveTimer$3) {
|
|
103
|
+
clearTimeout(saveTimer$3);
|
|
104
|
+
saveTimer$3 = null;
|
|
105
|
+
}
|
|
106
|
+
return fs.promises.mkdir(path.dirname(getSettingsPath()), { recursive: true }).then(
|
|
92
107
|
() => fs.promises.writeFile(getSettingsPath(), JSON.stringify(settings, null, 2))
|
|
93
108
|
).catch((err) => console.error("[Vessel] Failed to save settings:", err));
|
|
94
109
|
}
|
|
110
|
+
function saveSettings() {
|
|
111
|
+
saveDirty$3 = true;
|
|
112
|
+
if (saveTimer$3) return;
|
|
113
|
+
saveTimer$3 = setTimeout(() => {
|
|
114
|
+
saveTimer$3 = null;
|
|
115
|
+
if (saveDirty$3) {
|
|
116
|
+
void persistNow$3();
|
|
117
|
+
}
|
|
118
|
+
}, SAVE_DEBOUNCE_MS$3);
|
|
119
|
+
}
|
|
95
120
|
function setSetting(key, value) {
|
|
96
121
|
loadSettings();
|
|
97
122
|
if (key === "mcpPort") {
|
|
@@ -102,6 +127,9 @@ function setSetting(key, value) {
|
|
|
102
127
|
saveSettings();
|
|
103
128
|
return { ...settings };
|
|
104
129
|
}
|
|
130
|
+
function flushPersist$3() {
|
|
131
|
+
return saveDirty$3 ? persistNow$3() : Promise.resolve();
|
|
132
|
+
}
|
|
105
133
|
function checkDomainPolicy(url) {
|
|
106
134
|
if (!url || url.startsWith("about:")) return null;
|
|
107
135
|
const settings2 = loadSettings();
|
|
@@ -215,7 +243,7 @@ class Tab {
|
|
|
215
243
|
this._state.canGoForward = this.urlForwardStack.length > 0;
|
|
216
244
|
this.onChange();
|
|
217
245
|
};
|
|
218
|
-
|
|
246
|
+
const recordNavigation = (url) => {
|
|
219
247
|
if (this.navigatingViaHistory) {
|
|
220
248
|
this.navigatingViaHistory = false;
|
|
221
249
|
this.lastCommittedUrl = url;
|
|
@@ -231,6 +259,9 @@ class Tab {
|
|
|
231
259
|
}
|
|
232
260
|
this.lastCommittedUrl = url;
|
|
233
261
|
syncNavigationState();
|
|
262
|
+
};
|
|
263
|
+
wc.on("did-navigate", (_event, url) => {
|
|
264
|
+
recordNavigation(url);
|
|
234
265
|
});
|
|
235
266
|
wc.on("page-title-updated", (_, title) => {
|
|
236
267
|
this._state.title = title;
|
|
@@ -244,8 +275,9 @@ class Tab {
|
|
|
244
275
|
this._state.isLoading = false;
|
|
245
276
|
syncNavigationState();
|
|
246
277
|
});
|
|
247
|
-
wc.on("did-navigate-in-page", () => {
|
|
248
|
-
|
|
278
|
+
wc.on("did-navigate-in-page", (_event, url, isMainFrame) => {
|
|
279
|
+
if (!isMainFrame) return;
|
|
280
|
+
recordNavigation(url);
|
|
249
281
|
this.onPageLoad?.(wc.getURL(), wc);
|
|
250
282
|
});
|
|
251
283
|
wc.on("did-finish-load", () => {
|
|
@@ -533,6 +565,9 @@ class Tab {
|
|
|
533
565
|
}
|
|
534
566
|
let state$3 = null;
|
|
535
567
|
const listeners$2 = /* @__PURE__ */ new Set();
|
|
568
|
+
const SAVE_DEBOUNCE_MS$2 = 250;
|
|
569
|
+
let saveTimer$2 = null;
|
|
570
|
+
let saveDirty$2 = false;
|
|
536
571
|
function getHighlightsPath() {
|
|
537
572
|
return path.join(electron.app.getPath("userData"), "vessel-highlights.json");
|
|
538
573
|
}
|
|
@@ -549,8 +584,18 @@ function load$2() {
|
|
|
549
584
|
}
|
|
550
585
|
return state$3;
|
|
551
586
|
}
|
|
587
|
+
function persistNow$2() {
|
|
588
|
+
if (!state$3) return Promise.resolve();
|
|
589
|
+
saveDirty$2 = false;
|
|
590
|
+
return fs.promises.writeFile(getHighlightsPath(), JSON.stringify(state$3, null, 2), "utf-8").catch((err) => console.error("[Vessel] Failed to save highlights:", err));
|
|
591
|
+
}
|
|
552
592
|
function save$2() {
|
|
553
|
-
|
|
593
|
+
saveDirty$2 = true;
|
|
594
|
+
if (saveTimer$2) clearTimeout(saveTimer$2);
|
|
595
|
+
saveTimer$2 = setTimeout(() => {
|
|
596
|
+
saveTimer$2 = null;
|
|
597
|
+
void persistNow$2();
|
|
598
|
+
}, SAVE_DEBOUNCE_MS$2);
|
|
554
599
|
}
|
|
555
600
|
function emit$2() {
|
|
556
601
|
if (!state$3) return;
|
|
@@ -580,7 +625,7 @@ function getHighlightsForUrl(url) {
|
|
|
580
625
|
function addHighlight(url, selector, text, label, color, source) {
|
|
581
626
|
load$2();
|
|
582
627
|
const highlight = {
|
|
583
|
-
id: crypto.randomUUID(),
|
|
628
|
+
id: crypto$1.randomUUID(),
|
|
584
629
|
url: normalizeUrl(url),
|
|
585
630
|
selector: selector || void 0,
|
|
586
631
|
text: text || void 0,
|
|
@@ -631,6 +676,14 @@ function clearHighlightsForUrl(url) {
|
|
|
631
676
|
}
|
|
632
677
|
return removed;
|
|
633
678
|
}
|
|
679
|
+
function flushPersist$2() {
|
|
680
|
+
if (saveTimer$2) {
|
|
681
|
+
clearTimeout(saveTimer$2);
|
|
682
|
+
saveTimer$2 = null;
|
|
683
|
+
}
|
|
684
|
+
if (!saveDirty$2) return Promise.resolve();
|
|
685
|
+
return persistNow$2();
|
|
686
|
+
}
|
|
634
687
|
const HIGHLIGHT_COLORS = {
|
|
635
688
|
yellow: {
|
|
636
689
|
solid: "#f0c636",
|
|
@@ -1201,8 +1254,11 @@ function persistHighlight(url, text) {
|
|
|
1201
1254
|
return { success: true, text: capped, id: highlight.id };
|
|
1202
1255
|
}
|
|
1203
1256
|
const MAX_HISTORY_ENTRIES = 5e3;
|
|
1257
|
+
const SAVE_DEBOUNCE_MS$1 = 250;
|
|
1204
1258
|
let state$2 = null;
|
|
1205
1259
|
const listeners$1 = /* @__PURE__ */ new Set();
|
|
1260
|
+
let saveTimer$1 = null;
|
|
1261
|
+
let saveDirty$1 = false;
|
|
1206
1262
|
function getHistoryPath() {
|
|
1207
1263
|
return path.join(electron.app.getPath("userData"), "vessel-history.json");
|
|
1208
1264
|
}
|
|
@@ -1219,8 +1275,13 @@ function load$1() {
|
|
|
1219
1275
|
}
|
|
1220
1276
|
return state$2;
|
|
1221
1277
|
}
|
|
1222
|
-
function
|
|
1223
|
-
|
|
1278
|
+
function persistNow$1() {
|
|
1279
|
+
saveDirty$1 = false;
|
|
1280
|
+
if (saveTimer$1) {
|
|
1281
|
+
clearTimeout(saveTimer$1);
|
|
1282
|
+
saveTimer$1 = null;
|
|
1283
|
+
}
|
|
1284
|
+
return fs.promises.mkdir(path.dirname(getHistoryPath()), { recursive: true }).then(
|
|
1224
1285
|
() => fs.promises.writeFile(
|
|
1225
1286
|
getHistoryPath(),
|
|
1226
1287
|
JSON.stringify(state$2, null, 2),
|
|
@@ -1228,6 +1289,16 @@ function save$1() {
|
|
|
1228
1289
|
)
|
|
1229
1290
|
).catch((err) => console.error("[Vessel] Failed to save history:", err));
|
|
1230
1291
|
}
|
|
1292
|
+
function save$1() {
|
|
1293
|
+
saveDirty$1 = true;
|
|
1294
|
+
if (saveTimer$1) return;
|
|
1295
|
+
saveTimer$1 = setTimeout(() => {
|
|
1296
|
+
saveTimer$1 = null;
|
|
1297
|
+
if (saveDirty$1) {
|
|
1298
|
+
void persistNow$1();
|
|
1299
|
+
}
|
|
1300
|
+
}, SAVE_DEBOUNCE_MS$1);
|
|
1301
|
+
}
|
|
1231
1302
|
function emit$1() {
|
|
1232
1303
|
if (!state$2) return;
|
|
1233
1304
|
const snapshot = { entries: [...state$2.entries] };
|
|
@@ -1282,6 +1353,9 @@ function clearAll$1() {
|
|
|
1282
1353
|
save$1();
|
|
1283
1354
|
emit$1();
|
|
1284
1355
|
}
|
|
1356
|
+
function flushPersist$1() {
|
|
1357
|
+
return saveDirty$1 ? persistNow$1() : Promise.resolve();
|
|
1358
|
+
}
|
|
1285
1359
|
const MAX_CONSOLE_ENTRIES = 500;
|
|
1286
1360
|
const MAX_NETWORK_ENTRIES = 200;
|
|
1287
1361
|
const MAX_ERROR_ENTRIES = 200;
|
|
@@ -1999,7 +2073,7 @@ class TabManager {
|
|
|
1999
2073
|
}
|
|
2000
2074
|
createTab(url = "about:blank", options) {
|
|
2001
2075
|
const background = options?.background ?? false;
|
|
2002
|
-
const id = crypto.randomUUID();
|
|
2076
|
+
const id = crypto$1.randomUUID();
|
|
2003
2077
|
const tab = new Tab(id, url, () => this.broadcastState(), {
|
|
2004
2078
|
adBlockingEnabled: options?.adBlockingEnabled,
|
|
2005
2079
|
parentWindow: this.window,
|
|
@@ -2039,6 +2113,10 @@ class TabManager {
|
|
|
2039
2113
|
closeTab(id) {
|
|
2040
2114
|
const tab = this.tabs.get(id);
|
|
2041
2115
|
if (!tab) return;
|
|
2116
|
+
const wcId = tab.webContentsId;
|
|
2117
|
+
if (wcId !== void 0) {
|
|
2118
|
+
this.lastReapply.delete(wcId);
|
|
2119
|
+
}
|
|
2042
2120
|
destroySession(id);
|
|
2043
2121
|
this.window.contentView.removeChildView(tab.view);
|
|
2044
2122
|
tab.destroy();
|
|
@@ -2296,6 +2374,10 @@ const Channels = {
|
|
|
2296
2374
|
AI_STREAM_START: "ai:stream-start",
|
|
2297
2375
|
AI_STREAM_CHUNK: "ai:stream-chunk",
|
|
2298
2376
|
AI_STREAM_END: "ai:stream-end",
|
|
2377
|
+
AI_STREAM_IDLE: "ai:stream-idle",
|
|
2378
|
+
AUTOMATION_ACTIVITY_START: "automation:activity-start",
|
|
2379
|
+
AUTOMATION_ACTIVITY_CHUNK: "automation:activity-chunk",
|
|
2380
|
+
AUTOMATION_ACTIVITY_END: "automation:activity-end",
|
|
2299
2381
|
AI_CANCEL: "ai:cancel",
|
|
2300
2382
|
AI_FETCH_MODELS: "ai:fetch-models",
|
|
2301
2383
|
AGENT_RUNTIME_GET: "agent-runtime:get",
|
|
@@ -2324,6 +2406,7 @@ const Channels = {
|
|
|
2324
2406
|
SETTINGS_SET: "settings:set",
|
|
2325
2407
|
SETTINGS_UPDATE: "settings:update",
|
|
2326
2408
|
SETTINGS_HEALTH_GET: "settings:health:get",
|
|
2409
|
+
SETTINGS_HEALTH_UPDATE: "settings:health:update",
|
|
2327
2410
|
// Bookmarks
|
|
2328
2411
|
BOOKMARKS_GET: "bookmarks:get",
|
|
2329
2412
|
BOOKMARKS_UPDATE: "bookmarks:update",
|
|
@@ -2338,6 +2421,7 @@ const Channels = {
|
|
|
2338
2421
|
HIGHLIGHT_CAPTURE_RESULT: "highlights:capture-result",
|
|
2339
2422
|
HIGHLIGHT_SELECTION: "vessel:highlight-selection",
|
|
2340
2423
|
HIGHLIGHT_NAV_COUNT: "highlights:nav-count",
|
|
2424
|
+
HIGHLIGHT_COUNT_UPDATE: "highlights:count-update",
|
|
2341
2425
|
HIGHLIGHT_NAV_SCROLL: "highlights:nav-scroll",
|
|
2342
2426
|
HIGHLIGHT_NAV_REMOVE: "highlights:nav-remove",
|
|
2343
2427
|
HIGHLIGHT_NAV_CLEAR: "highlights:nav-clear",
|
|
@@ -2373,6 +2457,16 @@ const Channels = {
|
|
|
2373
2457
|
VAULT_UPDATE: "vault:update",
|
|
2374
2458
|
VAULT_REMOVE: "vault:remove",
|
|
2375
2459
|
VAULT_AUDIT_LOG: "vault:audit-log",
|
|
2460
|
+
// Automation kits
|
|
2461
|
+
AUTOMATION_GET_INSTALLED: "automation:get-installed",
|
|
2462
|
+
AUTOMATION_INSTALL_FROM_FILE: "automation:install-from-file",
|
|
2463
|
+
AUTOMATION_UNINSTALL: "automation:uninstall",
|
|
2464
|
+
// Scheduled jobs
|
|
2465
|
+
SCHEDULE_GET_ALL: "schedule:get-all",
|
|
2466
|
+
SCHEDULE_CREATE: "schedule:create",
|
|
2467
|
+
SCHEDULE_UPDATE: "schedule:update",
|
|
2468
|
+
SCHEDULE_DELETE: "schedule:delete",
|
|
2469
|
+
SCHEDULE_JOBS_UPDATE: "schedule:jobs-update",
|
|
2376
2470
|
// Window controls
|
|
2377
2471
|
WINDOW_MINIMIZE: "window:minimize",
|
|
2378
2472
|
WINDOW_MAXIMIZE: "window:maximize",
|
|
@@ -2533,6 +2627,7 @@ function createMainWindow(onTabStateChange) {
|
|
|
2533
2627
|
minWidth: 800,
|
|
2534
2628
|
minHeight: 600,
|
|
2535
2629
|
frame: false,
|
|
2630
|
+
show: false,
|
|
2536
2631
|
backgroundColor: "#1a1a1e",
|
|
2537
2632
|
icon: getWindowIconPath()
|
|
2538
2633
|
});
|
|
@@ -2575,7 +2670,7 @@ function createMainWindow(onTabStateChange) {
|
|
|
2575
2670
|
enableClipboardShortcuts(devtoolsPanelView);
|
|
2576
2671
|
const settings2 = loadSettings();
|
|
2577
2672
|
const uiState = {
|
|
2578
|
-
sidebarOpen:
|
|
2673
|
+
sidebarOpen: true,
|
|
2579
2674
|
sidebarWidth: settings2.sidebarWidth,
|
|
2580
2675
|
focusMode: false,
|
|
2581
2676
|
settingsOpen: false,
|
|
@@ -3392,7 +3487,7 @@ function getDeviceId() {
|
|
|
3392
3487
|
if (deviceId) return deviceId;
|
|
3393
3488
|
} catch {
|
|
3394
3489
|
}
|
|
3395
|
-
deviceId = crypto.randomUUID();
|
|
3490
|
+
deviceId = crypto$1.randomUUID();
|
|
3396
3491
|
try {
|
|
3397
3492
|
fs.mkdirSync(path.dirname(idPath), { recursive: true });
|
|
3398
3493
|
fs.writeFileSync(idPath, deviceId, "utf-8");
|
|
@@ -4705,15 +4800,28 @@ function escapeHtml(str) {
|
|
|
4705
4800
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
4706
4801
|
}
|
|
4707
4802
|
const mcpStatusChangeListeners = /* @__PURE__ */ new Set();
|
|
4803
|
+
const runtimeHealthChangeListeners = /* @__PURE__ */ new Set();
|
|
4708
4804
|
function onMcpStatusChange(listener) {
|
|
4709
4805
|
mcpStatusChangeListeners.add(listener);
|
|
4710
4806
|
return () => {
|
|
4711
4807
|
mcpStatusChangeListeners.delete(listener);
|
|
4712
4808
|
};
|
|
4713
4809
|
}
|
|
4810
|
+
function onRuntimeHealthChange(listener) {
|
|
4811
|
+
runtimeHealthChangeListeners.add(listener);
|
|
4812
|
+
return () => {
|
|
4813
|
+
runtimeHealthChangeListeners.delete(listener);
|
|
4814
|
+
};
|
|
4815
|
+
}
|
|
4714
4816
|
function getMcpStatus() {
|
|
4715
4817
|
return state$1.mcp.status;
|
|
4716
4818
|
}
|
|
4819
|
+
function emitRuntimeHealthChange() {
|
|
4820
|
+
const snapshot = getRuntimeHealth();
|
|
4821
|
+
for (const listener of runtimeHealthChangeListeners) {
|
|
4822
|
+
listener(snapshot);
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
4717
4825
|
const state$1 = {
|
|
4718
4826
|
userDataPath: "",
|
|
4719
4827
|
settingsPath: "",
|
|
@@ -4734,9 +4842,11 @@ function initializeRuntimeHealth(paths) {
|
|
|
4734
4842
|
state$1.mcp.endpoint = null;
|
|
4735
4843
|
state$1.mcp.status = "stopped";
|
|
4736
4844
|
state$1.mcp.message = "MCP server has not started yet.";
|
|
4845
|
+
emitRuntimeHealthChange();
|
|
4737
4846
|
}
|
|
4738
4847
|
function setStartupIssues(issues) {
|
|
4739
4848
|
state$1.startupIssues = issues.map((issue) => ({ ...issue }));
|
|
4849
|
+
emitRuntimeHealthChange();
|
|
4740
4850
|
}
|
|
4741
4851
|
function getRuntimeHealth() {
|
|
4742
4852
|
return {
|
|
@@ -4764,6 +4874,7 @@ function setMcpHealth(update) {
|
|
|
4764
4874
|
listener(state$1.mcp.status);
|
|
4765
4875
|
}
|
|
4766
4876
|
}
|
|
4877
|
+
emitRuntimeHealthChange();
|
|
4767
4878
|
}
|
|
4768
4879
|
const VAULT_FILENAME = "vessel-vault.enc";
|
|
4769
4880
|
const KEY_FILENAME = "vessel-vault.key";
|
|
@@ -4789,7 +4900,7 @@ function getOrCreateEncryptionKey() {
|
|
|
4789
4900
|
}
|
|
4790
4901
|
return encryptedKey;
|
|
4791
4902
|
}
|
|
4792
|
-
const key = crypto$
|
|
4903
|
+
const key = crypto$2.randomBytes(32);
|
|
4793
4904
|
fs$1.mkdirSync(path$1.dirname(keyPath), { recursive: true });
|
|
4794
4905
|
if (electron.safeStorage.isEncryptionAvailable()) {
|
|
4795
4906
|
const encrypted = electron.safeStorage.encryptString(key.toString("utf-8"));
|
|
@@ -4802,8 +4913,8 @@ function getOrCreateEncryptionKey() {
|
|
|
4802
4913
|
}
|
|
4803
4914
|
function encrypt(plaintext) {
|
|
4804
4915
|
const key = getOrCreateEncryptionKey();
|
|
4805
|
-
const iv = crypto$
|
|
4806
|
-
const cipher = crypto$
|
|
4916
|
+
const iv = crypto$2.randomBytes(IV_LENGTH);
|
|
4917
|
+
const cipher = crypto$2.createCipheriv(ALGORITHM, key, iv, {
|
|
4807
4918
|
authTagLength: AUTH_TAG_LENGTH
|
|
4808
4919
|
});
|
|
4809
4920
|
const encrypted = Buffer.concat([
|
|
@@ -4818,7 +4929,7 @@ function decrypt(data) {
|
|
|
4818
4929
|
const iv = data.subarray(0, IV_LENGTH);
|
|
4819
4930
|
const authTag = data.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH);
|
|
4820
4931
|
const ciphertext = data.subarray(IV_LENGTH + AUTH_TAG_LENGTH);
|
|
4821
|
-
const decipher = crypto$
|
|
4932
|
+
const decipher = crypto$2.createDecipheriv(ALGORITHM, key, iv, {
|
|
4822
4933
|
authTagLength: AUTH_TAG_LENGTH
|
|
4823
4934
|
});
|
|
4824
4935
|
decipher.setAuthTag(authTag);
|
|
@@ -4877,7 +4988,7 @@ function addEntry(entry) {
|
|
|
4877
4988
|
const entries = loadVault();
|
|
4878
4989
|
const newEntry = {
|
|
4879
4990
|
...entry,
|
|
4880
|
-
id: crypto$
|
|
4991
|
+
id: crypto$2.randomUUID(),
|
|
4881
4992
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4882
4993
|
useCount: 0
|
|
4883
4994
|
};
|
|
@@ -4936,7 +5047,7 @@ function generateTotpCode(secret) {
|
|
|
4936
5047
|
const counterBuf = Buffer.alloc(8);
|
|
4937
5048
|
counterBuf.writeUInt32BE(Math.floor(counter / 4294967296), 0);
|
|
4938
5049
|
counterBuf.writeUInt32BE(counter & 4294967295, 4);
|
|
4939
|
-
const hmac = crypto$
|
|
5050
|
+
const hmac = crypto$2.createHmac("sha1", keyBytes).update(counterBuf).digest();
|
|
4940
5051
|
const offset = hmac[hmac.length - 1] & 15;
|
|
4941
5052
|
const code = (hmac[offset] & 127) << 24 | (hmac[offset + 1] & 255) << 16 | (hmac[offset + 2] & 255) << 8 | hmac[offset + 3] & 255;
|
|
4942
5053
|
return (code % 1e6).toString().padStart(6, "0");
|
|
@@ -5542,6 +5653,48 @@ function createProvider(config) {
|
|
|
5542
5653
|
}
|
|
5543
5654
|
return new OpenAICompatProvider(normalized);
|
|
5544
5655
|
}
|
|
5656
|
+
const require$1 = node_module.createRequire(require("url").pathToFileURL(__filename).href);
|
|
5657
|
+
let cachedFactory;
|
|
5658
|
+
function createNoopTraceSession() {
|
|
5659
|
+
return {
|
|
5660
|
+
logToolCall() {
|
|
5661
|
+
},
|
|
5662
|
+
end() {
|
|
5663
|
+
}
|
|
5664
|
+
};
|
|
5665
|
+
}
|
|
5666
|
+
function getCandidatePaths() {
|
|
5667
|
+
const roots = /* @__PURE__ */ new Set([
|
|
5668
|
+
process.cwd(),
|
|
5669
|
+
electron.app.getAppPath(),
|
|
5670
|
+
path$1.join(electron.app.getAppPath(), "..")
|
|
5671
|
+
]);
|
|
5672
|
+
return Array.from(roots).map(
|
|
5673
|
+
(root) => path$1.join(root, "src", "main", "telemetry", "trace-logger.local.cjs")
|
|
5674
|
+
);
|
|
5675
|
+
}
|
|
5676
|
+
function loadLocalFactory() {
|
|
5677
|
+
if (cachedFactory !== void 0) return cachedFactory;
|
|
5678
|
+
cachedFactory = null;
|
|
5679
|
+
if (electron.app.isPackaged) return cachedFactory;
|
|
5680
|
+
for (const candidate of getCandidatePaths()) {
|
|
5681
|
+
if (!fs$1.existsSync(candidate)) continue;
|
|
5682
|
+
try {
|
|
5683
|
+
const loaded = require$1(candidate);
|
|
5684
|
+
if (typeof loaded.createTraceSession === "function") {
|
|
5685
|
+
cachedFactory = loaded.createTraceSession;
|
|
5686
|
+
return cachedFactory;
|
|
5687
|
+
}
|
|
5688
|
+
} catch (err) {
|
|
5689
|
+
console.warn("[dev-trace] Failed to load local trace logger:", err);
|
|
5690
|
+
}
|
|
5691
|
+
}
|
|
5692
|
+
return cachedFactory;
|
|
5693
|
+
}
|
|
5694
|
+
function createTraceSession(query, url, title) {
|
|
5695
|
+
const factory = loadLocalFactory();
|
|
5696
|
+
return factory ? factory(query, url, title) : createNoopTraceSession();
|
|
5697
|
+
}
|
|
5545
5698
|
const CORRECT_HINT_RE = /\b(correct|right choice|this is correct|correct answer|pick this|select this|choose this|right answer)\b/i;
|
|
5546
5699
|
const WRONG_HINT_RE = /\b(wrong|incorrect|not this|don't pick|do not pick|bad option|decoy)\b/i;
|
|
5547
5700
|
function elementLabel(el) {
|
|
@@ -7186,7 +7339,7 @@ const TOOL_DEFINITIONS = [
|
|
|
7186
7339
|
{
|
|
7187
7340
|
name: "click",
|
|
7188
7341
|
title: "Click Element",
|
|
7189
|
-
description: "Click an element on the page by its index number or CSS selector.",
|
|
7342
|
+
description: "Click an element on the page by its index number or CSS selector. Use this to check or uncheck checkboxes and to select radio buttons — do NOT use select_option for those.",
|
|
7190
7343
|
inputSchema: {
|
|
7191
7344
|
index: zod.z.number().optional().describe("Element index from the page content listing"),
|
|
7192
7345
|
selector: zod.z.string().optional().describe("CSS selector as fallback")
|
|
@@ -7211,7 +7364,7 @@ const TOOL_DEFINITIONS = [
|
|
|
7211
7364
|
{
|
|
7212
7365
|
name: "select_option",
|
|
7213
7366
|
title: "Select Option",
|
|
7214
|
-
description: "Select an option in a dropdown by visible label or option value.",
|
|
7367
|
+
description: "Select an option in a <select> dropdown by visible label or option value. Only works on <select> elements — for checkboxes or radio buttons use click instead.",
|
|
7215
7368
|
inputSchema: {
|
|
7216
7369
|
index: zod.z.number().optional().describe("The select element index number"),
|
|
7217
7370
|
selector: zod.z.string().optional().describe("CSS selector as fallback"),
|
|
@@ -8021,8 +8174,11 @@ function getBookmarkSearchMatch(args) {
|
|
|
8021
8174
|
}
|
|
8022
8175
|
const UNSORTED_ID = "unsorted";
|
|
8023
8176
|
const ARCHIVE_FOLDER_NAME = "Archive";
|
|
8177
|
+
const SAVE_DEBOUNCE_MS = 250;
|
|
8024
8178
|
let state = null;
|
|
8025
8179
|
const listeners = /* @__PURE__ */ new Set();
|
|
8180
|
+
let saveTimer = null;
|
|
8181
|
+
let saveDirty = false;
|
|
8026
8182
|
function cloneState(current) {
|
|
8027
8183
|
return {
|
|
8028
8184
|
folders: current.folders.map((folder) => ({ ...folder })),
|
|
@@ -8046,8 +8202,13 @@ function load() {
|
|
|
8046
8202
|
}
|
|
8047
8203
|
return state;
|
|
8048
8204
|
}
|
|
8049
|
-
function
|
|
8050
|
-
|
|
8205
|
+
function persistNow() {
|
|
8206
|
+
saveDirty = false;
|
|
8207
|
+
if (saveTimer) {
|
|
8208
|
+
clearTimeout(saveTimer);
|
|
8209
|
+
saveTimer = null;
|
|
8210
|
+
}
|
|
8211
|
+
return fs.promises.mkdir(path.dirname(getBookmarksPath()), { recursive: true }).then(
|
|
8051
8212
|
() => fs.promises.writeFile(
|
|
8052
8213
|
getBookmarksPath(),
|
|
8053
8214
|
JSON.stringify(state, null, 2),
|
|
@@ -8055,6 +8216,16 @@ function save() {
|
|
|
8055
8216
|
)
|
|
8056
8217
|
).catch((err) => console.error("[Vessel] Failed to save bookmarks:", err));
|
|
8057
8218
|
}
|
|
8219
|
+
function save() {
|
|
8220
|
+
saveDirty = true;
|
|
8221
|
+
if (saveTimer) return;
|
|
8222
|
+
saveTimer = setTimeout(() => {
|
|
8223
|
+
saveTimer = null;
|
|
8224
|
+
if (saveDirty) {
|
|
8225
|
+
void persistNow();
|
|
8226
|
+
}
|
|
8227
|
+
}, SAVE_DEBOUNCE_MS);
|
|
8228
|
+
}
|
|
8058
8229
|
function emit() {
|
|
8059
8230
|
if (!state) return;
|
|
8060
8231
|
const snapshot = cloneState(state);
|
|
@@ -8168,7 +8339,7 @@ function createFolderWithSummary(name, summary) {
|
|
|
8168
8339
|
const trimmed = name.trim();
|
|
8169
8340
|
if (!trimmed) throw new Error("Folder name cannot be empty");
|
|
8170
8341
|
const folder = {
|
|
8171
|
-
id: crypto.randomUUID(),
|
|
8342
|
+
id: crypto$1.randomUUID(),
|
|
8172
8343
|
name: trimmed,
|
|
8173
8344
|
summary: summary?.trim() || void 0,
|
|
8174
8345
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -8236,7 +8407,7 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
|
|
|
8236
8407
|
}
|
|
8237
8408
|
}
|
|
8238
8409
|
const bookmark = {
|
|
8239
|
-
id: crypto.randomUUID(),
|
|
8410
|
+
id: crypto$1.randomUUID(),
|
|
8240
8411
|
url: normalizedUrl,
|
|
8241
8412
|
title: normalizedTitle,
|
|
8242
8413
|
note: note?.trim() || void 0,
|
|
@@ -8305,6 +8476,9 @@ function renameFolder(id, newName, summary) {
|
|
|
8305
8476
|
emit();
|
|
8306
8477
|
return { ...folder };
|
|
8307
8478
|
}
|
|
8479
|
+
function flushPersist() {
|
|
8480
|
+
return saveDirty ? persistNow() : Promise.resolve();
|
|
8481
|
+
}
|
|
8308
8482
|
function normalizeText(text) {
|
|
8309
8483
|
return text?.trim() ?? "";
|
|
8310
8484
|
}
|
|
@@ -8531,7 +8705,7 @@ function normalizeSessionName(name) {
|
|
|
8531
8705
|
function sessionFileName(name) {
|
|
8532
8706
|
const normalized = normalizeSessionName(name).toLowerCase();
|
|
8533
8707
|
const slug = normalized.replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "session";
|
|
8534
|
-
const hash = crypto$
|
|
8708
|
+
const hash = crypto$2.createHash("sha256").update(normalized).digest("hex").slice(0, 8);
|
|
8535
8709
|
return `${slug}-${hash}.json`;
|
|
8536
8710
|
}
|
|
8537
8711
|
function getSessionPath(name) {
|
|
@@ -12660,13 +12834,38 @@ Instructions:
|
|
|
12660
12834
|
pageType,
|
|
12661
12835
|
query
|
|
12662
12836
|
);
|
|
12837
|
+
const trace = createTraceSession(query, activeTabUrl, activeTabTitle);
|
|
12838
|
+
let accumulatedResponse = "";
|
|
12839
|
+
const tracedOnChunk = (text) => {
|
|
12840
|
+
accumulatedResponse += text;
|
|
12841
|
+
onChunk(text);
|
|
12842
|
+
};
|
|
12843
|
+
const tracedOnEnd = () => {
|
|
12844
|
+
trace.end(accumulatedResponse);
|
|
12845
|
+
onEnd();
|
|
12846
|
+
};
|
|
12847
|
+
const tracedExecuteAction = async (name, args) => {
|
|
12848
|
+
const t0 = Date.now();
|
|
12849
|
+
let output = "";
|
|
12850
|
+
let isError = false;
|
|
12851
|
+
try {
|
|
12852
|
+
output = await executeAction(name, args, actionCtx);
|
|
12853
|
+
} catch (err) {
|
|
12854
|
+
isError = true;
|
|
12855
|
+
output = err instanceof Error ? err.message : String(err);
|
|
12856
|
+
throw err;
|
|
12857
|
+
} finally {
|
|
12858
|
+
trace.logToolCall(name, args, output, Date.now() - t0, isError);
|
|
12859
|
+
}
|
|
12860
|
+
return output;
|
|
12861
|
+
};
|
|
12663
12862
|
await provider.streamAgentQuery(
|
|
12664
12863
|
systemPrompt,
|
|
12665
12864
|
query,
|
|
12666
12865
|
contextualTools,
|
|
12667
|
-
|
|
12668
|
-
|
|
12669
|
-
|
|
12866
|
+
tracedOnChunk,
|
|
12867
|
+
tracedExecuteAction,
|
|
12868
|
+
tracedOnEnd,
|
|
12670
12869
|
history
|
|
12671
12870
|
);
|
|
12672
12871
|
return;
|
|
@@ -12696,6 +12895,27 @@ Instructions:
|
|
|
12696
12895
|
history
|
|
12697
12896
|
);
|
|
12698
12897
|
}
|
|
12898
|
+
let activeSource = null;
|
|
12899
|
+
const idleListeners = /* @__PURE__ */ new Set();
|
|
12900
|
+
function tryBeginAIStream(source) {
|
|
12901
|
+
if (activeSource !== null) return false;
|
|
12902
|
+
activeSource = source;
|
|
12903
|
+
return true;
|
|
12904
|
+
}
|
|
12905
|
+
function endAIStream(source) {
|
|
12906
|
+
if (activeSource !== source) return;
|
|
12907
|
+
activeSource = null;
|
|
12908
|
+
for (const listener of idleListeners) {
|
|
12909
|
+
listener();
|
|
12910
|
+
}
|
|
12911
|
+
}
|
|
12912
|
+
function isAIStreamActive() {
|
|
12913
|
+
return activeSource !== null;
|
|
12914
|
+
}
|
|
12915
|
+
function onAIStreamIdle(listener) {
|
|
12916
|
+
idleListeners.add(listener);
|
|
12917
|
+
return () => idleListeners.delete(listener);
|
|
12918
|
+
}
|
|
12699
12919
|
const DEFAULT_PAGE_FOLDER = "Vessel/Pages";
|
|
12700
12920
|
const DEFAULT_NOTE_FOLDER = "Vessel/Research";
|
|
12701
12921
|
const DEFAULT_BOOKMARK_FOLDER = "Vessel/Bookmarks";
|
|
@@ -15214,6 +15434,13 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
15214
15434
|
`Navigation blocked: ${url} returned ${preCheck.detail || "dead link"}. Try a different URL or go back and choose another link.`
|
|
15215
15435
|
);
|
|
15216
15436
|
}
|
|
15437
|
+
try {
|
|
15438
|
+
assertSafeURL(url);
|
|
15439
|
+
} catch (err) {
|
|
15440
|
+
return asTextResponse(
|
|
15441
|
+
`Navigation blocked: ${err instanceof Error ? err.message : "Unsafe URL scheme"}`
|
|
15442
|
+
);
|
|
15443
|
+
}
|
|
15217
15444
|
return withAction(runtime2, tabManager, "navigate", { url }, async () => {
|
|
15218
15445
|
const id = tabManager.getActiveTabId();
|
|
15219
15446
|
const navError = tabManager.navigateTab(id, url, postBody);
|
|
@@ -15894,27 +16121,6 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
15894
16121
|
}
|
|
15895
16122
|
)
|
|
15896
16123
|
);
|
|
15897
|
-
server.registerTool(
|
|
15898
|
-
"vessel_create_checkpoint",
|
|
15899
|
-
{
|
|
15900
|
-
title: "Create Checkpoint",
|
|
15901
|
-
description: "Alias for vessel_checkpoint_create. Capture the current session as a checkpoint.",
|
|
15902
|
-
inputSchema: {
|
|
15903
|
-
name: zod.z.string().optional().describe("Optional checkpoint name"),
|
|
15904
|
-
note: zod.z.string().optional().describe("Optional note")
|
|
15905
|
-
}
|
|
15906
|
-
},
|
|
15907
|
-
async ({ name, note }) => withAction(
|
|
15908
|
-
runtime2,
|
|
15909
|
-
tabManager,
|
|
15910
|
-
"create_checkpoint",
|
|
15911
|
-
{ name, note },
|
|
15912
|
-
async () => {
|
|
15913
|
-
const checkpoint = runtime2.createCheckpoint(name, note);
|
|
15914
|
-
return `Created checkpoint ${checkpoint.name} (${checkpoint.id})`;
|
|
15915
|
-
}
|
|
15916
|
-
)
|
|
15917
|
-
);
|
|
15918
16124
|
server.registerTool(
|
|
15919
16125
|
"vessel_checkpoint_restore",
|
|
15920
16126
|
{
|
|
@@ -15941,32 +16147,6 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
15941
16147
|
}
|
|
15942
16148
|
)
|
|
15943
16149
|
);
|
|
15944
|
-
server.registerTool(
|
|
15945
|
-
"vessel_restore_checkpoint",
|
|
15946
|
-
{
|
|
15947
|
-
title: "Restore Checkpoint",
|
|
15948
|
-
description: "Alias for vessel_checkpoint_restore. Restore a saved checkpoint by ID or exact name.",
|
|
15949
|
-
inputSchema: {
|
|
15950
|
-
checkpointId: zod.z.string().optional().describe("Checkpoint ID"),
|
|
15951
|
-
name: zod.z.string().optional().describe("Exact checkpoint name")
|
|
15952
|
-
}
|
|
15953
|
-
},
|
|
15954
|
-
async ({ checkpointId, name }) => withAction(
|
|
15955
|
-
runtime2,
|
|
15956
|
-
tabManager,
|
|
15957
|
-
"restore_checkpoint",
|
|
15958
|
-
{ checkpointId, name },
|
|
15959
|
-
async () => {
|
|
15960
|
-
const state2 = runtime2.getState();
|
|
15961
|
-
const checkpoint = state2.checkpoints.find((item) => item.id === checkpointId) || state2.checkpoints.find((item) => item.name === name);
|
|
15962
|
-
if (!checkpoint) {
|
|
15963
|
-
return "Error: No matching checkpoint found";
|
|
15964
|
-
}
|
|
15965
|
-
runtime2.restoreCheckpoint(checkpoint.id);
|
|
15966
|
-
return `Restored checkpoint ${checkpoint.name}`;
|
|
15967
|
-
}
|
|
15968
|
-
)
|
|
15969
|
-
);
|
|
15970
16150
|
server.registerTool(
|
|
15971
16151
|
"vessel_save_session",
|
|
15972
16152
|
{
|
|
@@ -18099,7 +18279,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
18099
18279
|
status: "starting",
|
|
18100
18280
|
message: `Starting MCP server on port ${port}.`
|
|
18101
18281
|
});
|
|
18102
|
-
mcpAuthToken = crypto$
|
|
18282
|
+
mcpAuthToken = crypto$2.randomBytes(32).toString("hex");
|
|
18103
18283
|
return new Promise((resolve) => {
|
|
18104
18284
|
const server = http.createServer(async (req, res) => {
|
|
18105
18285
|
const url = new URL(req.url || "/", `http://localhost:${port}`);
|
|
@@ -18190,8 +18370,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
18190
18370
|
status: "ready",
|
|
18191
18371
|
message: `MCP server listening on ${endpoint}.`
|
|
18192
18372
|
});
|
|
18193
|
-
console.log(`[Vessel MCP] Server listening on ${endpoint}`);
|
|
18194
|
-
console.log(`[Vessel MCP] Auth token: ${mcpAuthToken}`);
|
|
18373
|
+
console.log(`[Vessel MCP] Server listening on ${endpoint} (auth enabled)`);
|
|
18195
18374
|
finish({
|
|
18196
18375
|
ok: true,
|
|
18197
18376
|
configuredPort: port,
|
|
@@ -18229,6 +18408,372 @@ function stopMcpServer() {
|
|
|
18229
18408
|
});
|
|
18230
18409
|
});
|
|
18231
18410
|
}
|
|
18411
|
+
const BUNDLED_KIT_IDS = /* @__PURE__ */ new Set([
|
|
18412
|
+
"research-collect",
|
|
18413
|
+
"price-scout",
|
|
18414
|
+
"form-filler"
|
|
18415
|
+
]);
|
|
18416
|
+
function getUserKitsDir() {
|
|
18417
|
+
return path$1.join(electron.app.getPath("userData"), "kits");
|
|
18418
|
+
}
|
|
18419
|
+
function ensureKitsDir() {
|
|
18420
|
+
const dir = getUserKitsDir();
|
|
18421
|
+
if (!fs$1.existsSync(dir)) {
|
|
18422
|
+
fs$1.mkdirSync(dir, { recursive: true });
|
|
18423
|
+
}
|
|
18424
|
+
}
|
|
18425
|
+
function isValidKit(value) {
|
|
18426
|
+
if (!value || typeof value !== "object") return false;
|
|
18427
|
+
const k = value;
|
|
18428
|
+
return typeof k.id === "string" && k.id.length > 0 && typeof k.name === "string" && k.name.length > 0 && typeof k.description === "string" && typeof k.icon === "string" && typeof k.promptTemplate === "string" && k.promptTemplate.length > 0 && Array.isArray(k.inputs);
|
|
18429
|
+
}
|
|
18430
|
+
function getInstalledKits() {
|
|
18431
|
+
ensureKitsDir();
|
|
18432
|
+
const dir = getUserKitsDir();
|
|
18433
|
+
let files;
|
|
18434
|
+
try {
|
|
18435
|
+
files = fs$1.readdirSync(dir).filter((f) => f.endsWith(".kit.json"));
|
|
18436
|
+
} catch {
|
|
18437
|
+
return [];
|
|
18438
|
+
}
|
|
18439
|
+
const kits = [];
|
|
18440
|
+
for (const file of files) {
|
|
18441
|
+
try {
|
|
18442
|
+
const raw = fs$1.readFileSync(path$1.join(dir, file), "utf-8");
|
|
18443
|
+
const parsed = JSON.parse(raw);
|
|
18444
|
+
if (isValidKit(parsed)) {
|
|
18445
|
+
kits.push(parsed);
|
|
18446
|
+
} else {
|
|
18447
|
+
console.warn(`[kit-registry] Skipping invalid kit file: ${file}`);
|
|
18448
|
+
}
|
|
18449
|
+
} catch {
|
|
18450
|
+
console.warn(`[kit-registry] Failed to read kit file: ${file}`);
|
|
18451
|
+
}
|
|
18452
|
+
}
|
|
18453
|
+
return kits;
|
|
18454
|
+
}
|
|
18455
|
+
async function installKitFromFile() {
|
|
18456
|
+
const { canceled, filePaths } = await electron.dialog.showOpenDialog({
|
|
18457
|
+
title: "Install Automation Kit",
|
|
18458
|
+
filters: [{ name: "Automation Kit", extensions: ["kit.json", "json"] }],
|
|
18459
|
+
properties: ["openFile"]
|
|
18460
|
+
});
|
|
18461
|
+
if (canceled || filePaths.length === 0) {
|
|
18462
|
+
return { ok: false, error: "canceled" };
|
|
18463
|
+
}
|
|
18464
|
+
let raw;
|
|
18465
|
+
try {
|
|
18466
|
+
raw = fs$1.readFileSync(filePaths[0], "utf-8");
|
|
18467
|
+
} catch {
|
|
18468
|
+
return { ok: false, error: "Could not read the selected file." };
|
|
18469
|
+
}
|
|
18470
|
+
let parsed;
|
|
18471
|
+
try {
|
|
18472
|
+
parsed = JSON.parse(raw);
|
|
18473
|
+
} catch {
|
|
18474
|
+
return { ok: false, error: "File is not valid JSON." };
|
|
18475
|
+
}
|
|
18476
|
+
if (!isValidKit(parsed)) {
|
|
18477
|
+
return {
|
|
18478
|
+
ok: false,
|
|
18479
|
+
error: "File is not a valid automation kit. Required fields: id, name, description, icon, inputs, promptTemplate."
|
|
18480
|
+
};
|
|
18481
|
+
}
|
|
18482
|
+
if (BUNDLED_KIT_IDS.has(parsed.id)) {
|
|
18483
|
+
return {
|
|
18484
|
+
ok: false,
|
|
18485
|
+
error: `Kit id "${parsed.id}" conflicts with a built-in kit and cannot be overwritten.`
|
|
18486
|
+
};
|
|
18487
|
+
}
|
|
18488
|
+
ensureKitsDir();
|
|
18489
|
+
const dest = path$1.join(getUserKitsDir(), `${parsed.id}.kit.json`);
|
|
18490
|
+
try {
|
|
18491
|
+
fs$1.writeFileSync(dest, JSON.stringify(parsed, null, 2), "utf-8");
|
|
18492
|
+
} catch {
|
|
18493
|
+
return { ok: false, error: "Failed to save the kit file." };
|
|
18494
|
+
}
|
|
18495
|
+
return { ok: true, kit: parsed };
|
|
18496
|
+
}
|
|
18497
|
+
function uninstallKit(id) {
|
|
18498
|
+
if (BUNDLED_KIT_IDS.has(id)) {
|
|
18499
|
+
return { ok: false, error: "Built-in kits cannot be removed." };
|
|
18500
|
+
}
|
|
18501
|
+
ensureKitsDir();
|
|
18502
|
+
const target = path$1.join(getUserKitsDir(), `${id}.kit.json`);
|
|
18503
|
+
if (!fs$1.existsSync(target)) {
|
|
18504
|
+
return { ok: false, error: "Kit not found." };
|
|
18505
|
+
}
|
|
18506
|
+
try {
|
|
18507
|
+
fs$1.unlinkSync(target);
|
|
18508
|
+
return { ok: true };
|
|
18509
|
+
} catch {
|
|
18510
|
+
return { ok: false, error: "Failed to remove the kit file." };
|
|
18511
|
+
}
|
|
18512
|
+
}
|
|
18513
|
+
let jobs = [];
|
|
18514
|
+
let removeIdleListener = null;
|
|
18515
|
+
let broadcastFn = null;
|
|
18516
|
+
function getJobsPath() {
|
|
18517
|
+
return path$1.join(electron.app.getPath("userData"), "scheduled-jobs.json");
|
|
18518
|
+
}
|
|
18519
|
+
function loadJobs() {
|
|
18520
|
+
try {
|
|
18521
|
+
const raw = fs$1.readFileSync(getJobsPath(), "utf-8");
|
|
18522
|
+
const parsed = JSON.parse(raw);
|
|
18523
|
+
if (Array.isArray(parsed)) {
|
|
18524
|
+
jobs = parsed;
|
|
18525
|
+
}
|
|
18526
|
+
} catch {
|
|
18527
|
+
jobs = [];
|
|
18528
|
+
}
|
|
18529
|
+
}
|
|
18530
|
+
function saveJobs() {
|
|
18531
|
+
try {
|
|
18532
|
+
fs$1.writeFileSync(getJobsPath(), JSON.stringify(jobs, null, 2), "utf-8");
|
|
18533
|
+
} catch (err) {
|
|
18534
|
+
console.warn("[scheduler] Failed to save jobs:", err);
|
|
18535
|
+
}
|
|
18536
|
+
}
|
|
18537
|
+
function normalizeJob(job, now = /* @__PURE__ */ new Date()) {
|
|
18538
|
+
if (!job.enabled) return false;
|
|
18539
|
+
if (job.schedule.type === "once") {
|
|
18540
|
+
const runAt = job.schedule.runAt ? new Date(job.schedule.runAt) : null;
|
|
18541
|
+
if (!runAt || Number.isNaN(runAt.getTime()) || runAt <= now) {
|
|
18542
|
+
job.enabled = false;
|
|
18543
|
+
return true;
|
|
18544
|
+
}
|
|
18545
|
+
const nextRunAt2 = runAt.toISOString();
|
|
18546
|
+
if (job.nextRunAt !== nextRunAt2) {
|
|
18547
|
+
job.nextRunAt = nextRunAt2;
|
|
18548
|
+
return true;
|
|
18549
|
+
}
|
|
18550
|
+
return false;
|
|
18551
|
+
}
|
|
18552
|
+
const nextRunAt = computeNextRun(job.schedule, now).toISOString();
|
|
18553
|
+
if (job.nextRunAt !== nextRunAt) {
|
|
18554
|
+
job.nextRunAt = nextRunAt;
|
|
18555
|
+
return true;
|
|
18556
|
+
}
|
|
18557
|
+
return false;
|
|
18558
|
+
}
|
|
18559
|
+
function normalizeJobs(now = /* @__PURE__ */ new Date()) {
|
|
18560
|
+
let changed = false;
|
|
18561
|
+
for (const job of jobs) {
|
|
18562
|
+
changed = normalizeJob(job, now) || changed;
|
|
18563
|
+
}
|
|
18564
|
+
return changed;
|
|
18565
|
+
}
|
|
18566
|
+
function isIntegerInRange(value, min, max) {
|
|
18567
|
+
return Number.isInteger(value) && Number(value) >= min && Number(value) <= max;
|
|
18568
|
+
}
|
|
18569
|
+
function isStringRecord(value) {
|
|
18570
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
18571
|
+
return Object.values(value).every((entry) => typeof entry === "string");
|
|
18572
|
+
}
|
|
18573
|
+
function computeNextRun(schedule, from = /* @__PURE__ */ new Date()) {
|
|
18574
|
+
switch (schedule.type) {
|
|
18575
|
+
case "once":
|
|
18576
|
+
return new Date(schedule.runAt);
|
|
18577
|
+
case "hourly": {
|
|
18578
|
+
const next = new Date(from);
|
|
18579
|
+
next.setMinutes(0, 0, 0);
|
|
18580
|
+
next.setHours(next.getHours() + 1);
|
|
18581
|
+
return next;
|
|
18582
|
+
}
|
|
18583
|
+
case "daily": {
|
|
18584
|
+
const next = new Date(from);
|
|
18585
|
+
next.setHours(schedule.hour, schedule.minute, 0, 0);
|
|
18586
|
+
if (next <= from) next.setDate(next.getDate() + 1);
|
|
18587
|
+
return next;
|
|
18588
|
+
}
|
|
18589
|
+
case "weekly": {
|
|
18590
|
+
const next = new Date(from);
|
|
18591
|
+
next.setHours(schedule.hour, schedule.minute, 0, 0);
|
|
18592
|
+
const daysUntil = (schedule.dayOfWeek - next.getDay() + 7) % 7;
|
|
18593
|
+
if (daysUntil === 0 && next <= from) {
|
|
18594
|
+
next.setDate(next.getDate() + 7);
|
|
18595
|
+
} else {
|
|
18596
|
+
next.setDate(next.getDate() + (daysUntil || 7));
|
|
18597
|
+
}
|
|
18598
|
+
return next;
|
|
18599
|
+
}
|
|
18600
|
+
}
|
|
18601
|
+
}
|
|
18602
|
+
function isValidScheduleConfig(s) {
|
|
18603
|
+
if (!s || typeof s !== "object") return false;
|
|
18604
|
+
const sc = s;
|
|
18605
|
+
if (!["once", "hourly", "daily", "weekly"].includes(sc.type)) return false;
|
|
18606
|
+
if (sc.type === "once") {
|
|
18607
|
+
if (typeof sc.runAt !== "string") return false;
|
|
18608
|
+
if (Number.isNaN(new Date(sc.runAt).getTime())) return false;
|
|
18609
|
+
}
|
|
18610
|
+
if ((sc.type === "daily" || sc.type === "weekly") && (!isIntegerInRange(sc.hour, 0, 23) || !isIntegerInRange(sc.minute, 0, 59)))
|
|
18611
|
+
return false;
|
|
18612
|
+
if (sc.type === "weekly" && !isIntegerInRange(sc.dayOfWeek, 0, 6)) return false;
|
|
18613
|
+
return true;
|
|
18614
|
+
}
|
|
18615
|
+
function isValidJobData(v) {
|
|
18616
|
+
if (!v || typeof v !== "object") return false;
|
|
18617
|
+
const j = v;
|
|
18618
|
+
return typeof j.kitId === "string" && j.kitId.length > 0 && typeof j.kitName === "string" && j.kitName.length > 0 && typeof j.kitIcon === "string" && typeof j.renderedPrompt === "string" && j.renderedPrompt.length > 0 && (j.fieldValues === void 0 || isStringRecord(j.fieldValues)) && isValidScheduleConfig(j.schedule) && typeof j.enabled === "boolean";
|
|
18619
|
+
}
|
|
18620
|
+
async function fireJob(job, windowState, runtime2) {
|
|
18621
|
+
const { chromeView, sidebarView, devtoolsPanelView, tabManager } = windowState;
|
|
18622
|
+
const send = (channel, ...args) => {
|
|
18623
|
+
if (!chromeView.webContents.isDestroyed())
|
|
18624
|
+
chromeView.webContents.send(channel, ...args);
|
|
18625
|
+
if (!sidebarView.webContents.isDestroyed())
|
|
18626
|
+
sidebarView.webContents.send(channel, ...args);
|
|
18627
|
+
if (!devtoolsPanelView.webContents.isDestroyed())
|
|
18628
|
+
devtoolsPanelView.webContents.send(channel, ...args);
|
|
18629
|
+
};
|
|
18630
|
+
const settings2 = loadSettings();
|
|
18631
|
+
const activityId = `scheduled:${job.id}:${Date.now()}`;
|
|
18632
|
+
const startActivity = () => {
|
|
18633
|
+
const entry = {
|
|
18634
|
+
id: activityId,
|
|
18635
|
+
source: "scheduled",
|
|
18636
|
+
title: job.kitName,
|
|
18637
|
+
icon: job.kitIcon,
|
|
18638
|
+
status: "running",
|
|
18639
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18640
|
+
output: ""
|
|
18641
|
+
};
|
|
18642
|
+
send(Channels.AUTOMATION_ACTIVITY_START, entry);
|
|
18643
|
+
};
|
|
18644
|
+
const appendActivity = (chunk) => {
|
|
18645
|
+
send(Channels.AUTOMATION_ACTIVITY_CHUNK, { id: activityId, chunk });
|
|
18646
|
+
};
|
|
18647
|
+
const finishActivity = (status) => {
|
|
18648
|
+
send(Channels.AUTOMATION_ACTIVITY_END, {
|
|
18649
|
+
id: activityId,
|
|
18650
|
+
status,
|
|
18651
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
18652
|
+
});
|
|
18653
|
+
};
|
|
18654
|
+
startActivity();
|
|
18655
|
+
if (!settings2.chatProvider) {
|
|
18656
|
+
console.warn(`[scheduler] Job "${job.kitName}" skipped — no chat provider configured`);
|
|
18657
|
+
appendActivity(
|
|
18658
|
+
"Chat provider not configured. Open Settings (Ctrl+,) to choose a provider."
|
|
18659
|
+
);
|
|
18660
|
+
finishActivity("failed");
|
|
18661
|
+
return;
|
|
18662
|
+
}
|
|
18663
|
+
console.log(`[scheduler] Firing scheduled job: ${job.kitName} (${job.id})`);
|
|
18664
|
+
try {
|
|
18665
|
+
const provider = createProvider(settings2.chatProvider);
|
|
18666
|
+
const activeTab = tabManager.getActiveTab();
|
|
18667
|
+
await handleAIQuery(
|
|
18668
|
+
job.renderedPrompt,
|
|
18669
|
+
provider,
|
|
18670
|
+
activeTab?.view.webContents,
|
|
18671
|
+
(chunk) => appendActivity(chunk),
|
|
18672
|
+
() => finishActivity("completed"),
|
|
18673
|
+
tabManager,
|
|
18674
|
+
runtime2
|
|
18675
|
+
);
|
|
18676
|
+
} catch (err) {
|
|
18677
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
18678
|
+
appendActivity(`
|
|
18679
|
+
[Scheduled Kit Error: ${msg}]`);
|
|
18680
|
+
finishActivity("failed");
|
|
18681
|
+
}
|
|
18682
|
+
}
|
|
18683
|
+
function tick(windowState, runtime2) {
|
|
18684
|
+
if (isAIStreamActive()) return;
|
|
18685
|
+
const now = /* @__PURE__ */ new Date();
|
|
18686
|
+
let changed = false;
|
|
18687
|
+
for (const job of jobs) {
|
|
18688
|
+
if (!job.enabled) continue;
|
|
18689
|
+
if (now < new Date(job.nextRunAt)) continue;
|
|
18690
|
+
if (!tryBeginAIStream("scheduled")) break;
|
|
18691
|
+
void fireJob(job, windowState, runtime2).finally(() => {
|
|
18692
|
+
endAIStream("scheduled");
|
|
18693
|
+
queueMicrotask(() => tick(windowState, runtime2));
|
|
18694
|
+
});
|
|
18695
|
+
job.lastRunAt = now.toISOString();
|
|
18696
|
+
if (job.schedule.type === "once") {
|
|
18697
|
+
job.enabled = false;
|
|
18698
|
+
} else {
|
|
18699
|
+
job.nextRunAt = computeNextRun(job.schedule, now).toISOString();
|
|
18700
|
+
}
|
|
18701
|
+
changed = true;
|
|
18702
|
+
break;
|
|
18703
|
+
}
|
|
18704
|
+
if (changed) {
|
|
18705
|
+
saveJobs();
|
|
18706
|
+
broadcastFn?.(Channels.SCHEDULE_JOBS_UPDATE, jobs);
|
|
18707
|
+
}
|
|
18708
|
+
}
|
|
18709
|
+
function registerScheduleHandlers(windowState, runtime2, sendToAll) {
|
|
18710
|
+
broadcastFn = sendToAll;
|
|
18711
|
+
loadJobs();
|
|
18712
|
+
if (normalizeJobs()) {
|
|
18713
|
+
saveJobs();
|
|
18714
|
+
}
|
|
18715
|
+
removeIdleListener?.();
|
|
18716
|
+
removeIdleListener = onAIStreamIdle(() => tick(windowState, runtime2));
|
|
18717
|
+
const now = /* @__PURE__ */ new Date();
|
|
18718
|
+
const msToNextMinute = (60 - now.getSeconds()) * 1e3 - now.getMilliseconds();
|
|
18719
|
+
setTimeout(() => {
|
|
18720
|
+
tick(windowState, runtime2);
|
|
18721
|
+
setInterval(() => tick(windowState, runtime2), 6e4);
|
|
18722
|
+
}, msToNextMinute);
|
|
18723
|
+
electron.ipcMain.handle(Channels.SCHEDULE_GET_ALL, () => jobs);
|
|
18724
|
+
electron.ipcMain.handle(Channels.SCHEDULE_CREATE, (_, rawJob) => {
|
|
18725
|
+
if (!isValidJobData(rawJob)) {
|
|
18726
|
+
throw new Error(
|
|
18727
|
+
"Invalid job data. Required: kitId, kitName, kitIcon, renderedPrompt, schedule, enabled."
|
|
18728
|
+
);
|
|
18729
|
+
}
|
|
18730
|
+
const newJob = {
|
|
18731
|
+
...rawJob,
|
|
18732
|
+
id: crypto.randomUUID(),
|
|
18733
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18734
|
+
nextRunAt: computeNextRun(rawJob.schedule).toISOString()
|
|
18735
|
+
};
|
|
18736
|
+
jobs.push(newJob);
|
|
18737
|
+
saveJobs();
|
|
18738
|
+
sendToAll(Channels.SCHEDULE_JOBS_UPDATE, jobs);
|
|
18739
|
+
return newJob;
|
|
18740
|
+
});
|
|
18741
|
+
electron.ipcMain.handle(Channels.SCHEDULE_UPDATE, (_, id, updates) => {
|
|
18742
|
+
if (typeof id !== "string") throw new Error("id must be a string");
|
|
18743
|
+
const job = jobs.find((j) => j.id === id);
|
|
18744
|
+
if (!job) return null;
|
|
18745
|
+
if (updates && typeof updates === "object") {
|
|
18746
|
+
const u = updates;
|
|
18747
|
+
const wasEnabled = job.enabled;
|
|
18748
|
+
if (u.enabled !== void 0) job.enabled = u.enabled;
|
|
18749
|
+
if (u.schedule !== void 0 && isValidScheduleConfig(u.schedule)) {
|
|
18750
|
+
job.schedule = u.schedule;
|
|
18751
|
+
job.nextRunAt = computeNextRun(u.schedule).toISOString();
|
|
18752
|
+
}
|
|
18753
|
+
if (typeof u.renderedPrompt === "string" && u.renderedPrompt.length > 0) {
|
|
18754
|
+
job.renderedPrompt = u.renderedPrompt;
|
|
18755
|
+
}
|
|
18756
|
+
if (u.fieldValues !== void 0 && isStringRecord(u.fieldValues)) {
|
|
18757
|
+
job.fieldValues = u.fieldValues;
|
|
18758
|
+
}
|
|
18759
|
+
if ((u.schedule !== void 0 || u.enabled === true && !wasEnabled) && job.enabled) {
|
|
18760
|
+
normalizeJob(job);
|
|
18761
|
+
}
|
|
18762
|
+
}
|
|
18763
|
+
saveJobs();
|
|
18764
|
+
sendToAll(Channels.SCHEDULE_JOBS_UPDATE, jobs);
|
|
18765
|
+
return job;
|
|
18766
|
+
});
|
|
18767
|
+
electron.ipcMain.handle(Channels.SCHEDULE_DELETE, (_, id) => {
|
|
18768
|
+
if (typeof id !== "string") throw new Error("id must be a string");
|
|
18769
|
+
const before = jobs.length;
|
|
18770
|
+
jobs = jobs.filter((j) => j.id !== id);
|
|
18771
|
+
if (jobs.length === before) return false;
|
|
18772
|
+
saveJobs();
|
|
18773
|
+
sendToAll(Channels.SCHEDULE_JOBS_UPDATE, jobs);
|
|
18774
|
+
return true;
|
|
18775
|
+
});
|
|
18776
|
+
}
|
|
18232
18777
|
let activeChatProvider = null;
|
|
18233
18778
|
function assertString(value, name) {
|
|
18234
18779
|
if (typeof value !== "string") throw new Error(`${name} must be a string`);
|
|
@@ -18247,9 +18792,30 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
18247
18792
|
sidebarView.webContents.send(channel, ...args);
|
|
18248
18793
|
devtoolsPanelView.webContents.send(channel, ...args);
|
|
18249
18794
|
};
|
|
18795
|
+
const getActiveHighlightCountSafe = async () => {
|
|
18796
|
+
const tab = tabManager.getActiveTab();
|
|
18797
|
+
if (!tab) return 0;
|
|
18798
|
+
const wc = tab.view.webContents;
|
|
18799
|
+
if (wc.isDestroyed()) return 0;
|
|
18800
|
+
try {
|
|
18801
|
+
return await getHighlightCount(wc) ?? 0;
|
|
18802
|
+
} catch {
|
|
18803
|
+
return 0;
|
|
18804
|
+
}
|
|
18805
|
+
};
|
|
18806
|
+
const emitHighlightCount = async () => {
|
|
18807
|
+
const count = await getActiveHighlightCountSafe();
|
|
18808
|
+
sendToRendererViews(Channels.HIGHLIGHT_COUNT_UPDATE, count);
|
|
18809
|
+
};
|
|
18250
18810
|
runtime2.setUpdateListener((state2) => {
|
|
18251
18811
|
sendToRendererViews(Channels.AGENT_RUNTIME_UPDATE, state2);
|
|
18252
18812
|
});
|
|
18813
|
+
onRuntimeHealthChange((health) => {
|
|
18814
|
+
sendToRendererViews(Channels.SETTINGS_HEALTH_UPDATE, health);
|
|
18815
|
+
});
|
|
18816
|
+
onAIStreamIdle(() => {
|
|
18817
|
+
sendToRendererViews(Channels.AI_STREAM_IDLE);
|
|
18818
|
+
});
|
|
18253
18819
|
electron.ipcMain.handle(Channels.TAB_CREATE, (_, url) => {
|
|
18254
18820
|
const id = tabManager.createTab(url || loadSettings().defaultUrl);
|
|
18255
18821
|
layoutViews(windowState);
|
|
@@ -18283,15 +18849,19 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
18283
18849
|
electron.ipcMain.handle(Channels.AI_QUERY, async (_, query, history) => {
|
|
18284
18850
|
const settings2 = loadSettings();
|
|
18285
18851
|
const chatConfig = settings2.chatProvider;
|
|
18286
|
-
sendToRendererViews(Channels.AI_STREAM_START, query);
|
|
18287
18852
|
if (!chatConfig) {
|
|
18853
|
+
sendToRendererViews(Channels.AI_STREAM_START, query);
|
|
18288
18854
|
sendToRendererViews(
|
|
18289
18855
|
Channels.AI_STREAM_CHUNK,
|
|
18290
18856
|
"Chat provider not configured. Open Settings (Ctrl+,) to choose a provider."
|
|
18291
18857
|
);
|
|
18292
18858
|
sendToRendererViews(Channels.AI_STREAM_END);
|
|
18293
|
-
return;
|
|
18859
|
+
return { accepted: true };
|
|
18294
18860
|
}
|
|
18861
|
+
if (!tryBeginAIStream("manual")) {
|
|
18862
|
+
return { accepted: false, reason: "busy" };
|
|
18863
|
+
}
|
|
18864
|
+
sendToRendererViews(Channels.AI_STREAM_START, query);
|
|
18295
18865
|
try {
|
|
18296
18866
|
activeChatProvider = createProvider(chatConfig);
|
|
18297
18867
|
trackProviderConfigured(chatConfig.id);
|
|
@@ -18313,7 +18883,9 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
18313
18883
|
sendToRendererViews(Channels.AI_STREAM_END);
|
|
18314
18884
|
} finally {
|
|
18315
18885
|
activeChatProvider = null;
|
|
18886
|
+
endAIStream("manual");
|
|
18316
18887
|
}
|
|
18888
|
+
return { accepted: true };
|
|
18317
18889
|
});
|
|
18318
18890
|
electron.ipcMain.handle(Channels.AI_CANCEL, () => {
|
|
18319
18891
|
activeChatProvider?.cancel();
|
|
@@ -18484,6 +19056,7 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
18484
19056
|
if (result.success && result.text) {
|
|
18485
19057
|
await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(() => {
|
|
18486
19058
|
});
|
|
19059
|
+
await emitHighlightCount();
|
|
18487
19060
|
}
|
|
18488
19061
|
return result;
|
|
18489
19062
|
} catch {
|
|
@@ -18491,6 +19064,9 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
18491
19064
|
}
|
|
18492
19065
|
});
|
|
18493
19066
|
tabManager.onHighlightCapture((result) => {
|
|
19067
|
+
if (result.success) {
|
|
19068
|
+
void emitHighlightCount();
|
|
19069
|
+
}
|
|
18494
19070
|
if (!chromeView.webContents.isDestroyed()) {
|
|
18495
19071
|
chromeView.webContents.send(Channels.HIGHLIGHT_CAPTURE_RESULT, result);
|
|
18496
19072
|
}
|
|
@@ -18503,6 +19079,7 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
18503
19079
|
if (!tab || !tab.highlightModeActive) return;
|
|
18504
19080
|
void persistAndMarkHighlight(wc, text).then((result) => {
|
|
18505
19081
|
if (result.success && !chromeView.webContents.isDestroyed()) {
|
|
19082
|
+
void emitHighlightCount();
|
|
18506
19083
|
chromeView.webContents.send(Channels.HIGHLIGHT_CAPTURE_RESULT, result);
|
|
18507
19084
|
}
|
|
18508
19085
|
});
|
|
@@ -18510,15 +19087,7 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
18510
19087
|
}
|
|
18511
19088
|
});
|
|
18512
19089
|
electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_COUNT, () => {
|
|
18513
|
-
|
|
18514
|
-
if (!tab) return 0;
|
|
18515
|
-
const wc = tab.view.webContents;
|
|
18516
|
-
if (wc.isDestroyed()) return 0;
|
|
18517
|
-
try {
|
|
18518
|
-
return getHighlightCount(wc);
|
|
18519
|
-
} catch {
|
|
18520
|
-
return 0;
|
|
18521
|
-
}
|
|
19090
|
+
return getActiveHighlightCountSafe();
|
|
18522
19091
|
});
|
|
18523
19092
|
electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_SCROLL, (_, index) => {
|
|
18524
19093
|
const tab = tabManager.getActiveTab();
|
|
@@ -18531,24 +19100,32 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
18531
19100
|
return false;
|
|
18532
19101
|
}
|
|
18533
19102
|
});
|
|
18534
|
-
electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_REMOVE, (_, index) => {
|
|
19103
|
+
electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_REMOVE, async (_, index) => {
|
|
18535
19104
|
const tab = tabManager.getActiveTab();
|
|
18536
19105
|
if (!tab) return false;
|
|
18537
19106
|
const wc = tab.view.webContents;
|
|
18538
19107
|
if (wc.isDestroyed()) return false;
|
|
18539
19108
|
try {
|
|
18540
|
-
|
|
19109
|
+
const removed = await removeHighlightAtIndex(wc, index);
|
|
19110
|
+
if (removed) {
|
|
19111
|
+
await emitHighlightCount();
|
|
19112
|
+
}
|
|
19113
|
+
return removed;
|
|
18541
19114
|
} catch {
|
|
18542
19115
|
return false;
|
|
18543
19116
|
}
|
|
18544
19117
|
});
|
|
18545
|
-
electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_CLEAR, () => {
|
|
19118
|
+
electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_CLEAR, async () => {
|
|
18546
19119
|
const tab = tabManager.getActiveTab();
|
|
18547
19120
|
if (!tab) return false;
|
|
18548
19121
|
const wc = tab.view.webContents;
|
|
18549
19122
|
if (wc.isDestroyed()) return false;
|
|
18550
19123
|
try {
|
|
18551
|
-
|
|
19124
|
+
const cleared = await clearAllHighlightElements(wc);
|
|
19125
|
+
if (cleared) {
|
|
19126
|
+
await emitHighlightCount();
|
|
19127
|
+
}
|
|
19128
|
+
return cleared;
|
|
18552
19129
|
} catch {
|
|
18553
19130
|
return false;
|
|
18554
19131
|
}
|
|
@@ -18702,6 +19279,17 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
18702
19279
|
electron.ipcMain.handle(Channels.WINDOW_CLOSE, () => {
|
|
18703
19280
|
mainWindow.close();
|
|
18704
19281
|
});
|
|
19282
|
+
electron.ipcMain.handle(Channels.AUTOMATION_GET_INSTALLED, () => {
|
|
19283
|
+
return getInstalledKits();
|
|
19284
|
+
});
|
|
19285
|
+
electron.ipcMain.handle(Channels.AUTOMATION_INSTALL_FROM_FILE, async () => {
|
|
19286
|
+
return await installKitFromFile();
|
|
19287
|
+
});
|
|
19288
|
+
electron.ipcMain.handle(Channels.AUTOMATION_UNINSTALL, (_event, id) => {
|
|
19289
|
+
assertString(id, "id");
|
|
19290
|
+
return uninstallKit(id);
|
|
19291
|
+
});
|
|
19292
|
+
registerScheduleHandlers(windowState, runtime2, sendToRendererViews);
|
|
18705
19293
|
}
|
|
18706
19294
|
const MAX_TRANSCRIPT_TEXT_LENGTH = 8e3;
|
|
18707
19295
|
const PERSIST_DEBOUNCE_MS = 500;
|
|
@@ -18782,7 +19370,7 @@ class AgentRuntime {
|
|
|
18782
19370
|
createCheckpoint(name, note) {
|
|
18783
19371
|
const snapshot = this.captureSession(note);
|
|
18784
19372
|
const checkpoint = {
|
|
18785
|
-
id: crypto$
|
|
19373
|
+
id: crypto$2.randomUUID(),
|
|
18786
19374
|
name: name?.trim() || `Checkpoint ${this.state.checkpoints.length + 1}`,
|
|
18787
19375
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18788
19376
|
note: note?.trim() || void 0,
|
|
@@ -18836,7 +19424,7 @@ class AgentRuntime {
|
|
|
18836
19424
|
}
|
|
18837
19425
|
}
|
|
18838
19426
|
const entry = {
|
|
18839
|
-
id: crypto$
|
|
19427
|
+
id: crypto$2.randomUUID(),
|
|
18840
19428
|
source: input.source,
|
|
18841
19429
|
kind,
|
|
18842
19430
|
title: input.title?.trim() || void 0,
|
|
@@ -18860,7 +19448,7 @@ class AgentRuntime {
|
|
|
18860
19448
|
// --- Speedee Flow State ---
|
|
18861
19449
|
startFlow(goal, steps, startUrl) {
|
|
18862
19450
|
const flow = {
|
|
18863
|
-
id: crypto$
|
|
19451
|
+
id: crypto$2.randomUUID(),
|
|
18864
19452
|
goal,
|
|
18865
19453
|
steps: steps.map((label) => ({ label, status: "pending" })),
|
|
18866
19454
|
currentStepIndex: 0,
|
|
@@ -19089,7 +19677,7 @@ ${progress}
|
|
|
19089
19677
|
}
|
|
19090
19678
|
startAction(input) {
|
|
19091
19679
|
const action = {
|
|
19092
|
-
id: crypto$
|
|
19680
|
+
id: crypto$2.randomUUID(),
|
|
19093
19681
|
source: input.source,
|
|
19094
19682
|
name: input.name,
|
|
19095
19683
|
args: clone(input.args),
|
|
@@ -19161,7 +19749,7 @@ ${progress}
|
|
|
19161
19749
|
}
|
|
19162
19750
|
awaitApproval(action, reason) {
|
|
19163
19751
|
const approval = {
|
|
19164
|
-
id: crypto$
|
|
19752
|
+
id: crypto$2.randomUUID(),
|
|
19165
19753
|
actionId: action.id,
|
|
19166
19754
|
source: action.source,
|
|
19167
19755
|
name: action.name,
|
|
@@ -19287,12 +19875,25 @@ function installAdBlocking(tabManager) {
|
|
|
19287
19875
|
callback({ cancel: shouldBlockRequest(details) });
|
|
19288
19876
|
});
|
|
19289
19877
|
}
|
|
19878
|
+
function resolveDownloadPath(downloadDir, filename) {
|
|
19879
|
+
fs$1.mkdirSync(downloadDir, { recursive: true });
|
|
19880
|
+
const parsed = path.parse(filename);
|
|
19881
|
+
let attempt = 0;
|
|
19882
|
+
while (true) {
|
|
19883
|
+
const candidateName = attempt === 0 ? filename : `${parsed.name} (${attempt})${parsed.ext}`;
|
|
19884
|
+
const candidatePath = path.join(downloadDir, candidateName);
|
|
19885
|
+
if (!fs$1.existsSync(candidatePath)) {
|
|
19886
|
+
return candidatePath;
|
|
19887
|
+
}
|
|
19888
|
+
attempt += 1;
|
|
19889
|
+
}
|
|
19890
|
+
}
|
|
19290
19891
|
function installDownloadHandler(chromeView) {
|
|
19291
19892
|
electron.session.defaultSession.on("will-download", (_event, item) => {
|
|
19292
19893
|
const settings2 = loadSettings();
|
|
19293
19894
|
const downloadDir = settings2.downloadPath.trim() || electron.app.getPath("downloads");
|
|
19294
19895
|
const filename = item.getFilename();
|
|
19295
|
-
const savePath =
|
|
19896
|
+
const savePath = resolveDownloadPath(downloadDir, filename);
|
|
19296
19897
|
item.setSavePath(savePath);
|
|
19297
19898
|
const info = {
|
|
19298
19899
|
filename,
|
|
@@ -19363,6 +19964,21 @@ function rendererUrlFor(view) {
|
|
|
19363
19964
|
url.searchParams.set("view", view);
|
|
19364
19965
|
return url.toString();
|
|
19365
19966
|
}
|
|
19967
|
+
function resolveRendererFile() {
|
|
19968
|
+
const candidates = [
|
|
19969
|
+
path$1.join(__dirname, "../renderer/index.html"),
|
|
19970
|
+
path$1.join(__dirname, "../../out/renderer/index.html"),
|
|
19971
|
+
path$1.join(electron.app.getAppPath(), "out/renderer/index.html"),
|
|
19972
|
+
path$1.join(electron.app.getAppPath(), "renderer/index.html")
|
|
19973
|
+
];
|
|
19974
|
+
const match = candidates.find((candidate) => fs$1.existsSync(candidate));
|
|
19975
|
+
if (!match) {
|
|
19976
|
+
throw new Error(
|
|
19977
|
+
`Could not locate renderer/index.html. Tried: ${candidates.join(", ")}`
|
|
19978
|
+
);
|
|
19979
|
+
}
|
|
19980
|
+
return match;
|
|
19981
|
+
}
|
|
19366
19982
|
function loadRenderers(chromeView, sidebarView, devtoolsPanelView) {
|
|
19367
19983
|
const chromeUrl = rendererUrlFor("chrome");
|
|
19368
19984
|
const sidebarUrl = rendererUrlFor("sidebar");
|
|
@@ -19372,7 +19988,7 @@ function loadRenderers(chromeView, sidebarView, devtoolsPanelView) {
|
|
|
19372
19988
|
sidebarView.webContents.loadURL(sidebarUrl);
|
|
19373
19989
|
devtoolsPanelView.webContents.loadURL(devtoolsUrl);
|
|
19374
19990
|
} else {
|
|
19375
|
-
const rendererFile =
|
|
19991
|
+
const rendererFile = resolveRendererFile();
|
|
19376
19992
|
chromeView.webContents.loadFile(rendererFile, {
|
|
19377
19993
|
query: { view: "chrome" }
|
|
19378
19994
|
});
|
|
@@ -19384,6 +20000,181 @@ function loadRenderers(chromeView, sidebarView, devtoolsPanelView) {
|
|
|
19384
20000
|
});
|
|
19385
20001
|
}
|
|
19386
20002
|
}
|
|
20003
|
+
function findIconBase64() {
|
|
20004
|
+
const candidates = [
|
|
20005
|
+
path$1.join(electron.app.getAppPath(), "resources", "vessel-icon-400x400.png"),
|
|
20006
|
+
path$1.join(process.resourcesPath, "vessel-icon-400x400.png"),
|
|
20007
|
+
path$1.join(__dirname, "../../resources/vessel-icon-400x400.png"),
|
|
20008
|
+
path$1.join(electron.app.getAppPath(), "resources", "vessel-icon.png"),
|
|
20009
|
+
path$1.join(process.resourcesPath, "vessel-icon.png"),
|
|
20010
|
+
path$1.join(__dirname, "../../resources/vessel-icon.png")
|
|
20011
|
+
];
|
|
20012
|
+
for (const p of candidates) {
|
|
20013
|
+
try {
|
|
20014
|
+
const data = fs$1.readFileSync(p);
|
|
20015
|
+
return `data:image/png;base64,${data.toString("base64")}`;
|
|
20016
|
+
} catch {
|
|
20017
|
+
}
|
|
20018
|
+
}
|
|
20019
|
+
return "";
|
|
20020
|
+
}
|
|
20021
|
+
function buildSplashHTML(iconSrc) {
|
|
20022
|
+
const imgTag = iconSrc ? `<img class="logo" src="${iconSrc}" alt="" />` : `<div class="logo-fallback">V</div>`;
|
|
20023
|
+
return `<!DOCTYPE html>
|
|
20024
|
+
<html>
|
|
20025
|
+
<head>
|
|
20026
|
+
<meta charset="UTF-8">
|
|
20027
|
+
<style>
|
|
20028
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
20029
|
+
html, body {
|
|
20030
|
+
width: 100%; height: 100%;
|
|
20031
|
+
background: #1a1a1e;
|
|
20032
|
+
display: flex;
|
|
20033
|
+
flex-direction: column;
|
|
20034
|
+
align-items: center;
|
|
20035
|
+
justify-content: center;
|
|
20036
|
+
gap: 20px;
|
|
20037
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
|
20038
|
+
overflow: hidden;
|
|
20039
|
+
-webkit-app-region: drag;
|
|
20040
|
+
user-select: none;
|
|
20041
|
+
}
|
|
20042
|
+
.logo-wrap {
|
|
20043
|
+
position: relative;
|
|
20044
|
+
display: flex;
|
|
20045
|
+
align-items: center;
|
|
20046
|
+
justify-content: center;
|
|
20047
|
+
animation: pop-in 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) both;
|
|
20048
|
+
}
|
|
20049
|
+
.glow {
|
|
20050
|
+
position: absolute;
|
|
20051
|
+
inset: -16px;
|
|
20052
|
+
border-radius: 36px;
|
|
20053
|
+
background: radial-gradient(ellipse at center,
|
|
20054
|
+
rgba(196, 160, 90, 0.22) 0%,
|
|
20055
|
+
transparent 68%
|
|
20056
|
+
);
|
|
20057
|
+
animation: glow-pulse 2.8s ease-in-out infinite;
|
|
20058
|
+
}
|
|
20059
|
+
.logo {
|
|
20060
|
+
width: 84px;
|
|
20061
|
+
height: 84px;
|
|
20062
|
+
border-radius: 20px;
|
|
20063
|
+
display: block;
|
|
20064
|
+
position: relative;
|
|
20065
|
+
}
|
|
20066
|
+
.logo-fallback {
|
|
20067
|
+
width: 84px;
|
|
20068
|
+
height: 84px;
|
|
20069
|
+
border-radius: 20px;
|
|
20070
|
+
background: linear-gradient(135deg, #2a2a30, #1e1e24);
|
|
20071
|
+
border: 1px solid rgba(196, 160, 90, 0.25);
|
|
20072
|
+
display: flex;
|
|
20073
|
+
align-items: center;
|
|
20074
|
+
justify-content: center;
|
|
20075
|
+
font-size: 36px;
|
|
20076
|
+
font-weight: 700;
|
|
20077
|
+
color: #c4a05a;
|
|
20078
|
+
position: relative;
|
|
20079
|
+
}
|
|
20080
|
+
.name {
|
|
20081
|
+
font-size: 13px;
|
|
20082
|
+
font-weight: 600;
|
|
20083
|
+
letter-spacing: 0.22em;
|
|
20084
|
+
color: #7a7a8a;
|
|
20085
|
+
text-transform: uppercase;
|
|
20086
|
+
animation: fade-up 0.5s 0.2s cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
20087
|
+
}
|
|
20088
|
+
.dots {
|
|
20089
|
+
display: flex;
|
|
20090
|
+
gap: 6px;
|
|
20091
|
+
animation: fade-up 0.4s 0.35s cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
20092
|
+
}
|
|
20093
|
+
.dot {
|
|
20094
|
+
width: 5px;
|
|
20095
|
+
height: 5px;
|
|
20096
|
+
border-radius: 50%;
|
|
20097
|
+
background: #3e3e50;
|
|
20098
|
+
animation: dot-bounce 1.5s ease-in-out infinite;
|
|
20099
|
+
}
|
|
20100
|
+
.dot:nth-child(2) { animation-delay: 0.2s; }
|
|
20101
|
+
.dot:nth-child(3) { animation-delay: 0.4s; }
|
|
20102
|
+
|
|
20103
|
+
@keyframes pop-in {
|
|
20104
|
+
from { opacity: 0; transform: scale(0.78); }
|
|
20105
|
+
to { opacity: 1; transform: scale(1); }
|
|
20106
|
+
}
|
|
20107
|
+
@keyframes fade-up {
|
|
20108
|
+
from { opacity: 0; transform: translateY(7px); }
|
|
20109
|
+
to { opacity: 1; transform: translateY(0); }
|
|
20110
|
+
}
|
|
20111
|
+
@keyframes glow-pulse {
|
|
20112
|
+
0%, 100% { opacity: 0.55; transform: scale(1); }
|
|
20113
|
+
50% { opacity: 1; transform: scale(1.1); }
|
|
20114
|
+
}
|
|
20115
|
+
@keyframes dot-bounce {
|
|
20116
|
+
0%, 75%, 100% { transform: translateY(0); opacity: 0.3; }
|
|
20117
|
+
40% { transform: translateY(-6px); opacity: 1; }
|
|
20118
|
+
}
|
|
20119
|
+
</style>
|
|
20120
|
+
</head>
|
|
20121
|
+
<body>
|
|
20122
|
+
<div class="logo-wrap">
|
|
20123
|
+
<div class="glow"></div>
|
|
20124
|
+
${imgTag}
|
|
20125
|
+
</div>
|
|
20126
|
+
<div class="name">Vessel</div>
|
|
20127
|
+
<div class="dots">
|
|
20128
|
+
<div class="dot"></div>
|
|
20129
|
+
<div class="dot"></div>
|
|
20130
|
+
<div class="dot"></div>
|
|
20131
|
+
</div>
|
|
20132
|
+
</body>
|
|
20133
|
+
</html>`;
|
|
20134
|
+
}
|
|
20135
|
+
function createSplashWindow() {
|
|
20136
|
+
const splash = new electron.BrowserWindow({
|
|
20137
|
+
width: 1280,
|
|
20138
|
+
height: 800,
|
|
20139
|
+
center: true,
|
|
20140
|
+
frame: false,
|
|
20141
|
+
show: false,
|
|
20142
|
+
// only show once content has painted — prevents black-window flash
|
|
20143
|
+
resizable: false,
|
|
20144
|
+
movable: true,
|
|
20145
|
+
alwaysOnTop: true,
|
|
20146
|
+
skipTaskbar: true,
|
|
20147
|
+
backgroundColor: "#1a1a1e",
|
|
20148
|
+
webPreferences: {
|
|
20149
|
+
nodeIntegration: false,
|
|
20150
|
+
contextIsolation: true
|
|
20151
|
+
}
|
|
20152
|
+
});
|
|
20153
|
+
splash.once("ready-to-show", () => splash.show());
|
|
20154
|
+
const iconSrc = findIconBase64();
|
|
20155
|
+
const html = buildSplashHTML(iconSrc);
|
|
20156
|
+
try {
|
|
20157
|
+
const tmpDir = fs$1.mkdtempSync(path$1.join(os.tmpdir(), "vessel-splash-"));
|
|
20158
|
+
const tmpPath = path$1.join(tmpDir, "index.html");
|
|
20159
|
+
splash.once("closed", () => {
|
|
20160
|
+
try {
|
|
20161
|
+
fs$1.rmSync(tmpDir, { recursive: true, force: true });
|
|
20162
|
+
} catch {
|
|
20163
|
+
}
|
|
20164
|
+
});
|
|
20165
|
+
fs$1.writeFileSync(tmpPath, html, "utf-8");
|
|
20166
|
+
void splash.loadFile(tmpPath);
|
|
20167
|
+
} catch (err) {
|
|
20168
|
+
console.warn("[splash] Failed to write temp HTML, using fallback:", err);
|
|
20169
|
+
void splash.loadFile(path$1.join(__dirname, "../../resources/vessel-icon.png"));
|
|
20170
|
+
}
|
|
20171
|
+
return splash;
|
|
20172
|
+
}
|
|
20173
|
+
function closeSplash(splash, delayMs = 0) {
|
|
20174
|
+
setTimeout(() => {
|
|
20175
|
+
if (!splash.isDestroyed()) splash.close();
|
|
20176
|
+
}, delayMs);
|
|
20177
|
+
}
|
|
19387
20178
|
let runtime = null;
|
|
19388
20179
|
function checkWritableUserData(userDataPath) {
|
|
19389
20180
|
const issues = [];
|
|
@@ -19448,6 +20239,7 @@ Action: Open Settings (Ctrl+,) to choose a different port, then save to restart
|
|
|
19448
20239
|
});
|
|
19449
20240
|
}
|
|
19450
20241
|
async function bootstrap() {
|
|
20242
|
+
const splash = createSplashWindow();
|
|
19451
20243
|
const settings2 = loadSettings();
|
|
19452
20244
|
const userDataPath = electron.app.getPath("userData");
|
|
19453
20245
|
initializeRuntimeHealth({
|
|
@@ -19459,15 +20251,51 @@ async function bootstrap() {
|
|
|
19459
20251
|
if (settings2.clearBookmarksOnLaunch) {
|
|
19460
20252
|
clearAll();
|
|
19461
20253
|
}
|
|
20254
|
+
const syncActiveHighlightCount = async (state2) => {
|
|
20255
|
+
const activeTab = state2.tabManager.getActiveTab();
|
|
20256
|
+
const wc = activeTab?.view.webContents;
|
|
20257
|
+
let count = 0;
|
|
20258
|
+
if (wc && !wc.isDestroyed()) {
|
|
20259
|
+
try {
|
|
20260
|
+
count = await getHighlightCount(wc) ?? 0;
|
|
20261
|
+
} catch {
|
|
20262
|
+
count = 0;
|
|
20263
|
+
}
|
|
20264
|
+
}
|
|
20265
|
+
if (!state2.chromeView.webContents.isDestroyed()) {
|
|
20266
|
+
state2.chromeView.webContents.send(Channels.HIGHLIGHT_COUNT_UPDATE, count);
|
|
20267
|
+
}
|
|
20268
|
+
if (!state2.sidebarView.webContents.isDestroyed()) {
|
|
20269
|
+
state2.sidebarView.webContents.send(Channels.HIGHLIGHT_COUNT_UPDATE, count);
|
|
20270
|
+
}
|
|
20271
|
+
if (!state2.devtoolsPanelView.webContents.isDestroyed()) {
|
|
20272
|
+
state2.devtoolsPanelView.webContents.send(
|
|
20273
|
+
Channels.HIGHLIGHT_COUNT_UPDATE,
|
|
20274
|
+
count
|
|
20275
|
+
);
|
|
20276
|
+
}
|
|
20277
|
+
};
|
|
19462
20278
|
const windowState = createMainWindow((tabs, activeId) => {
|
|
19463
20279
|
windowState.chromeView.webContents.send(
|
|
19464
20280
|
Channels.TAB_STATE_UPDATE,
|
|
19465
20281
|
tabs,
|
|
19466
20282
|
activeId
|
|
19467
20283
|
);
|
|
20284
|
+
void syncActiveHighlightCount(windowState);
|
|
19468
20285
|
layoutViews(windowState);
|
|
19469
20286
|
runtime?.onTabStateChanged();
|
|
19470
20287
|
});
|
|
20288
|
+
let didRevealMainWindow = false;
|
|
20289
|
+
const revealMainWindow = () => {
|
|
20290
|
+
if (didRevealMainWindow) return;
|
|
20291
|
+
didRevealMainWindow = true;
|
|
20292
|
+
windowState.mainWindow.show();
|
|
20293
|
+
closeSplash(splash, 0);
|
|
20294
|
+
};
|
|
20295
|
+
const splashTimeout = setTimeout(() => {
|
|
20296
|
+
console.warn("[bootstrap] Renderer did not finish loading before splash timeout");
|
|
20297
|
+
revealMainWindow();
|
|
20298
|
+
}, 8e3);
|
|
19471
20299
|
const { chromeView, sidebarView, devtoolsPanelView, tabManager } = windowState;
|
|
19472
20300
|
runtime = new AgentRuntime(tabManager);
|
|
19473
20301
|
installAdBlocking(tabManager);
|
|
@@ -19491,7 +20319,6 @@ async function bootstrap() {
|
|
|
19491
20319
|
startBackgroundRevalidation();
|
|
19492
20320
|
startTelemetry();
|
|
19493
20321
|
loadRenderers(chromeView, sidebarView, devtoolsPanelView);
|
|
19494
|
-
await startMcpServer(tabManager, runtime, settings2.mcpPort);
|
|
19495
20322
|
chromeView.webContents.once("did-finish-load", () => {
|
|
19496
20323
|
const savedSession = runtime.getState().session;
|
|
19497
20324
|
if (settings2.autoRestoreSession && savedSession?.tabs.length) {
|
|
@@ -19502,8 +20329,27 @@ async function bootstrap() {
|
|
|
19502
20329
|
}
|
|
19503
20330
|
layoutViews(windowState);
|
|
19504
20331
|
setImmediate(() => layoutViews(windowState));
|
|
20332
|
+
clearTimeout(splashTimeout);
|
|
20333
|
+
revealMainWindow();
|
|
19505
20334
|
void maybeShowStartupHealthDialog(windowState);
|
|
19506
20335
|
});
|
|
20336
|
+
chromeView.webContents.once(
|
|
20337
|
+
"did-fail-load",
|
|
20338
|
+
(_event, errorCode, errorDescription, validatedURL, isMainFrame) => {
|
|
20339
|
+
if (!isMainFrame) return;
|
|
20340
|
+
console.error(
|
|
20341
|
+
"[bootstrap] Chrome renderer failed to load:",
|
|
20342
|
+
errorCode,
|
|
20343
|
+
errorDescription,
|
|
20344
|
+
validatedURL
|
|
20345
|
+
);
|
|
20346
|
+
clearTimeout(splashTimeout);
|
|
20347
|
+
revealMainWindow();
|
|
20348
|
+
}
|
|
20349
|
+
);
|
|
20350
|
+
startMcpServer(tabManager, runtime, settings2.mcpPort).catch((err) => {
|
|
20351
|
+
console.error("[bootstrap] MCP server failed to start:", err);
|
|
20352
|
+
});
|
|
19507
20353
|
}
|
|
19508
20354
|
process.on("uncaughtException", (error) => {
|
|
19509
20355
|
console.error("[Vessel] Uncaught exception:", error.message, error.stack);
|
|
@@ -19523,8 +20369,15 @@ electron.app.on("window-all-closed", () => {
|
|
|
19523
20369
|
electron.globalShortcut.unregisterAll();
|
|
19524
20370
|
stopTelemetry();
|
|
19525
20371
|
stopBackgroundRevalidation();
|
|
19526
|
-
|
|
19527
|
-
|
|
19528
|
-
|
|
20372
|
+
void Promise.all([
|
|
20373
|
+
runtime?.flushPersist() ?? Promise.resolve(),
|
|
20374
|
+
flushPersist(),
|
|
20375
|
+
flushPersist$1(),
|
|
20376
|
+
flushPersist$2(),
|
|
20377
|
+
flushPersist$3()
|
|
20378
|
+
]).finally(() => {
|
|
20379
|
+
void stopMcpServer().finally(() => {
|
|
20380
|
+
electron.app.quit();
|
|
20381
|
+
});
|
|
19529
20382
|
});
|
|
19530
20383
|
});
|