@quanta-intellect/vessel-browser 0.1.94 → 0.1.97
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 +6 -8
- package/out/main/index.js +1570 -363
- package/out/preload/index.js +34 -0
- package/out/renderer/assets/{index-BeUwMCx4.js → index-BLDCpaUR.js} +1196 -522
- package/out/renderer/assets/{index-BFKx2klB.css → index-CzIBoLK8.css} +288 -17
- package/out/renderer/index.html +2 -2
- package/package.json +5 -1
package/out/main/index.js
CHANGED
|
@@ -15,6 +15,7 @@ const http$1 = require("node:http");
|
|
|
15
15
|
const os = require("node:os");
|
|
16
16
|
const mcp_js = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
17
17
|
const streamableHttp_js = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
18
|
+
const promises = require("fs/promises");
|
|
18
19
|
function getEnvFlag(name) {
|
|
19
20
|
const globalProcess = typeof globalThis === "object" && "process" in globalThis ? globalThis.process : void 0;
|
|
20
21
|
return globalProcess?.env?.[name];
|
|
@@ -79,7 +80,7 @@ const defaults = {
|
|
|
79
80
|
const SAVE_DEBOUNCE_MS$6 = 150;
|
|
80
81
|
const CHAT_PROVIDER_SECRET_FILENAME = "vessel-chat-provider-secret";
|
|
81
82
|
const CODEX_TOKENS_FILENAME = "vessel-codex-tokens";
|
|
82
|
-
const logger$
|
|
83
|
+
const logger$p = createLogger("Settings");
|
|
83
84
|
const SETTABLE_KEYS = new Set(Object.keys(defaults));
|
|
84
85
|
let settings = null;
|
|
85
86
|
let settingsIssues = [];
|
|
@@ -276,9 +277,10 @@ function persistNow() {
|
|
|
276
277
|
return fs.promises.mkdir(path.dirname(getSettingsPath()), { recursive: true }).then(
|
|
277
278
|
() => fs.promises.writeFile(
|
|
278
279
|
getSettingsPath(),
|
|
279
|
-
JSON.stringify(buildPersistedSettings(settings), null, 2)
|
|
280
|
+
JSON.stringify(buildPersistedSettings(settings), null, 2),
|
|
281
|
+
{ encoding: "utf-8", mode: 384 }
|
|
280
282
|
)
|
|
281
|
-
).catch((err) => logger$
|
|
283
|
+
).then(() => fs.promises.chmod(getSettingsPath(), 384).catch(() => void 0)).catch((err) => logger$p.error("Failed to save settings:", err));
|
|
282
284
|
}
|
|
283
285
|
function saveSettings() {
|
|
284
286
|
saveDirty = true;
|
|
@@ -407,7 +409,7 @@ function loadTrustedAppURL(wc, url) {
|
|
|
407
409
|
}
|
|
408
410
|
const MAX_CUSTOM_HISTORY = 50;
|
|
409
411
|
const READER_MODE_DATA_URL_PREFIX = "data:text/html;charset=utf-8,";
|
|
410
|
-
const logger$
|
|
412
|
+
const logger$o = createLogger("Tab");
|
|
411
413
|
const sessionCertExceptions = /* @__PURE__ */ new WeakMap();
|
|
412
414
|
const sessionsWithVerifyProc = /* @__PURE__ */ new WeakSet();
|
|
413
415
|
const CERT_VERIFY_TRUST = 0;
|
|
@@ -473,7 +475,7 @@ class Tab {
|
|
|
473
475
|
guardedLoadURL(url, options) {
|
|
474
476
|
const blockReason = this.getNavigationBlockReason(url);
|
|
475
477
|
if (blockReason) {
|
|
476
|
-
logger$
|
|
478
|
+
logger$o.warn(blockReason);
|
|
477
479
|
return blockReason;
|
|
478
480
|
}
|
|
479
481
|
void this.view.webContents.loadURL(url, options);
|
|
@@ -557,7 +559,7 @@ class Tab {
|
|
|
557
559
|
wc.setWindowOpenHandler(({ url, disposition }) => {
|
|
558
560
|
const error = this.getNavigationBlockReason(url);
|
|
559
561
|
if (error) {
|
|
560
|
-
logger$
|
|
562
|
+
logger$o.warn(error);
|
|
561
563
|
return { action: "deny" };
|
|
562
564
|
}
|
|
563
565
|
this.onOpenUrl?.({
|
|
@@ -571,7 +573,7 @@ class Tab {
|
|
|
571
573
|
const error = this.getNavigationBlockReason(url);
|
|
572
574
|
if (!error) return;
|
|
573
575
|
event.preventDefault();
|
|
574
|
-
logger$
|
|
576
|
+
logger$o.warn(`${context}: ${error}`);
|
|
575
577
|
};
|
|
576
578
|
wc.on("will-navigate", (event, url) => {
|
|
577
579
|
blockNavigation(event, url, "Blocked top-level navigation");
|
|
@@ -655,7 +657,7 @@ class Tab {
|
|
|
655
657
|
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.12); border-radius: 999px; }
|
|
656
658
|
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.22); }
|
|
657
659
|
::-webkit-scrollbar-corner { background: transparent; }
|
|
658
|
-
`).catch((err) => logger$
|
|
660
|
+
`).catch((err) => logger$o.warn("Failed to inject scrollbar CSS:", err));
|
|
659
661
|
});
|
|
660
662
|
wc.on("page-favicon-updated", (_, favicons) => {
|
|
661
663
|
this._state.favicon = favicons[0] || "";
|
|
@@ -691,7 +693,7 @@ class Tab {
|
|
|
691
693
|
).then((highlightedText) => {
|
|
692
694
|
this.buildContextMenu(wc, params, highlightedText.trim());
|
|
693
695
|
}).catch((err) => {
|
|
694
|
-
logger$
|
|
696
|
+
logger$o.warn("Failed to inspect highlighted text for context menu:", err);
|
|
695
697
|
this.buildContextMenu(wc, params, "");
|
|
696
698
|
});
|
|
697
699
|
});
|
|
@@ -892,7 +894,7 @@ class Tab {
|
|
|
892
894
|
"document.documentElement.outerHTML"
|
|
893
895
|
);
|
|
894
896
|
} catch (err) {
|
|
895
|
-
logger$
|
|
897
|
+
logger$o.warn("Failed to retrieve page source:", err);
|
|
896
898
|
return;
|
|
897
899
|
}
|
|
898
900
|
const escaped = html.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -1020,7 +1022,7 @@ class Tab {
|
|
|
1020
1022
|
document.addEventListener('mouseup', window.__vesselHighlightHandler);
|
|
1021
1023
|
}
|
|
1022
1024
|
})()
|
|
1023
|
-
`).catch((err) => logger$
|
|
1025
|
+
`).catch((err) => logger$o.warn("Failed to inject highlight listener:", err));
|
|
1024
1026
|
} else {
|
|
1025
1027
|
void wc.executeJavaScript(`
|
|
1026
1028
|
(function() {
|
|
@@ -1031,7 +1033,7 @@ class Tab {
|
|
|
1031
1033
|
delete window.__vesselHighlightHandler;
|
|
1032
1034
|
}
|
|
1033
1035
|
})()
|
|
1034
|
-
`).catch((err) => logger$
|
|
1036
|
+
`).catch((err) => logger$o.warn("Failed to remove highlight listener:", err));
|
|
1035
1037
|
}
|
|
1036
1038
|
}
|
|
1037
1039
|
get webContentsId() {
|
|
@@ -1068,7 +1070,7 @@ const SEARCH_ENGINE_PRESETS = {
|
|
|
1068
1070
|
ecosia: { label: "Ecosia", url: "https://www.ecosia.org/search?q=" },
|
|
1069
1071
|
kagi: { label: "Kagi", url: "https://kagi.com/search?q=" }
|
|
1070
1072
|
};
|
|
1071
|
-
const logger$
|
|
1073
|
+
const logger$n = createLogger("JsonPersistence");
|
|
1072
1074
|
function canUseSafeStorage() {
|
|
1073
1075
|
try {
|
|
1074
1076
|
return electron.safeStorage.isEncryptionAvailable();
|
|
@@ -1133,7 +1135,7 @@ function createDebouncedJsonPersistence({
|
|
|
1133
1135
|
data,
|
|
1134
1136
|
typeof data === "string" ? { encoding: "utf-8", mode: 384 } : { mode: 384 }
|
|
1135
1137
|
)
|
|
1136
|
-
).catch((err) => logger$
|
|
1138
|
+
).then(() => fs.promises.chmod(filePath2, 384).catch(() => void 0)).catch((err) => logger$n.error(`Failed to save ${logLabel}:`, err));
|
|
1137
1139
|
};
|
|
1138
1140
|
const schedule = () => {
|
|
1139
1141
|
saveDirty2 = true;
|
|
@@ -2814,7 +2816,7 @@ function destroySession(tabId) {
|
|
|
2814
2816
|
sessions.delete(tabId);
|
|
2815
2817
|
}
|
|
2816
2818
|
}
|
|
2817
|
-
const logger$
|
|
2819
|
+
const logger$m = createLogger("TabManager");
|
|
2818
2820
|
function sanitizePdfFilename(title) {
|
|
2819
2821
|
const clean = title.replace(/[<>:"/\\|?*\x00-\x1f]/g, " ").replace(/\s+/g, " ").trim();
|
|
2820
2822
|
const base = (clean || "Vessel Page").replace(/\.pdf$/i, "");
|
|
@@ -3213,7 +3215,7 @@ class TabManager {
|
|
|
3213
3215
|
}));
|
|
3214
3216
|
if (entries.length > 0) {
|
|
3215
3217
|
void highlightBatchOnPage(wc, entries).catch(
|
|
3216
|
-
(err) => logger$
|
|
3218
|
+
(err) => logger$m.warn("Failed to batch highlight:", err)
|
|
3217
3219
|
);
|
|
3218
3220
|
}
|
|
3219
3221
|
}
|
|
@@ -3235,12 +3237,12 @@ class TabManager {
|
|
|
3235
3237
|
const result = await captureSelectionHighlight(wc);
|
|
3236
3238
|
if (result.success && result.text) {
|
|
3237
3239
|
await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(
|
|
3238
|
-
(err) => logger$
|
|
3240
|
+
(err) => logger$m.warn("Failed to capture highlight:", err)
|
|
3239
3241
|
);
|
|
3240
3242
|
}
|
|
3241
3243
|
this.highlightCaptureCallback?.(result);
|
|
3242
3244
|
} catch (err) {
|
|
3243
|
-
logger$
|
|
3245
|
+
logger$m.warn("Failed to capture highlight from page:", err);
|
|
3244
3246
|
this.highlightCaptureCallback?.({
|
|
3245
3247
|
success: false,
|
|
3246
3248
|
message: "Could not capture selection"
|
|
@@ -3265,7 +3267,7 @@ class TabManager {
|
|
|
3265
3267
|
void this.removeHighlightMarksForText(wc, text);
|
|
3266
3268
|
}
|
|
3267
3269
|
} catch (err) {
|
|
3268
|
-
logger$
|
|
3270
|
+
logger$m.warn("Failed to remove highlight from matching tab:", err);
|
|
3269
3271
|
}
|
|
3270
3272
|
}
|
|
3271
3273
|
this.highlightCaptureCallback?.({
|
|
@@ -3296,12 +3298,12 @@ class TabManager {
|
|
|
3296
3298
|
void 0,
|
|
3297
3299
|
color
|
|
3298
3300
|
).catch(
|
|
3299
|
-
(err) => logger$
|
|
3301
|
+
(err) => logger$m.warn("Failed to update highlight color:", err)
|
|
3300
3302
|
);
|
|
3301
3303
|
});
|
|
3302
3304
|
}
|
|
3303
3305
|
} catch (err) {
|
|
3304
|
-
logger$
|
|
3306
|
+
logger$m.warn("Failed to iterate highlights for color change:", err);
|
|
3305
3307
|
}
|
|
3306
3308
|
}
|
|
3307
3309
|
this.highlightCaptureCallback?.({
|
|
@@ -3342,7 +3344,7 @@ class TabManager {
|
|
|
3342
3344
|
});
|
|
3343
3345
|
})()`
|
|
3344
3346
|
).catch(
|
|
3345
|
-
(err) => logger$
|
|
3347
|
+
(err) => logger$m.warn("Failed to remove highlight marks:", err)
|
|
3346
3348
|
);
|
|
3347
3349
|
}
|
|
3348
3350
|
broadcastState() {
|
|
@@ -3372,6 +3374,7 @@ const Channels = {
|
|
|
3372
3374
|
AI_STREAM_CHUNK: "ai:stream-chunk",
|
|
3373
3375
|
AI_STREAM_END: "ai:stream-end",
|
|
3374
3376
|
AI_STREAM_IDLE: "ai:stream-idle",
|
|
3377
|
+
AI_RESEARCH_CLARIFICATION: "ai:research-clarification",
|
|
3375
3378
|
AUTOMATION_ACTIVITY_START: "automation:activity-start",
|
|
3376
3379
|
AUTOMATION_ACTIVITY_CHUNK: "automation:activity-chunk",
|
|
3377
3380
|
AUTOMATION_ACTIVITY_END: "automation:activity-end",
|
|
@@ -3552,6 +3555,16 @@ const Channels = {
|
|
|
3552
3555
|
CLEAR_BROWSING_DATA_OPEN: "browsing-data:open",
|
|
3553
3556
|
// Picture-in-Picture
|
|
3554
3557
|
TAB_TOGGLE_PIP: "tab:toggle-pip",
|
|
3558
|
+
// Research Desk
|
|
3559
|
+
RESEARCH_STATE_GET: "research:state-get",
|
|
3560
|
+
RESEARCH_STATE_UPDATE: "research:state-update",
|
|
3561
|
+
RESEARCH_START_BRIEF: "research:start-brief",
|
|
3562
|
+
RESEARCH_CONFIRM_BRIEF: "research:confirm-brief",
|
|
3563
|
+
RESEARCH_APPROVE_OBJECTIVES: "research:approve-objectives",
|
|
3564
|
+
RESEARCH_SET_MODE: "research:set-mode",
|
|
3565
|
+
RESEARCH_SET_TRACES: "research:set-traces",
|
|
3566
|
+
RESEARCH_CANCEL: "research:cancel",
|
|
3567
|
+
RESEARCH_EXPORT_REPORT: "research:export-report",
|
|
3555
3568
|
// Codex OAuth
|
|
3556
3569
|
CODEX_START_AUTH: "codex:start-auth",
|
|
3557
3570
|
CODEX_CANCEL_AUTH: "codex:cancel-auth",
|
|
@@ -4521,7 +4534,7 @@ function errorResult(error, value) {
|
|
|
4521
4534
|
function getErrorMessage(error, fallback = "Unknown error") {
|
|
4522
4535
|
return error instanceof Error && error.message ? error.message : fallback;
|
|
4523
4536
|
}
|
|
4524
|
-
const logger$
|
|
4537
|
+
const logger$l = createLogger("Premium");
|
|
4525
4538
|
const VERIFICATION_API = process.env.VESSEL_PREMIUM_API || "https://vesselpremium.quantaintellect.com";
|
|
4526
4539
|
const FREE_TOOL_ITERATION_LIMIT = 50;
|
|
4527
4540
|
const REVALIDATION_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -4561,7 +4574,10 @@ const PREMIUM_TOOLS = /* @__PURE__ */ new Set([
|
|
|
4561
4574
|
"vault_totp",
|
|
4562
4575
|
"human_vault_list",
|
|
4563
4576
|
"human_vault_fill",
|
|
4564
|
-
"human_vault_remove"
|
|
4577
|
+
"human_vault_remove",
|
|
4578
|
+
"research_confirm_brief",
|
|
4579
|
+
"research_approve_objectives",
|
|
4580
|
+
"research_export_report"
|
|
4565
4581
|
]);
|
|
4566
4582
|
function isPremium() {
|
|
4567
4583
|
const { premium } = loadSettings();
|
|
@@ -4637,7 +4653,7 @@ async function verifySubscription$1(identifier) {
|
|
|
4637
4653
|
});
|
|
4638
4654
|
if (!res.ok) {
|
|
4639
4655
|
const detail = await readApiErrorDetail(res);
|
|
4640
|
-
logger$
|
|
4656
|
+
logger$l.warn(
|
|
4641
4657
|
"Verification API returned a non-OK status:",
|
|
4642
4658
|
res.status,
|
|
4643
4659
|
detail
|
|
@@ -4656,7 +4672,7 @@ async function verifySubscription$1(identifier) {
|
|
|
4656
4672
|
setSetting("premium", updated);
|
|
4657
4673
|
return updated;
|
|
4658
4674
|
} catch (err) {
|
|
4659
|
-
logger$
|
|
4675
|
+
logger$l.warn("Verification failed:", err);
|
|
4660
4676
|
return current;
|
|
4661
4677
|
}
|
|
4662
4678
|
}
|
|
@@ -4760,6 +4776,21 @@ const POSTHOG_HOST = process.env.POSTHOG_HOST || "https://us.i.posthog.com";
|
|
|
4760
4776
|
const BATCH_INTERVAL_MS = 6e4;
|
|
4761
4777
|
const MAX_BATCH_SIZE = 50;
|
|
4762
4778
|
const SENSITIVE_PROPERTY_RE = /url|uri|query|prompt|content|text|token|secret|key|password|credential|email|domain/i;
|
|
4779
|
+
const SENSITIVE_STRING_VALUE_RE = /https?:\/\/|www\.|[^\s@]+@[^\s@]+\.[^\s@]+|bearer\s+\S+|eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.|(?:sk|pk|rk|gh[pousr]|xox[baprs])-[-_A-Za-z0-9]{12,}/i;
|
|
4780
|
+
const EMPTY_PROPERTY_ALLOWLIST = /* @__PURE__ */ new Set();
|
|
4781
|
+
const TELEMETRY_PROPERTY_ALLOWLIST = {
|
|
4782
|
+
app_launched: /* @__PURE__ */ new Set(["electron_version", "chrome_version"]),
|
|
4783
|
+
app_session_ended: /* @__PURE__ */ new Set(["duration_minutes"]),
|
|
4784
|
+
tool_called: /* @__PURE__ */ new Set(["tool_name", "page_type"]),
|
|
4785
|
+
provider_configured: /* @__PURE__ */ new Set(["provider_id"]),
|
|
4786
|
+
page_type_detected: /* @__PURE__ */ new Set(["page_type"]),
|
|
4787
|
+
setting_changed: /* @__PURE__ */ new Set(["setting_key"]),
|
|
4788
|
+
approval_mode_changed: /* @__PURE__ */ new Set(["mode"]),
|
|
4789
|
+
bookmark_action: /* @__PURE__ */ new Set(["action"]),
|
|
4790
|
+
vault_action: /* @__PURE__ */ new Set(["action"]),
|
|
4791
|
+
extraction_failed: /* @__PURE__ */ new Set(["reason"]),
|
|
4792
|
+
premium_funnel: /* @__PURE__ */ new Set(["step", "status", "reason"])
|
|
4793
|
+
};
|
|
4763
4794
|
function getDeviceIdPath() {
|
|
4764
4795
|
return path.join(electron.app.getPath("userData"), ".vessel-device-id");
|
|
4765
4796
|
}
|
|
@@ -4775,7 +4806,8 @@ function getDeviceId() {
|
|
|
4775
4806
|
deviceId = crypto$1.randomUUID();
|
|
4776
4807
|
try {
|
|
4777
4808
|
fs.mkdirSync(path.dirname(idPath), { recursive: true });
|
|
4778
|
-
fs.writeFileSync(idPath, deviceId, "utf-8");
|
|
4809
|
+
fs.writeFileSync(idPath, deviceId, { encoding: "utf-8", mode: 384 });
|
|
4810
|
+
fs.chmodSync(idPath, 384);
|
|
4779
4811
|
} catch {
|
|
4780
4812
|
}
|
|
4781
4813
|
return deviceId;
|
|
@@ -4788,11 +4820,15 @@ function isEnabled() {
|
|
|
4788
4820
|
if (process.env.VESSEL_DEV === "1") return false;
|
|
4789
4821
|
return loadSettings().telemetryEnabled !== false;
|
|
4790
4822
|
}
|
|
4791
|
-
function sanitizeTelemetryProperties(properties) {
|
|
4823
|
+
function sanitizeTelemetryProperties(properties, allowedKeys) {
|
|
4792
4824
|
const safe = {};
|
|
4793
4825
|
for (const [key2, value] of Object.entries(properties)) {
|
|
4794
|
-
if (
|
|
4826
|
+
if (allowedKeys && !allowedKeys.has(key2)) continue;
|
|
4827
|
+
if (!allowedKeys?.has(key2) && SENSITIVE_PROPERTY_RE.test(key2)) continue;
|
|
4795
4828
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null) {
|
|
4829
|
+
if (typeof value === "string" && SENSITIVE_STRING_VALUE_RE.test(value)) {
|
|
4830
|
+
continue;
|
|
4831
|
+
}
|
|
4796
4832
|
safe[key2] = typeof value === "string" ? value.slice(0, 120) : value;
|
|
4797
4833
|
}
|
|
4798
4834
|
}
|
|
@@ -4800,10 +4836,11 @@ function sanitizeTelemetryProperties(properties) {
|
|
|
4800
4836
|
}
|
|
4801
4837
|
function trackEvent(event, properties = {}) {
|
|
4802
4838
|
if (!isEnabled()) return;
|
|
4839
|
+
const allowedKeys = TELEMETRY_PROPERTY_ALLOWLIST[event] ?? EMPTY_PROPERTY_ALLOWLIST;
|
|
4803
4840
|
eventQueue.push({
|
|
4804
4841
|
event,
|
|
4805
4842
|
properties: {
|
|
4806
|
-
...sanitizeTelemetryProperties(properties),
|
|
4843
|
+
...sanitizeTelemetryProperties(properties, allowedKeys),
|
|
4807
4844
|
premium_status: isPremium() ? "premium" : "free",
|
|
4808
4845
|
app_version: electron.app.getVersion(),
|
|
4809
4846
|
platform: process.platform,
|
|
@@ -5240,7 +5277,7 @@ const EXTRACT_TIMEOUT_MAX_MS = 2e4;
|
|
|
5240
5277
|
const MUTATION_CAPTURE_INTERVAL_MS = 5e3;
|
|
5241
5278
|
const MUTATION_SETTLE_AFTER_MS = 1500;
|
|
5242
5279
|
const AGENT_STREAM_IDLE_TIMEOUT_MS = 3e4;
|
|
5243
|
-
const logger$
|
|
5280
|
+
const logger$k = createLogger("Extractor");
|
|
5244
5281
|
const EMPTY_PAGE_CONTENT = {
|
|
5245
5282
|
title: "",
|
|
5246
5283
|
content: "",
|
|
@@ -5966,7 +6003,8 @@ async function waitForDomReady(webContents, timeoutMs = DEFAULT_PAGE_SCRIPT_TIME
|
|
|
5966
6003
|
(function() {
|
|
5967
6004
|
return document.readyState || "";
|
|
5968
6005
|
})()
|
|
5969
|
-
|
|
6006
|
+
`,
|
|
6007
|
+
{ label: "ready-state" }
|
|
5970
6008
|
);
|
|
5971
6009
|
if (readyState === "interactive" || readyState === "complete") {
|
|
5972
6010
|
return;
|
|
@@ -5974,7 +6012,7 @@ async function waitForDomReady(webContents, timeoutMs = DEFAULT_PAGE_SCRIPT_TIME
|
|
|
5974
6012
|
await delay(75);
|
|
5975
6013
|
}
|
|
5976
6014
|
}
|
|
5977
|
-
async function executeScript(webContents, script) {
|
|
6015
|
+
async function executeScript(webContents, script, options = {}) {
|
|
5978
6016
|
if (webContents.isDestroyed()) {
|
|
5979
6017
|
return null;
|
|
5980
6018
|
}
|
|
@@ -5990,7 +6028,15 @@ async function executeScript(webContents, script) {
|
|
|
5990
6028
|
})
|
|
5991
6029
|
]);
|
|
5992
6030
|
} catch (err) {
|
|
5993
|
-
|
|
6031
|
+
const label = options.label ? ` (${options.label})` : "";
|
|
6032
|
+
const url = webContents.getURL() || "unknown URL";
|
|
6033
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6034
|
+
const detail = `Failed to execute page script${label} on ${url}: ${message}`;
|
|
6035
|
+
if (options.warnOnFailure) {
|
|
6036
|
+
logger$k.warn(detail);
|
|
6037
|
+
} else {
|
|
6038
|
+
logger$k.debug(detail);
|
|
6039
|
+
}
|
|
5994
6040
|
return null;
|
|
5995
6041
|
} finally {
|
|
5996
6042
|
if (timer) {
|
|
@@ -6087,7 +6133,8 @@ async function estimateExtractionTimeout(webContents) {
|
|
|
6087
6133
|
try {
|
|
6088
6134
|
const elementCount = await executeScript(
|
|
6089
6135
|
webContents,
|
|
6090
|
-
`(function() { try { return document.querySelectorAll('*').length; } catch { return 0; } })()
|
|
6136
|
+
`(function() { try { return document.querySelectorAll('*').length; } catch { return 0; } })()`,
|
|
6137
|
+
{ label: "element-count" }
|
|
6091
6138
|
);
|
|
6092
6139
|
if (typeof elementCount === "number" && elementCount > 5e3) {
|
|
6093
6140
|
const extra = Math.min(
|
|
@@ -6097,15 +6144,19 @@ async function estimateExtractionTimeout(webContents) {
|
|
|
6097
6144
|
return EXTRACT_TIMEOUT_BASE_MS + extra;
|
|
6098
6145
|
}
|
|
6099
6146
|
} catch (err) {
|
|
6100
|
-
logger$
|
|
6147
|
+
logger$k.warn("Failed to estimate extraction timeout, using base timeout:", err);
|
|
6101
6148
|
}
|
|
6102
6149
|
return EXTRACT_TIMEOUT_BASE_MS;
|
|
6103
6150
|
}
|
|
6104
6151
|
async function extractContentInner(webContents) {
|
|
6105
6152
|
await waitForDomReady(webContents);
|
|
6106
6153
|
const [preloadResult, directResult] = await Promise.all([
|
|
6107
|
-
executeScript(webContents, PRELOAD_EXTRACTION_SCRIPT
|
|
6108
|
-
|
|
6154
|
+
executeScript(webContents, PRELOAD_EXTRACTION_SCRIPT, {
|
|
6155
|
+
label: "preload-extraction"
|
|
6156
|
+
}),
|
|
6157
|
+
executeScript(webContents, DIRECT_EXTRACTION_SCRIPT, {
|
|
6158
|
+
label: "direct-extraction"
|
|
6159
|
+
})
|
|
6109
6160
|
]);
|
|
6110
6161
|
return mergePageContent(
|
|
6111
6162
|
[preloadResult, directResult],
|
|
@@ -6985,8 +7036,9 @@ function isClickReadLoop(names) {
|
|
|
6985
7036
|
}
|
|
6986
7037
|
return clickReadPairs >= 2;
|
|
6987
7038
|
}
|
|
7039
|
+
const TERMINAL_TOOL_RESULT = "__VESSEL_TERMINAL_TOOL_RESULT__";
|
|
6988
7040
|
const ANTHROPIC_MAX_TOKENS = 4096;
|
|
6989
|
-
function isRecord(value) {
|
|
7041
|
+
function isRecord$1(value) {
|
|
6990
7042
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6991
7043
|
}
|
|
6992
7044
|
function anthropicModelLikelySupportsThinking(model) {
|
|
@@ -7110,7 +7162,7 @@ class AnthropicProvider {
|
|
|
7110
7162
|
} else if (event.type === "content_block_stop" && currentToolUse) {
|
|
7111
7163
|
try {
|
|
7112
7164
|
const input = JSON.parse(currentToolUse.inputJson || "{}");
|
|
7113
|
-
if (!isRecord(input)) {
|
|
7165
|
+
if (!isRecord$1(input)) {
|
|
7114
7166
|
throw new Error("Tool input must be a JSON object");
|
|
7115
7167
|
}
|
|
7116
7168
|
toolUseBlocks.push({
|
|
@@ -7188,6 +7240,9 @@ class AnthropicProvider {
|
|
|
7188
7240
|
const msg = toolErr instanceof Error ? toolErr.message : String(toolErr);
|
|
7189
7241
|
result = `Error: Tool execution failed — ${msg}. Try a different approach or call read_page to refresh context.`;
|
|
7190
7242
|
}
|
|
7243
|
+
if (result === TERMINAL_TOOL_RESULT) {
|
|
7244
|
+
return;
|
|
7245
|
+
}
|
|
7191
7246
|
let parsedRich = null;
|
|
7192
7247
|
try {
|
|
7193
7248
|
const parsed = JSON.parse(result);
|
|
@@ -7456,7 +7511,7 @@ const MAX_MCP_NAV_CONTENT_LENGTH = 3e4;
|
|
|
7456
7511
|
const MAX_AGENT_DEBUG_CONTENT_LENGTH = 2e4;
|
|
7457
7512
|
const LLAMA_CPP_MIN_CTX_TOKENS = 16384;
|
|
7458
7513
|
const LLAMA_CPP_RECOMMENDED_CTX_TOKENS = 32768;
|
|
7459
|
-
const logger$
|
|
7514
|
+
const logger$j = createLogger("OpenAIProvider");
|
|
7460
7515
|
function shouldDebugAgentLoop() {
|
|
7461
7516
|
const value = process.env.VESSEL_DEBUG_AGENT_LOOP;
|
|
7462
7517
|
return value === "1" || value === "true";
|
|
@@ -7981,9 +8036,9 @@ function resolveToolCallName(rawName, args, availableToolNames) {
|
|
|
7981
8036
|
function logAgentLoopDebug(payload) {
|
|
7982
8037
|
if (!shouldDebugAgentLoop()) return;
|
|
7983
8038
|
try {
|
|
7984
|
-
logger$
|
|
8039
|
+
logger$j.info(`[agent-debug] ${JSON.stringify(payload)}`);
|
|
7985
8040
|
} catch (err) {
|
|
7986
|
-
logger$
|
|
8041
|
+
logger$j.warn("Failed to serialize debug payload:", err);
|
|
7987
8042
|
}
|
|
7988
8043
|
}
|
|
7989
8044
|
function recoverTextEncodedToolCalls(text, availableToolNames) {
|
|
@@ -8454,6 +8509,9 @@ class OpenAICompatProvider {
|
|
|
8454
8509
|
const msg = toolErr instanceof Error ? toolErr.message : String(toolErr);
|
|
8455
8510
|
result = `Error: Tool execution failed — ${msg}. Try a different approach or call read_page to refresh context.`;
|
|
8456
8511
|
}
|
|
8512
|
+
if (result === TERMINAL_TOOL_RESULT) {
|
|
8513
|
+
return;
|
|
8514
|
+
}
|
|
8457
8515
|
let toolContent = result;
|
|
8458
8516
|
try {
|
|
8459
8517
|
const parsed = JSON.parse(result);
|
|
@@ -8523,12 +8581,15 @@ async function openExternalAllowlisted(url, rule) {
|
|
|
8523
8581
|
if (!schemes.includes(parsed.protocol)) {
|
|
8524
8582
|
throw new Error(`Blocked external URL scheme: ${parsed.protocol}`);
|
|
8525
8583
|
}
|
|
8584
|
+
if (parsed.username || parsed.password) {
|
|
8585
|
+
throw new Error("Blocked external URL with embedded credentials");
|
|
8586
|
+
}
|
|
8526
8587
|
if (rule.hosts && !rule.hosts.includes(parsed.hostname)) {
|
|
8527
8588
|
throw new Error(`Blocked external URL host: ${parsed.hostname}`);
|
|
8528
8589
|
}
|
|
8529
8590
|
await electron.shell.openExternal(parsed.toString());
|
|
8530
8591
|
}
|
|
8531
|
-
const logger$
|
|
8592
|
+
const logger$i = createLogger("CodexOAuth");
|
|
8532
8593
|
const ISSUER = "https://auth.openai.com";
|
|
8533
8594
|
const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
8534
8595
|
const SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
|
|
@@ -8593,54 +8654,6 @@ function parseTokenExpiry(accessToken) {
|
|
|
8593
8654
|
}
|
|
8594
8655
|
return Date.now() + 36e5;
|
|
8595
8656
|
}
|
|
8596
|
-
async function exchangeIdTokenForApiKey(idToken) {
|
|
8597
|
-
const body = new URLSearchParams({
|
|
8598
|
-
grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
|
|
8599
|
-
client_id: CLIENT_ID,
|
|
8600
|
-
requested_token: "openai-api-key",
|
|
8601
|
-
subject_token: idToken,
|
|
8602
|
-
subject_token_type: "urn:ietf:params:oauth:token-type:id_token"
|
|
8603
|
-
});
|
|
8604
|
-
const response = await fetch(`${ISSUER}/oauth/token`, {
|
|
8605
|
-
method: "POST",
|
|
8606
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
8607
|
-
body: body.toString()
|
|
8608
|
-
});
|
|
8609
|
-
if (!response.ok) {
|
|
8610
|
-
let errorMsg = `OpenAI API token exchange failed: ${response.status}`;
|
|
8611
|
-
try {
|
|
8612
|
-
const err = await response.json();
|
|
8613
|
-
if (typeof err.error_description === "string") {
|
|
8614
|
-
errorMsg = err.error_description;
|
|
8615
|
-
} else if (typeof err.error === "string") {
|
|
8616
|
-
errorMsg = err.error;
|
|
8617
|
-
}
|
|
8618
|
-
} catch {
|
|
8619
|
-
}
|
|
8620
|
-
throw new Error(errorMsg);
|
|
8621
|
-
}
|
|
8622
|
-
const data = await response.json();
|
|
8623
|
-
if (!data.access_token) {
|
|
8624
|
-
throw new Error("OpenAI API token exchange did not return an access token");
|
|
8625
|
-
}
|
|
8626
|
-
return data.access_token;
|
|
8627
|
-
}
|
|
8628
|
-
async function ensureCodexApiKey(tokens) {
|
|
8629
|
-
if (tokens.apiKey) return tokens;
|
|
8630
|
-
if (!tokens.idToken) return tokens;
|
|
8631
|
-
try {
|
|
8632
|
-
return {
|
|
8633
|
-
...tokens,
|
|
8634
|
-
apiKey: await exchangeIdTokenForApiKey(tokens.idToken)
|
|
8635
|
-
};
|
|
8636
|
-
} catch (err) {
|
|
8637
|
-
logger$g.warn(
|
|
8638
|
-
"Codex API-key token exchange failed; continuing with ChatGPT OAuth tokens:",
|
|
8639
|
-
err
|
|
8640
|
-
);
|
|
8641
|
-
return tokens;
|
|
8642
|
-
}
|
|
8643
|
-
}
|
|
8644
8657
|
async function exchangeCodeForTokens(code, redirectUri, codeVerifier) {
|
|
8645
8658
|
const body = new URLSearchParams({
|
|
8646
8659
|
grant_type: "authorization_code",
|
|
@@ -8678,7 +8691,7 @@ async function exchangeCodeForTokens(code, redirectUri, codeVerifier) {
|
|
|
8678
8691
|
accountId: claims?.accountId || "",
|
|
8679
8692
|
accountEmail: claims?.email
|
|
8680
8693
|
};
|
|
8681
|
-
return
|
|
8694
|
+
return tokens;
|
|
8682
8695
|
}
|
|
8683
8696
|
async function refreshAccessToken(tokens) {
|
|
8684
8697
|
const body = new URLSearchParams({
|
|
@@ -8714,7 +8727,7 @@ async function refreshAccessToken(tokens) {
|
|
|
8714
8727
|
accountId: claims?.accountId || tokens.accountId || "",
|
|
8715
8728
|
accountEmail: claims?.email || tokens.accountEmail
|
|
8716
8729
|
};
|
|
8717
|
-
return
|
|
8730
|
+
return refreshedTokens;
|
|
8718
8731
|
}
|
|
8719
8732
|
function startServer(port, pkce, expectedState, resolve, reject) {
|
|
8720
8733
|
const server = http.createServer(async (req, res) => {
|
|
@@ -8825,7 +8838,7 @@ async function startCodexOAuth(onStatus) {
|
|
|
8825
8838
|
try {
|
|
8826
8839
|
onStatus(status, error);
|
|
8827
8840
|
} catch {
|
|
8828
|
-
logger$
|
|
8841
|
+
logger$i.warn("Codex OAuth status callback failed — window may be closed");
|
|
8829
8842
|
}
|
|
8830
8843
|
};
|
|
8831
8844
|
const wrappedResolve = (tokens) => {
|
|
@@ -8865,7 +8878,7 @@ async function startCodexOAuth(onStatus) {
|
|
|
8865
8878
|
const authUrl = buildAuthorizeUrl(port, pkce, state2);
|
|
8866
8879
|
safeOnStatus("waiting");
|
|
8867
8880
|
openExternalAllowlisted(authUrl, { hosts: ["auth.openai.com"] }).catch((err) => {
|
|
8868
|
-
logger$
|
|
8881
|
+
logger$i.warn("Failed to open browser, user will need the URL:", err);
|
|
8869
8882
|
});
|
|
8870
8883
|
}).catch(wrappedReject);
|
|
8871
8884
|
});
|
|
@@ -8877,20 +8890,68 @@ function cancelCodexOAuth() {
|
|
|
8877
8890
|
try {
|
|
8878
8891
|
activeFlow.onStatus("idle");
|
|
8879
8892
|
} catch {
|
|
8880
|
-
logger$
|
|
8893
|
+
logger$i.warn("Codex OAuth cancel status callback failed — window may be closed");
|
|
8881
8894
|
}
|
|
8882
8895
|
activeFlow = null;
|
|
8883
8896
|
}
|
|
8884
|
-
const logger$
|
|
8897
|
+
const logger$h = createLogger("CodexProvider");
|
|
8885
8898
|
const REFRESH_WINDOW_MS = 5 * 60 * 1e3;
|
|
8886
8899
|
const CODEX_BACKEND_BASE_URL = "https://chatgpt.com/backend-api/codex";
|
|
8887
8900
|
const CODEX_CLIENT_VERSION = "0.129.0";
|
|
8901
|
+
function isRecord(value) {
|
|
8902
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
8903
|
+
}
|
|
8904
|
+
async function createCodexFunctionCallOutput(functionCall, availableToolNames, onChunk, onToolCall) {
|
|
8905
|
+
const callId = functionCall.call_id || functionCall.id || "";
|
|
8906
|
+
const name = functionCall.name || "";
|
|
8907
|
+
if (!callId) {
|
|
8908
|
+
return {
|
|
8909
|
+
type: "function_call_output",
|
|
8910
|
+
call_id: callId,
|
|
8911
|
+
output: "Error: Function call was missing a call_id. Please retry the tool call."
|
|
8912
|
+
};
|
|
8913
|
+
}
|
|
8914
|
+
if (!name || !availableToolNames.has(name)) {
|
|
8915
|
+
onChunk(`
|
|
8916
|
+
<<tool:${name || "unknown"}:⚠ unsupported>>
|
|
8917
|
+
`);
|
|
8918
|
+
return {
|
|
8919
|
+
type: "function_call_output",
|
|
8920
|
+
call_id: callId,
|
|
8921
|
+
output: `Error: Unsupported tool${name ? `: ${name}` : ""}. Use one of the provided tools.`
|
|
8922
|
+
};
|
|
8923
|
+
}
|
|
8924
|
+
let args;
|
|
8925
|
+
try {
|
|
8926
|
+
const parsed = JSON.parse(functionCall.arguments || "{}");
|
|
8927
|
+
if (!isRecord(parsed)) throw new Error("Tool arguments must be a JSON object");
|
|
8928
|
+
args = parsed;
|
|
8929
|
+
} catch {
|
|
8930
|
+
onChunk(`
|
|
8931
|
+
<<tool:${name}:⚠ invalid args>>
|
|
8932
|
+
`);
|
|
8933
|
+
return {
|
|
8934
|
+
type: "function_call_output",
|
|
8935
|
+
call_id: callId,
|
|
8936
|
+
output: "Error: Invalid JSON in tool arguments. Please retry with a valid JSON object."
|
|
8937
|
+
};
|
|
8938
|
+
}
|
|
8939
|
+
const output = await onToolCall(name, args);
|
|
8940
|
+
if (output === TERMINAL_TOOL_RESULT) {
|
|
8941
|
+
return { terminal: true };
|
|
8942
|
+
}
|
|
8943
|
+
return {
|
|
8944
|
+
type: "function_call_output",
|
|
8945
|
+
call_id: callId,
|
|
8946
|
+
output
|
|
8947
|
+
};
|
|
8948
|
+
}
|
|
8888
8949
|
class CodexProvider {
|
|
8889
8950
|
agentToolProfile;
|
|
8890
8951
|
tokens;
|
|
8891
8952
|
model;
|
|
8892
8953
|
abortController = null;
|
|
8893
|
-
constructor(tokens, model
|
|
8954
|
+
constructor(tokens, model) {
|
|
8894
8955
|
this.tokens = tokens;
|
|
8895
8956
|
this.model = model;
|
|
8896
8957
|
this.agentToolProfile = "default";
|
|
@@ -8898,7 +8959,7 @@ class CodexProvider {
|
|
|
8898
8959
|
async ensureFreshTokens() {
|
|
8899
8960
|
if (Date.now() < this.tokens.expiresAt - REFRESH_WINDOW_MS) return;
|
|
8900
8961
|
try {
|
|
8901
|
-
logger$
|
|
8962
|
+
logger$h.info("Refreshing Codex access token");
|
|
8902
8963
|
const fresh = await refreshAccessToken(this.tokens);
|
|
8903
8964
|
this.tokens = fresh;
|
|
8904
8965
|
writeStoredCodexTokens(fresh);
|
|
@@ -8909,7 +8970,7 @@ class CodexProvider {
|
|
|
8909
8970
|
);
|
|
8910
8971
|
}
|
|
8911
8972
|
}
|
|
8912
|
-
backendHeaders() {
|
|
8973
|
+
backendHeaders(turnState) {
|
|
8913
8974
|
const headers = {
|
|
8914
8975
|
Authorization: `Bearer ${this.tokens.accessToken}`,
|
|
8915
8976
|
"Content-Type": "application/json",
|
|
@@ -8920,6 +8981,9 @@ class CodexProvider {
|
|
|
8920
8981
|
if (this.tokens.accountId) {
|
|
8921
8982
|
headers["ChatGPT-Account-ID"] = this.tokens.accountId;
|
|
8922
8983
|
}
|
|
8984
|
+
if (turnState) {
|
|
8985
|
+
headers["x-codex-turn-state"] = turnState;
|
|
8986
|
+
}
|
|
8923
8987
|
return headers;
|
|
8924
8988
|
}
|
|
8925
8989
|
buildInput(userMessage, history) {
|
|
@@ -8928,7 +8992,7 @@ class CodexProvider {
|
|
|
8928
8992
|
input.push({
|
|
8929
8993
|
type: "message",
|
|
8930
8994
|
role: msg.role,
|
|
8931
|
-
content: [{ type: "input_text", text: msg.content }]
|
|
8995
|
+
content: [{ type: msg.role === "assistant" ? "output_text" : "input_text", text: msg.content }]
|
|
8932
8996
|
});
|
|
8933
8997
|
}
|
|
8934
8998
|
input.push({
|
|
@@ -8938,7 +9002,7 @@ class CodexProvider {
|
|
|
8938
9002
|
});
|
|
8939
9003
|
return input;
|
|
8940
9004
|
}
|
|
8941
|
-
handleStreamEvent(raw, onChunk,
|
|
9005
|
+
handleStreamEvent(raw, onChunk, acc) {
|
|
8942
9006
|
if (!raw.trim() || raw.trim() === "[DONE]") return;
|
|
8943
9007
|
let event;
|
|
8944
9008
|
try {
|
|
@@ -8947,13 +9011,28 @@ class CodexProvider {
|
|
|
8947
9011
|
return;
|
|
8948
9012
|
}
|
|
8949
9013
|
if (event.type === "response.output_text.delta" && event.delta) {
|
|
8950
|
-
emittedTextFromDelta
|
|
9014
|
+
acc.emittedTextFromDelta = true;
|
|
9015
|
+
acc.text += event.delta;
|
|
8951
9016
|
onChunk(event.delta);
|
|
8952
9017
|
return;
|
|
8953
9018
|
}
|
|
8954
|
-
if (event.type === "response.
|
|
8955
|
-
const
|
|
8956
|
-
if (
|
|
9019
|
+
if (event.type === "response.function_call_arguments.delta" && event.delta) {
|
|
9020
|
+
const key2 = event.call_id || event.item_id || "";
|
|
9021
|
+
if (key2) {
|
|
9022
|
+
acc.functionCallArgs.set(key2, (acc.functionCallArgs.get(key2) || "") + event.delta);
|
|
9023
|
+
}
|
|
9024
|
+
return;
|
|
9025
|
+
}
|
|
9026
|
+
if (event.type === "response.output_item.done" && event.item) {
|
|
9027
|
+
const item = event.item;
|
|
9028
|
+
if (item.type === "function_call") {
|
|
9029
|
+
const key2 = item.call_id || item.id || "";
|
|
9030
|
+
const args = acc.functionCallArgs.get(key2) || item.arguments || "";
|
|
9031
|
+
acc.functionCallArgs.delete(key2);
|
|
9032
|
+
acc.items.push({ ...item, arguments: args });
|
|
9033
|
+
} else if (item.type === "message") {
|
|
9034
|
+
acc.items.push(item);
|
|
9035
|
+
}
|
|
8957
9036
|
return;
|
|
8958
9037
|
}
|
|
8959
9038
|
if (event.type === "response.failed") {
|
|
@@ -8962,18 +9041,12 @@ class CodexProvider {
|
|
|
8962
9041
|
throw new Error(message);
|
|
8963
9042
|
}
|
|
8964
9043
|
}
|
|
8965
|
-
async streamCodexResponse(
|
|
9044
|
+
async streamCodexResponse(requestBody, onChunk, turnState) {
|
|
8966
9045
|
const response = await fetch(`${CODEX_BACKEND_BASE_URL}/responses`, {
|
|
8967
9046
|
method: "POST",
|
|
8968
|
-
headers: this.backendHeaders(),
|
|
9047
|
+
headers: this.backendHeaders(turnState),
|
|
8969
9048
|
signal: this.abortController?.signal,
|
|
8970
|
-
body: JSON.stringify(
|
|
8971
|
-
model: this.model,
|
|
8972
|
-
instructions: systemPrompt,
|
|
8973
|
-
input: this.buildInput(userMessage, history),
|
|
8974
|
-
stream: true,
|
|
8975
|
-
store: false
|
|
8976
|
-
})
|
|
9049
|
+
body: JSON.stringify(requestBody)
|
|
8977
9050
|
});
|
|
8978
9051
|
if (!response.ok) {
|
|
8979
9052
|
const text = await response.text().catch(() => "");
|
|
@@ -8984,37 +9057,57 @@ class CodexProvider {
|
|
|
8984
9057
|
if (!response.body) {
|
|
8985
9058
|
throw new Error("Codex backend returned an empty response stream");
|
|
8986
9059
|
}
|
|
9060
|
+
const newTurnState = response.headers.get("x-codex-turn-state") || null;
|
|
8987
9061
|
const reader = response.body.getReader();
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
|
|
8991
|
-
|
|
8992
|
-
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
|
|
9002
|
-
|
|
9003
|
-
|
|
9004
|
-
|
|
9005
|
-
|
|
9006
|
-
|
|
9062
|
+
try {
|
|
9063
|
+
const decoder = new TextDecoder();
|
|
9064
|
+
let buffer = "";
|
|
9065
|
+
const acc = {
|
|
9066
|
+
text: "",
|
|
9067
|
+
items: [],
|
|
9068
|
+
emittedTextFromDelta: false,
|
|
9069
|
+
functionCallArgs: /* @__PURE__ */ new Map()
|
|
9070
|
+
};
|
|
9071
|
+
while (true) {
|
|
9072
|
+
const { value, done } = await reader.read();
|
|
9073
|
+
if (done) break;
|
|
9074
|
+
buffer += decoder.decode(value, { stream: true });
|
|
9075
|
+
let separatorIndex;
|
|
9076
|
+
while ((separatorIndex = buffer.indexOf("\n\n")) !== -1) {
|
|
9077
|
+
const block = buffer.slice(0, separatorIndex);
|
|
9078
|
+
buffer = buffer.slice(separatorIndex + 2);
|
|
9079
|
+
const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
|
|
9080
|
+
this.handleStreamEvent(data, onChunk, acc);
|
|
9081
|
+
}
|
|
9082
|
+
}
|
|
9083
|
+
const trailing = buffer.trim();
|
|
9084
|
+
if (trailing) {
|
|
9085
|
+
const data = trailing.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
|
|
9086
|
+
this.handleStreamEvent(data, onChunk, acc);
|
|
9087
|
+
}
|
|
9088
|
+
return { text: acc.text, items: acc.items, turnState: newTurnState };
|
|
9089
|
+
} finally {
|
|
9090
|
+
reader.releaseLock();
|
|
9007
9091
|
}
|
|
9008
9092
|
}
|
|
9009
9093
|
async streamQuery(systemPrompt, userMessage, onChunk, onEnd, history) {
|
|
9010
9094
|
await this.ensureFreshTokens();
|
|
9011
9095
|
this.abortController = new AbortController();
|
|
9012
9096
|
try {
|
|
9013
|
-
await this.streamCodexResponse(
|
|
9097
|
+
await this.streamCodexResponse(
|
|
9098
|
+
{
|
|
9099
|
+
model: this.model,
|
|
9100
|
+
instructions: systemPrompt,
|
|
9101
|
+
input: this.buildInput(userMessage, history),
|
|
9102
|
+
stream: true,
|
|
9103
|
+
store: false
|
|
9104
|
+
},
|
|
9105
|
+
onChunk
|
|
9106
|
+
);
|
|
9014
9107
|
} catch (err) {
|
|
9015
9108
|
if (err.name !== "AbortError") {
|
|
9016
9109
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9017
|
-
logger$
|
|
9110
|
+
logger$h.error("Codex streamQuery error:", err);
|
|
9018
9111
|
onChunk(`
|
|
9019
9112
|
|
|
9020
9113
|
[Error: ${msg}]`);
|
|
@@ -9024,15 +9117,65 @@ class CodexProvider {
|
|
|
9024
9117
|
onEnd();
|
|
9025
9118
|
}
|
|
9026
9119
|
}
|
|
9027
|
-
async streamAgentQuery(systemPrompt, userMessage,
|
|
9120
|
+
async streamAgentQuery(systemPrompt, userMessage, tools, onChunk, onToolCall, onEnd, history) {
|
|
9028
9121
|
await this.ensureFreshTokens();
|
|
9029
9122
|
this.abortController = new AbortController();
|
|
9123
|
+
const maxIterations = getEffectiveMaxIterations();
|
|
9124
|
+
const availableToolNames = new Set(tools.map((tool) => tool.name));
|
|
9125
|
+
let iterationsUsed = 0;
|
|
9126
|
+
const convertedTools = tools.map((tool) => ({
|
|
9127
|
+
type: "function",
|
|
9128
|
+
name: tool.name,
|
|
9129
|
+
description: tool.description || "",
|
|
9130
|
+
parameters: tool.input_schema
|
|
9131
|
+
}));
|
|
9132
|
+
let currentInput = this.buildInput(userMessage, history);
|
|
9133
|
+
let turnState = null;
|
|
9030
9134
|
try {
|
|
9031
|
-
|
|
9135
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
9136
|
+
iterationsUsed = i + 1;
|
|
9137
|
+
const result = await this.streamCodexResponse(
|
|
9138
|
+
{
|
|
9139
|
+
model: this.model,
|
|
9140
|
+
instructions: systemPrompt,
|
|
9141
|
+
input: currentInput,
|
|
9142
|
+
tools: convertedTools,
|
|
9143
|
+
stream: true,
|
|
9144
|
+
store: false
|
|
9145
|
+
},
|
|
9146
|
+
onChunk,
|
|
9147
|
+
turnState || void 0
|
|
9148
|
+
);
|
|
9149
|
+
turnState = result.turnState || turnState;
|
|
9150
|
+
const functionCalls = result.items.filter(
|
|
9151
|
+
(item) => item.type === "function_call"
|
|
9152
|
+
);
|
|
9153
|
+
if (functionCalls.length === 0) {
|
|
9154
|
+
break;
|
|
9155
|
+
}
|
|
9156
|
+
currentInput = [];
|
|
9157
|
+
for (const fc of functionCalls) {
|
|
9158
|
+
const output = await createCodexFunctionCallOutput(
|
|
9159
|
+
fc,
|
|
9160
|
+
availableToolNames,
|
|
9161
|
+
onChunk,
|
|
9162
|
+
onToolCall
|
|
9163
|
+
);
|
|
9164
|
+
if ("terminal" in output) {
|
|
9165
|
+
return;
|
|
9166
|
+
}
|
|
9167
|
+
currentInput.push(output);
|
|
9168
|
+
}
|
|
9169
|
+
}
|
|
9170
|
+
if (iterationsUsed >= maxIterations) {
|
|
9171
|
+
onChunk(`
|
|
9172
|
+
|
|
9173
|
+
[Reached maximum tool call limit (${maxIterations} steps). You can adjust this in Settings → Max Tool Iterations, or continue by sending another message.]`);
|
|
9174
|
+
}
|
|
9032
9175
|
} catch (err) {
|
|
9033
9176
|
if (err.name !== "AbortError") {
|
|
9034
9177
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9035
|
-
logger$
|
|
9178
|
+
logger$h.error("Codex streamAgentQuery error:", err);
|
|
9036
9179
|
onChunk(`
|
|
9037
9180
|
|
|
9038
9181
|
[Error: ${msg}]`);
|
|
@@ -9112,11 +9255,11 @@ function buildLlamaCppCtxWarning(ctxSize) {
|
|
|
9112
9255
|
}
|
|
9113
9256
|
async function fetchCodexBackendModels(tokens) {
|
|
9114
9257
|
const url = new URL("https://chatgpt.com/backend-api/codex/models");
|
|
9115
|
-
url.searchParams.set("client_version",
|
|
9258
|
+
url.searchParams.set("client_version", CODEX_CLIENT_VERSION);
|
|
9116
9259
|
const headers = {
|
|
9117
9260
|
Authorization: `Bearer ${tokens.accessToken}`,
|
|
9118
9261
|
originator: "codex_cli_rs",
|
|
9119
|
-
"User-Agent":
|
|
9262
|
+
"User-Agent": `codex_cli_rs/${CODEX_CLIENT_VERSION} Vessel`
|
|
9120
9263
|
};
|
|
9121
9264
|
if (tokens.accountId) {
|
|
9122
9265
|
headers["ChatGPT-Account-ID"] = tokens.accountId;
|
|
@@ -9219,12 +9362,12 @@ function createProvider(config) {
|
|
|
9219
9362
|
"OpenAI Codex requires authentication. Open settings to connect your ChatGPT account."
|
|
9220
9363
|
);
|
|
9221
9364
|
}
|
|
9222
|
-
return new CodexProvider(tokens, normalized.model
|
|
9365
|
+
return new CodexProvider(tokens, normalized.model);
|
|
9223
9366
|
}
|
|
9224
9367
|
return new OpenAICompatProvider(normalized);
|
|
9225
9368
|
}
|
|
9226
9369
|
const require$1 = node_module.createRequire(require("url").pathToFileURL(__filename).href);
|
|
9227
|
-
const logger$
|
|
9370
|
+
const logger$g = createLogger("DevTrace");
|
|
9228
9371
|
let cachedFactory;
|
|
9229
9372
|
function createNoopTraceSession() {
|
|
9230
9373
|
return {
|
|
@@ -9257,7 +9400,7 @@ function loadLocalFactory() {
|
|
|
9257
9400
|
return cachedFactory;
|
|
9258
9401
|
}
|
|
9259
9402
|
} catch (err) {
|
|
9260
|
-
logger$
|
|
9403
|
+
logger$g.warn("Failed to load local trace logger:", err);
|
|
9261
9404
|
}
|
|
9262
9405
|
}
|
|
9263
9406
|
return cachedFactory;
|
|
@@ -13376,7 +13519,7 @@ function formatDeadLinkMessage(label, result) {
|
|
|
13376
13519
|
const status = result.statusCode ? `HTTP ${result.statusCode}` : "dead link";
|
|
13377
13520
|
return `Skipped stale link "${label}" because ${destination} returned ${status}. Try a different link or URL instead.`;
|
|
13378
13521
|
}
|
|
13379
|
-
const logger$
|
|
13522
|
+
const logger$f = createLogger("Screenshot");
|
|
13380
13523
|
const SCREENSHOT_RETRY_COUNT = 3;
|
|
13381
13524
|
const SCREENSHOT_RETRY_BASE_DELAY_MS = 120;
|
|
13382
13525
|
async function captureScreenshot(wc) {
|
|
@@ -13398,7 +13541,7 @@ async function captureScreenshot(wc) {
|
|
|
13398
13541
|
}
|
|
13399
13542
|
}
|
|
13400
13543
|
} catch (err) {
|
|
13401
|
-
logger$
|
|
13544
|
+
logger$f.debug(
|
|
13402
13545
|
`capturePage attempt ${attempt + 1} failed; retrying if attempts remain.`,
|
|
13403
13546
|
getErrorMessage(err)
|
|
13404
13547
|
);
|
|
@@ -14272,7 +14415,15 @@ function buildHuggingFaceSearchShortcut(currentUrl, rawQuery) {
|
|
|
14272
14415
|
appliedFilters
|
|
14273
14416
|
};
|
|
14274
14417
|
}
|
|
14275
|
-
|
|
14418
|
+
class TabMutex {
|
|
14419
|
+
queue = Promise.resolve();
|
|
14420
|
+
enqueue(fn) {
|
|
14421
|
+
return new Promise((resolve, reject) => {
|
|
14422
|
+
this.queue = this.queue.then(fn).then(resolve, reject);
|
|
14423
|
+
});
|
|
14424
|
+
}
|
|
14425
|
+
}
|
|
14426
|
+
const logger$e = createLogger("PageActions");
|
|
14276
14427
|
function getBookmarkMetadataFromArgs(args) {
|
|
14277
14428
|
return normalizeBookmarkMetadata({
|
|
14278
14429
|
intent: args.intent ?? args.intent,
|
|
@@ -14458,7 +14609,7 @@ async function executePageScript(wc, script, options) {
|
|
|
14458
14609
|
return result;
|
|
14459
14610
|
} catch (err) {
|
|
14460
14611
|
const label = options?.label ? ` (${options.label})` : "";
|
|
14461
|
-
logger$
|
|
14612
|
+
logger$e.warn(`Failed to execute page script${label}:`, err);
|
|
14462
14613
|
return null;
|
|
14463
14614
|
} finally {
|
|
14464
14615
|
if (timer) {
|
|
@@ -14559,7 +14710,7 @@ Search results snapshot:
|
|
|
14559
14710
|
${truncated}`;
|
|
14560
14711
|
}
|
|
14561
14712
|
} catch (err) {
|
|
14562
|
-
logger$
|
|
14713
|
+
logger$e.warn("Failed to build post-search summary, falling back to nav summary:", err);
|
|
14563
14714
|
}
|
|
14564
14715
|
const fallback = await getPostNavSummary(wc);
|
|
14565
14716
|
return fallback ? `${fallback}
|
|
@@ -14582,7 +14733,7 @@ Page snapshot after navigation:
|
|
|
14582
14733
|
${truncated}`;
|
|
14583
14734
|
}
|
|
14584
14735
|
} catch (err) {
|
|
14585
|
-
logger$
|
|
14736
|
+
logger$e.warn("Failed to build post-click navigation summary:", err);
|
|
14586
14737
|
}
|
|
14587
14738
|
return "";
|
|
14588
14739
|
}
|
|
@@ -15076,7 +15227,7 @@ async function restoreLocaleSnapshot(wc, snapshot2) {
|
|
|
15076
15227
|
}
|
|
15077
15228
|
}
|
|
15078
15229
|
} catch (err) {
|
|
15079
|
-
logger$
|
|
15230
|
+
logger$e.warn("Failed to restore locale via history navigation, trying URL reload fallback:", err);
|
|
15080
15231
|
}
|
|
15081
15232
|
if (snapshot2.url && snapshot2.url !== wc.getURL()) {
|
|
15082
15233
|
try {
|
|
@@ -15085,7 +15236,7 @@ async function restoreLocaleSnapshot(wc, snapshot2) {
|
|
|
15085
15236
|
await waitForLoad(wc, 3e3);
|
|
15086
15237
|
return;
|
|
15087
15238
|
} catch (err) {
|
|
15088
|
-
logger$
|
|
15239
|
+
logger$e.warn("Failed to restore locale via safe URL load, trying page reload fallback:", err);
|
|
15089
15240
|
}
|
|
15090
15241
|
}
|
|
15091
15242
|
if (snapshot2.url) {
|
|
@@ -15093,7 +15244,7 @@ async function restoreLocaleSnapshot(wc, snapshot2) {
|
|
|
15093
15244
|
await wc.reload();
|
|
15094
15245
|
await waitForLoad(wc, 3e3);
|
|
15095
15246
|
} catch (err) {
|
|
15096
|
-
logger$
|
|
15247
|
+
logger$e.warn("Failed to restore locale via page reload:", err);
|
|
15097
15248
|
}
|
|
15098
15249
|
}
|
|
15099
15250
|
}
|
|
@@ -15445,7 +15596,7 @@ ${postActivationOverlayHint}`;
|
|
|
15445
15596
|
return `${clickText} -> ${hrefFallbackUrl} (recovered via href fallback)`;
|
|
15446
15597
|
}
|
|
15447
15598
|
} catch (err) {
|
|
15448
|
-
logger$
|
|
15599
|
+
logger$e.warn("Failed href fallback after click, returning generic click result:", err);
|
|
15449
15600
|
}
|
|
15450
15601
|
}
|
|
15451
15602
|
}
|
|
@@ -15490,7 +15641,7 @@ async function tryAutoDismissCartDialog(wc) {
|
|
|
15490
15641
|
return result;
|
|
15491
15642
|
}
|
|
15492
15643
|
} catch (err) {
|
|
15493
|
-
logger$
|
|
15644
|
+
logger$e.warn("Failed to auto-dismiss cart dialog, falling back to dialog actions:", err);
|
|
15494
15645
|
}
|
|
15495
15646
|
return null;
|
|
15496
15647
|
}
|
|
@@ -17434,6 +17585,19 @@ const KNOWN_TOOLS = /* @__PURE__ */ new Set([
|
|
|
17434
17585
|
]);
|
|
17435
17586
|
async function executeAction(name, args, ctx) {
|
|
17436
17587
|
name = normalizeToolAlias(name);
|
|
17588
|
+
if (ctx.tabId && ctx._tabMutex) {
|
|
17589
|
+
return ctx._tabMutex.enqueue(async () => {
|
|
17590
|
+
const prevActiveId = ctx.tabManager.getActiveTabId();
|
|
17591
|
+
if (prevActiveId !== ctx.tabId) ctx.tabManager.switchTab(ctx.tabId);
|
|
17592
|
+
try {
|
|
17593
|
+
return await executeAction(name, args, { ...ctx, tabId: void 0, _tabMutex: void 0 });
|
|
17594
|
+
} finally {
|
|
17595
|
+
if (prevActiveId && prevActiveId !== ctx.tabId) {
|
|
17596
|
+
ctx.tabManager.switchTab(prevActiveId);
|
|
17597
|
+
}
|
|
17598
|
+
}
|
|
17599
|
+
});
|
|
17600
|
+
}
|
|
17437
17601
|
if (!KNOWN_TOOLS.has(name)) {
|
|
17438
17602
|
for (const known of KNOWN_TOOLS) {
|
|
17439
17603
|
if (name.startsWith(known) && name.length > known.length) {
|
|
@@ -17760,7 +17924,7 @@ async function executeAction(name, args, ctx) {
|
|
|
17760
17924
|
)
|
|
17761
17925
|
]);
|
|
17762
17926
|
} catch (err) {
|
|
17763
|
-
logger$
|
|
17927
|
+
logger$e.warn("Failed to extract content for read_page, falling back to lighter recovery:", err);
|
|
17764
17928
|
content = null;
|
|
17765
17929
|
}
|
|
17766
17930
|
if (!content || content.content.length === 0) {
|
|
@@ -17777,12 +17941,12 @@ async function executeAction(name, args, ctx) {
|
|
|
17777
17941
|
new Promise((resolve) => setTimeout(() => resolve(null), 3e3))
|
|
17778
17942
|
]);
|
|
17779
17943
|
} catch (err) {
|
|
17780
|
-
logger$
|
|
17944
|
+
logger$e.warn("Failed to re-extract content after iframe consent dismissal:", err);
|
|
17781
17945
|
content = null;
|
|
17782
17946
|
}
|
|
17783
17947
|
}
|
|
17784
17948
|
} catch (err) {
|
|
17785
|
-
logger$
|
|
17949
|
+
logger$e.warn("Failed iframe consent dismissal during read_page recovery:", err);
|
|
17786
17950
|
}
|
|
17787
17951
|
}
|
|
17788
17952
|
if (content && content.content.length > 0) {
|
|
@@ -18195,7 +18359,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
18195
18359
|
try {
|
|
18196
18360
|
page = await extractContent(wc);
|
|
18197
18361
|
} catch (err) {
|
|
18198
|
-
logger$
|
|
18362
|
+
logger$e.warn("Failed to extract content for suggest:", err);
|
|
18199
18363
|
return "Could not read page. Try navigate to a working URL.";
|
|
18200
18364
|
}
|
|
18201
18365
|
const suggestions = [];
|
|
@@ -18583,7 +18747,80 @@ WARNING: You have clicked ${clickStreakCount} elements on this page without veri
|
|
|
18583
18747
|
}
|
|
18584
18748
|
return formattedResult + await getPostActionState$1(ctx, name) + clickNavSummary + streakWarning + flowCtx;
|
|
18585
18749
|
}
|
|
18586
|
-
|
|
18750
|
+
function buildOrchestratorSystemPrompt() {
|
|
18751
|
+
return `You are the Research Captain of Vessel. You orchestrate deep research on behalf of the user.
|
|
18752
|
+
|
|
18753
|
+
YOUR ROLE:
|
|
18754
|
+
You are accountable for the final Research Report. The report has YOUR name on it. You do not blindly accept sub-agent findings — you review, challenge, and demand more when needed. You are the captain, and the sub-agents are your crew.
|
|
18755
|
+
|
|
18756
|
+
CORE PRINCIPLES:
|
|
18757
|
+
- You OWN the research question end-to-end. If the answer is insufficient, you dig deeper.
|
|
18758
|
+
- Every factual claim in your final report MUST be backed by a specific source URL and extracted quote. No citation = the claim does not survive synthesis.
|
|
18759
|
+
- You are authoritative but honest. Flag contradictions and gaps explicitly. Never invent to fill a hole.
|
|
18760
|
+
|
|
18761
|
+
BRIEF PHASE:
|
|
18762
|
+
Your first job is to interview the user. Ask one question at a time, and for EVERY question you MUST provide 2–6 concrete answer choices as a bullet list so the user can click instead of typing. Cover:
|
|
18763
|
+
- What exactly do they want to know?
|
|
18764
|
+
- How deep? How many sources?
|
|
18765
|
+
- Who is the report for? Technical or layperson?
|
|
18766
|
+
- Any domains to prefer or avoid?
|
|
18767
|
+
- What does a good answer look like?
|
|
18768
|
+
|
|
18769
|
+
If the user's question is vague, switch into EXPLORATION MODE: proactively suggest 2–3 concrete research angles they might be interested in. Help them discover what they actually want to know.
|
|
18770
|
+
|
|
18771
|
+
Never ask a bare question without listed options. Every assistant turn must end with a question and concrete answer choices.
|
|
18772
|
+
|
|
18773
|
+
You CANNOT navigate or use tools during the brief. The brief is dialogue only. When you are confident you have enough context, summarize what you heard and ask the user to confirm before moving to planning.
|
|
18774
|
+
|
|
18775
|
+
PLANNING PHASE:
|
|
18776
|
+
After the brief is confirmed, produce a structured Research Objectives document with 2–5 independent threads. Each thread gets a specific question, suggested search queries, and a source budget. Present this as a clear, structured card for the user to review, edit, or approve.
|
|
18777
|
+
|
|
18778
|
+
EXECUTION PHASE:
|
|
18779
|
+
Sub-agents run in parallel, each handling one thread. You monitor their progress. If a thread stalls or produces thin findings, rebalance — reassign effort, ask the sub-agent to dig deeper, or spawn a replacement.
|
|
18780
|
+
|
|
18781
|
+
SYNTHESIS PHASE:
|
|
18782
|
+
Before writing the report, self-audit: "Do I have enough to answer the research question? Am I confident in every claim?" If not, request more from sub-agents.
|
|
18783
|
+
|
|
18784
|
+
Write the report with:
|
|
18785
|
+
- An executive summary
|
|
18786
|
+
- One section per thread with sourced claims
|
|
18787
|
+
- Explicit contradictions and gaps
|
|
18788
|
+
- A numbered source index
|
|
18789
|
+
|
|
18790
|
+
Never use emojis. Be concise. Be precise.`;
|
|
18791
|
+
}
|
|
18792
|
+
async function handleAIQuery(query, provider, activeWebContents, onChunk, onEnd, tabManager, runtime2, history, researchOrchestrator) {
|
|
18793
|
+
if (researchOrchestrator) {
|
|
18794
|
+
const researchState = researchOrchestrator.getState();
|
|
18795
|
+
if (researchState.phase === "briefing" || researchState.phase === "planning") {
|
|
18796
|
+
const isPlanning = researchState.phase === "planning";
|
|
18797
|
+
const phaseInstruction = isPlanning ? "\n\nNow produce the Research Objectives based on the brief conversation above. Output them as a JSON object with researchQuestion, threads (array of {label, question, searchQueries, sourceBudget}), audience, reportOutline, and totalSourceBudget fields." : "\n\nContinue the briefing interview. You MUST end every assistant turn with one concise question AND 2–6 concrete answer choices formatted as bullet points (e.g. `- Option one`). Never ask a bare question without listed options. Each option should read like something the user could click as their answer. Do not browse, plan the report, or write a prose preamble.";
|
|
18798
|
+
let fullResponse = "";
|
|
18799
|
+
const wrappedOnChunk = (text) => {
|
|
18800
|
+
fullResponse += text;
|
|
18801
|
+
if (text) onChunk(text);
|
|
18802
|
+
};
|
|
18803
|
+
const wrappedOnEnd = () => {
|
|
18804
|
+
if (isPlanning) {
|
|
18805
|
+
const parsed = researchOrchestrator.parseAndSetObjectives(fullResponse);
|
|
18806
|
+
if (!parsed) {
|
|
18807
|
+
onChunk(
|
|
18808
|
+
"\n\n[Failed to parse objectives. Please try confirming the brief again or refine your research question.]"
|
|
18809
|
+
);
|
|
18810
|
+
}
|
|
18811
|
+
}
|
|
18812
|
+
onEnd();
|
|
18813
|
+
};
|
|
18814
|
+
await provider.streamQuery(
|
|
18815
|
+
buildOrchestratorSystemPrompt() + phaseInstruction,
|
|
18816
|
+
query,
|
|
18817
|
+
wrappedOnChunk,
|
|
18818
|
+
wrappedOnEnd,
|
|
18819
|
+
history
|
|
18820
|
+
);
|
|
18821
|
+
return;
|
|
18822
|
+
}
|
|
18823
|
+
}
|
|
18587
18824
|
const lowerQuery = query.toLowerCase().trim();
|
|
18588
18825
|
const isSummarize = lowerQuery.startsWith("summarize") || lowerQuery.startsWith("tldr") || lowerQuery === "summary";
|
|
18589
18826
|
if (provider.streamAgentQuery && tabManager && activeWebContents && runtime2) {
|
|
@@ -19520,10 +19757,20 @@ Exception: ${result.exceptionDetails}`);
|
|
|
19520
19757
|
}
|
|
19521
19758
|
);
|
|
19522
19759
|
}
|
|
19523
|
-
const logger$
|
|
19760
|
+
const logger$d = createLogger("VaultShared");
|
|
19524
19761
|
const ALGORITHM = "aes-256-gcm";
|
|
19525
19762
|
const IV_LENGTH = 12;
|
|
19526
19763
|
const AUTH_TAG_LENGTH = 16;
|
|
19764
|
+
const KEY_STORAGE_PREFIX = "base64:";
|
|
19765
|
+
function encodeEncryptionKeyForStorage(key2) {
|
|
19766
|
+
return `${KEY_STORAGE_PREFIX}${key2.toString("base64")}`;
|
|
19767
|
+
}
|
|
19768
|
+
function decodeEncryptionKeyFromStorage(value) {
|
|
19769
|
+
if (value.startsWith(KEY_STORAGE_PREFIX)) {
|
|
19770
|
+
return Buffer.from(value.slice(KEY_STORAGE_PREFIX.length), "base64");
|
|
19771
|
+
}
|
|
19772
|
+
return Buffer.from(value, "utf-8");
|
|
19773
|
+
}
|
|
19527
19774
|
function assertSecretStorageAvailable(customMessage) {
|
|
19528
19775
|
if (!electron.safeStorage.isEncryptionAvailable()) {
|
|
19529
19776
|
throw new Error(
|
|
@@ -19536,11 +19783,17 @@ function getOrCreateEncryptionKey(keyFilename) {
|
|
|
19536
19783
|
const keyPath = path$1.join(electron.app.getPath("userData"), keyFilename);
|
|
19537
19784
|
if (fs$1.existsSync(keyPath)) {
|
|
19538
19785
|
const encryptedKey = fs$1.readFileSync(keyPath);
|
|
19539
|
-
|
|
19786
|
+
const key22 = decodeEncryptionKeyFromStorage(
|
|
19787
|
+
electron.safeStorage.decryptString(encryptedKey)
|
|
19788
|
+
);
|
|
19789
|
+
if (key22.length !== 32) {
|
|
19790
|
+
throw new Error("Stored vault encryption key has an invalid length.");
|
|
19791
|
+
}
|
|
19792
|
+
return key22;
|
|
19540
19793
|
}
|
|
19541
19794
|
const key2 = crypto$2.randomBytes(32);
|
|
19542
19795
|
fs$1.mkdirSync(path$1.dirname(keyPath), { recursive: true });
|
|
19543
|
-
const encrypted = electron.safeStorage.encryptString(key2
|
|
19796
|
+
const encrypted = electron.safeStorage.encryptString(encodeEncryptionKeyForStorage(key2));
|
|
19544
19797
|
fs$1.writeFileSync(keyPath, encrypted, { mode: 384 });
|
|
19545
19798
|
fs$1.chmodSync(keyPath, 384);
|
|
19546
19799
|
return key2;
|
|
@@ -19598,7 +19851,7 @@ function createVaultIO(vaultFilename, encrypt2, decrypt2) {
|
|
|
19598
19851
|
cachedEntries = JSON.parse(json);
|
|
19599
19852
|
return cachedEntries;
|
|
19600
19853
|
} catch (err) {
|
|
19601
|
-
logger$
|
|
19854
|
+
logger$d.error("Failed to load vault:", err);
|
|
19602
19855
|
throw new Error("Could not unlock the vault. Check OS secret storage availability.");
|
|
19603
19856
|
}
|
|
19604
19857
|
}
|
|
@@ -19607,7 +19860,7 @@ function createVaultIO(vaultFilename, encrypt2, decrypt2) {
|
|
|
19607
19860
|
const encrypted = encrypt2(json);
|
|
19608
19861
|
const vaultPath = getVaultPath();
|
|
19609
19862
|
fs$1.mkdirSync(path$1.dirname(vaultPath), { recursive: true });
|
|
19610
|
-
fs$1.writeFileSync(vaultPath, encrypted);
|
|
19863
|
+
fs$1.writeFileSync(vaultPath, encrypted, { mode: 384 });
|
|
19611
19864
|
fs$1.chmodSync(vaultPath, 384);
|
|
19612
19865
|
cachedEntries = entries;
|
|
19613
19866
|
}
|
|
@@ -19663,17 +19916,25 @@ function createAuditLog(filename, maxEntries) {
|
|
|
19663
19916
|
try {
|
|
19664
19917
|
const auditPath = getAuditPath2();
|
|
19665
19918
|
fs$1.mkdirSync(path$1.dirname(auditPath), { recursive: true });
|
|
19666
|
-
fs$1.appendFileSync(auditPath, JSON.stringify(entry) + "\n"
|
|
19919
|
+
fs$1.appendFileSync(auditPath, JSON.stringify(entry) + "\n", {
|
|
19920
|
+
encoding: "utf-8",
|
|
19921
|
+
mode: 384
|
|
19922
|
+
});
|
|
19923
|
+
fs$1.chmodSync(auditPath, 384);
|
|
19667
19924
|
try {
|
|
19668
19925
|
const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
19669
19926
|
if (lines.length > maxEntries) {
|
|
19670
19927
|
const trimmed = lines.slice(-maxEntries);
|
|
19671
|
-
fs$1.writeFileSync(auditPath, trimmed.join("\n") + "\n"
|
|
19928
|
+
fs$1.writeFileSync(auditPath, trimmed.join("\n") + "\n", {
|
|
19929
|
+
encoding: "utf-8",
|
|
19930
|
+
mode: 384
|
|
19931
|
+
});
|
|
19932
|
+
fs$1.chmodSync(auditPath, 384);
|
|
19672
19933
|
}
|
|
19673
19934
|
} catch {
|
|
19674
19935
|
}
|
|
19675
19936
|
} catch (err) {
|
|
19676
|
-
logger$
|
|
19937
|
+
logger$d.error("Failed to write audit log:", err);
|
|
19677
19938
|
}
|
|
19678
19939
|
}
|
|
19679
19940
|
function readAuditLog2(limit = 100) {
|
|
@@ -19683,7 +19944,7 @@ function createAuditLog(filename, maxEntries) {
|
|
|
19683
19944
|
const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
19684
19945
|
return lines.slice(-Math.min(limit, maxEntries)).map((line) => JSON.parse(line)).reverse();
|
|
19685
19946
|
} catch (err) {
|
|
19686
|
-
logger$
|
|
19947
|
+
logger$d.error("Failed to read audit log:", err);
|
|
19687
19948
|
return [];
|
|
19688
19949
|
}
|
|
19689
19950
|
}
|
|
@@ -19787,7 +20048,7 @@ async function requestConsent(request) {
|
|
|
19787
20048
|
}
|
|
19788
20049
|
const AUDIT_FILENAME = "vessel-vault-audit.jsonl";
|
|
19789
20050
|
const MAX_ENTRIES = 1e3;
|
|
19790
|
-
const logger$
|
|
20051
|
+
const logger$c = createLogger("VaultAudit");
|
|
19791
20052
|
function getAuditPath() {
|
|
19792
20053
|
return path$1.join(electron.app.getPath("userData"), AUDIT_FILENAME);
|
|
19793
20054
|
}
|
|
@@ -19795,9 +20056,13 @@ function appendAuditEntry(entry) {
|
|
|
19795
20056
|
try {
|
|
19796
20057
|
const auditPath = getAuditPath();
|
|
19797
20058
|
fs$1.mkdirSync(path$1.dirname(auditPath), { recursive: true });
|
|
19798
|
-
fs$1.appendFileSync(auditPath, JSON.stringify(entry) + "\n"
|
|
20059
|
+
fs$1.appendFileSync(auditPath, JSON.stringify(entry) + "\n", {
|
|
20060
|
+
encoding: "utf-8",
|
|
20061
|
+
mode: 384
|
|
20062
|
+
});
|
|
20063
|
+
fs$1.chmodSync(auditPath, 384);
|
|
19799
20064
|
} catch (err) {
|
|
19800
|
-
logger$
|
|
20065
|
+
logger$c.error("Failed to write audit log:", err);
|
|
19801
20066
|
}
|
|
19802
20067
|
}
|
|
19803
20068
|
function readAuditLog$1(limit = 100) {
|
|
@@ -19807,7 +20072,7 @@ function readAuditLog$1(limit = 100) {
|
|
|
19807
20072
|
const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
19808
20073
|
return lines.slice(-Math.min(limit, MAX_ENTRIES)).map((line) => JSON.parse(line)).reverse();
|
|
19809
20074
|
} catch (err) {
|
|
19810
|
-
logger$
|
|
20075
|
+
logger$c.error("Failed to read audit log:", err);
|
|
19811
20076
|
return [];
|
|
19812
20077
|
}
|
|
19813
20078
|
}
|
|
@@ -19975,7 +20240,7 @@ async function requestHumanVaultConsent(request) {
|
|
|
19975
20240
|
}
|
|
19976
20241
|
let httpServer = null;
|
|
19977
20242
|
let mcpAuthToken = null;
|
|
19978
|
-
const logger$
|
|
20243
|
+
const logger$b = createLogger("MCP");
|
|
19979
20244
|
const MCP_AUTH_FILENAME = "mcp-auth.json";
|
|
19980
20245
|
function getMcpAuthFilePath() {
|
|
19981
20246
|
const configDir = process.env.VESSEL_CONFIG_DIR || path$1.join(
|
|
@@ -20012,7 +20277,7 @@ function writeMcpAuthFile(endpoint, token) {
|
|
|
20012
20277
|
);
|
|
20013
20278
|
fs$1.chmodSync(filePath2, 384);
|
|
20014
20279
|
} catch (err) {
|
|
20015
|
-
logger$
|
|
20280
|
+
logger$b.warn("Failed to write auth file:", err);
|
|
20016
20281
|
}
|
|
20017
20282
|
}
|
|
20018
20283
|
function clearMcpAuthFile() {
|
|
@@ -20038,7 +20303,7 @@ function clearMcpAuthFile() {
|
|
|
20038
20303
|
);
|
|
20039
20304
|
fs$1.chmodSync(filePath2, 384);
|
|
20040
20305
|
} catch (err) {
|
|
20041
|
-
logger$
|
|
20306
|
+
logger$b.warn("Failed to clear auth file:", err);
|
|
20042
20307
|
}
|
|
20043
20308
|
}
|
|
20044
20309
|
function regenerateMcpAuthToken() {
|
|
@@ -20075,7 +20340,7 @@ function isDangerousMcpAction(name) {
|
|
|
20075
20340
|
}
|
|
20076
20341
|
function requiresExplicitMcpApproval(name, args) {
|
|
20077
20342
|
if (name === "delete_session" || name === "close_tab" || name === "load_session") return true;
|
|
20078
|
-
if (name === "
|
|
20343
|
+
if (name === "remove_bookmark_folder" && args.delete_contents === true) return true;
|
|
20079
20344
|
return false;
|
|
20080
20345
|
}
|
|
20081
20346
|
function getActiveTabSummary(tabManager) {
|
|
@@ -20140,7 +20405,7 @@ async function getPostActionState(tabManager, name) {
|
|
|
20140
20405
|
}
|
|
20141
20406
|
}
|
|
20142
20407
|
} catch (err) {
|
|
20143
|
-
logger$
|
|
20408
|
+
logger$b.warn("Failed to compute post-action state warning:", err);
|
|
20144
20409
|
}
|
|
20145
20410
|
return `${warning}
|
|
20146
20411
|
[state: url=${wc.getURL()}, canGoBack=${tab.canGoBack()}, canGoForward=${tab.canGoForward()}, loading=${wc.isLoading()}]`;
|
|
@@ -20243,7 +20508,7 @@ async function waitForConditionMcp(wc, text, selector, timeoutMs) {
|
|
|
20243
20508
|
}
|
|
20244
20509
|
})()
|
|
20245
20510
|
`).catch((err) => {
|
|
20246
|
-
logger$
|
|
20511
|
+
logger$b.warn("Failed to gather wait_for timeout diagnostic:", err);
|
|
20247
20512
|
return null;
|
|
20248
20513
|
});
|
|
20249
20514
|
if (typeof diagnostic === "string" && diagnostic.trim()) {
|
|
@@ -20330,7 +20595,7 @@ function registerTools(server, tabManager, runtime2) {
|
|
|
20330
20595
|
const page = await extractContent(wc);
|
|
20331
20596
|
pageType = detectPageType(page);
|
|
20332
20597
|
} catch (err) {
|
|
20333
|
-
logger$
|
|
20598
|
+
logger$b.warn("Failed to detect page type for tool scoring, falling back to GENERAL:", err);
|
|
20334
20599
|
}
|
|
20335
20600
|
}
|
|
20336
20601
|
const scored = TOOL_DEFINITIONS.map((def) => {
|
|
@@ -21728,7 +21993,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
21728
21993
|
void 0,
|
|
21729
21994
|
h.color
|
|
21730
21995
|
).catch(
|
|
21731
|
-
(err) => logger$
|
|
21996
|
+
(err) => logger$b.warn("Failed to restore highlight after removal:", err)
|
|
21732
21997
|
);
|
|
21733
21998
|
}
|
|
21734
21999
|
}
|
|
@@ -22576,7 +22841,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
22576
22841
|
try {
|
|
22577
22842
|
page = await extractContent(wc);
|
|
22578
22843
|
} catch (err) {
|
|
22579
|
-
logger$
|
|
22844
|
+
logger$b.warn("Failed to extract page while generating suggestions:", err);
|
|
22580
22845
|
return asTextResponse(
|
|
22581
22846
|
"Could not read page. Try navigate to a working URL."
|
|
22582
22847
|
);
|
|
@@ -23185,7 +23450,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
23185
23450
|
try {
|
|
23186
23451
|
targetDomain = new URL(tab.state.url).hostname;
|
|
23187
23452
|
} catch (err) {
|
|
23188
|
-
logger$
|
|
23453
|
+
logger$b.warn("Failed to parse active tab URL for vault_status:", err);
|
|
23189
23454
|
return asErrorTextResponse("Could not parse active tab URL");
|
|
23190
23455
|
}
|
|
23191
23456
|
}
|
|
@@ -23251,7 +23516,7 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
23251
23516
|
try {
|
|
23252
23517
|
hostname = new URL(tab.state.url).hostname;
|
|
23253
23518
|
} catch (err) {
|
|
23254
|
-
logger$
|
|
23519
|
+
logger$b.warn("Failed to parse active tab URL for vault_login:", err);
|
|
23255
23520
|
return asErrorTextResponse("Could not parse active tab URL");
|
|
23256
23521
|
}
|
|
23257
23522
|
const matches = findEntriesForDomain(`https://${hostname}`);
|
|
@@ -23345,7 +23610,7 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
23345
23610
|
try {
|
|
23346
23611
|
hostname = new URL(tab.state.url).hostname;
|
|
23347
23612
|
} catch (err) {
|
|
23348
|
-
logger$
|
|
23613
|
+
logger$b.warn("Failed to parse active tab URL for vault_totp:", err);
|
|
23349
23614
|
return asErrorTextResponse("Could not parse active tab URL");
|
|
23350
23615
|
}
|
|
23351
23616
|
const matches = findEntriesForDomain(`https://${hostname}`);
|
|
@@ -23685,7 +23950,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
23685
23950
|
await mcpServer.connect(transport);
|
|
23686
23951
|
await transport.handleRequest(req, res);
|
|
23687
23952
|
} catch (error) {
|
|
23688
|
-
logger$
|
|
23953
|
+
logger$b.error("Error handling request:", error);
|
|
23689
23954
|
if (!res.headersSent) {
|
|
23690
23955
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
23691
23956
|
res.end(
|
|
@@ -23704,7 +23969,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
23704
23969
|
};
|
|
23705
23970
|
server.once("error", (error) => {
|
|
23706
23971
|
const message = error.code === "EADDRINUSE" ? `Port ${port} is already in use. MCP server not started.` : error.message;
|
|
23707
|
-
logger$
|
|
23972
|
+
logger$b.error("Server error:", error);
|
|
23708
23973
|
clearMcpAuthFile();
|
|
23709
23974
|
setMcpHealth({
|
|
23710
23975
|
configuredPort: port,
|
|
@@ -23736,7 +24001,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
23736
24001
|
message: `MCP server listening on ${endpoint}.`
|
|
23737
24002
|
});
|
|
23738
24003
|
if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
|
|
23739
|
-
logger$
|
|
24004
|
+
logger$b.info(`Server listening on ${endpoint} (auth enabled)`);
|
|
23740
24005
|
}
|
|
23741
24006
|
if (mcpAuthToken) {
|
|
23742
24007
|
writeMcpAuthFile(endpoint, mcpAuthToken);
|
|
@@ -23775,7 +24040,7 @@ function stopMcpServer() {
|
|
|
23775
24040
|
message: "MCP server is stopped."
|
|
23776
24041
|
});
|
|
23777
24042
|
if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
|
|
23778
|
-
logger$
|
|
24043
|
+
logger$b.info("Server stopped");
|
|
23779
24044
|
}
|
|
23780
24045
|
resolve();
|
|
23781
24046
|
});
|
|
@@ -23796,7 +24061,7 @@ const KIT_ID_UNSAFE_CHAR_PATTERN = /[\/\\\0]/;
|
|
|
23796
24061
|
function isSafeAutomationKitId(id) {
|
|
23797
24062
|
return id.length > 0 && !KIT_ID_UNSAFE_CHAR_PATTERN.test(id);
|
|
23798
24063
|
}
|
|
23799
|
-
const logger$
|
|
24064
|
+
const logger$a = createLogger("KitRegistry");
|
|
23800
24065
|
function getUserKitsDir() {
|
|
23801
24066
|
return path$1.join(electron.app.getPath("userData"), "kits");
|
|
23802
24067
|
}
|
|
@@ -23834,10 +24099,10 @@ function getInstalledKits() {
|
|
|
23834
24099
|
if (isValidKit(parsed)) {
|
|
23835
24100
|
kits.push(parsed);
|
|
23836
24101
|
} else {
|
|
23837
|
-
logger$
|
|
24102
|
+
logger$a.warn(`Skipping invalid kit file: ${file}`);
|
|
23838
24103
|
}
|
|
23839
24104
|
} catch (err) {
|
|
23840
|
-
logger$
|
|
24105
|
+
logger$a.warn(`Failed to read kit file: ${file}`, err);
|
|
23841
24106
|
}
|
|
23842
24107
|
}
|
|
23843
24108
|
return kits;
|
|
@@ -23909,7 +24174,44 @@ function uninstallKit(id, scheduledKitIds) {
|
|
|
23909
24174
|
return errorResult("Failed to remove the kit file.");
|
|
23910
24175
|
}
|
|
23911
24176
|
}
|
|
23912
|
-
const
|
|
24177
|
+
const trustedIpcSenderIds = /* @__PURE__ */ new Set();
|
|
24178
|
+
function registerTrustedIpcSender(wc) {
|
|
24179
|
+
trustedIpcSenderIds.add(wc.id);
|
|
24180
|
+
wc.once("destroyed", () => trustedIpcSenderIds.delete(wc.id));
|
|
24181
|
+
}
|
|
24182
|
+
function assertTrustedIpcSender(event) {
|
|
24183
|
+
if (!trustedIpcSenderIds.has(event.sender.id)) {
|
|
24184
|
+
throw new Error("Blocked IPC from untrusted renderer");
|
|
24185
|
+
}
|
|
24186
|
+
}
|
|
24187
|
+
function isManagedTabIpcSender(event, tabManager) {
|
|
24188
|
+
return Boolean(tabManager.findTabByWebContentsId(event.sender.id));
|
|
24189
|
+
}
|
|
24190
|
+
function assertString(value, name) {
|
|
24191
|
+
if (typeof value !== "string") throw new Error(`${name} must be a string`);
|
|
24192
|
+
}
|
|
24193
|
+
function assertOptionalString(value, name) {
|
|
24194
|
+
if (value !== void 0 && typeof value !== "string") {
|
|
24195
|
+
throw new Error(`${name} must be a string`);
|
|
24196
|
+
}
|
|
24197
|
+
}
|
|
24198
|
+
function assertNumber(value, name) {
|
|
24199
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
24200
|
+
throw new Error(`${name} must be a number`);
|
|
24201
|
+
}
|
|
24202
|
+
}
|
|
24203
|
+
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
24204
|
+
function isValidEmail(value) {
|
|
24205
|
+
return EMAIL_RE.test(value.trim());
|
|
24206
|
+
}
|
|
24207
|
+
function getActiveTabInfo(tabManager) {
|
|
24208
|
+
const tab = tabManager.getActiveTab();
|
|
24209
|
+
if (!tab) return null;
|
|
24210
|
+
const wc = tab.view.webContents;
|
|
24211
|
+
if (wc.isDestroyed()) return null;
|
|
24212
|
+
return { tab, wc };
|
|
24213
|
+
}
|
|
24214
|
+
const logger$9 = createLogger("Scheduler");
|
|
23913
24215
|
let jobs = [];
|
|
23914
24216
|
let removeIdleListener = null;
|
|
23915
24217
|
let broadcastFn = null;
|
|
@@ -23932,9 +24234,15 @@ function loadJobs() {
|
|
|
23932
24234
|
}
|
|
23933
24235
|
function saveJobs() {
|
|
23934
24236
|
try {
|
|
23935
|
-
|
|
24237
|
+
const jobsPath = getJobsPath();
|
|
24238
|
+
fs$1.mkdirSync(path$1.dirname(jobsPath), { recursive: true });
|
|
24239
|
+
fs$1.writeFileSync(jobsPath, JSON.stringify(jobs, null, 2), {
|
|
24240
|
+
encoding: "utf-8",
|
|
24241
|
+
mode: 384
|
|
24242
|
+
});
|
|
24243
|
+
fs$1.chmodSync(jobsPath, 384);
|
|
23936
24244
|
} catch (err) {
|
|
23937
|
-
logger$
|
|
24245
|
+
logger$9.warn("Failed to save jobs:", err);
|
|
23938
24246
|
}
|
|
23939
24247
|
}
|
|
23940
24248
|
function normalizeJob(job, now = /* @__PURE__ */ new Date()) {
|
|
@@ -24056,7 +24364,7 @@ async function fireJob(job, windowState, runtime2) {
|
|
|
24056
24364
|
};
|
|
24057
24365
|
startActivity();
|
|
24058
24366
|
if (!settings2.chatProvider) {
|
|
24059
|
-
logger$
|
|
24367
|
+
logger$9.warn(`Job "${job.kitName}" skipped — no chat provider configured`);
|
|
24060
24368
|
appendActivity(
|
|
24061
24369
|
"Chat provider not configured. Open Settings (Ctrl+,) to choose a provider."
|
|
24062
24370
|
);
|
|
@@ -24064,7 +24372,7 @@ async function fireJob(job, windowState, runtime2) {
|
|
|
24064
24372
|
return;
|
|
24065
24373
|
}
|
|
24066
24374
|
if (process.env.VESSEL_DEBUG_SCHEDULER === "1" || process.env.VESSEL_DEBUG_SCHEDULER === "true") {
|
|
24067
|
-
logger$
|
|
24375
|
+
logger$9.info(`Firing scheduled job: ${job.kitName} (${job.id})`);
|
|
24068
24376
|
}
|
|
24069
24377
|
try {
|
|
24070
24378
|
const provider = createProvider(settings2.chatProvider);
|
|
@@ -24117,7 +24425,7 @@ function tick(windowState, runtime2) {
|
|
|
24117
24425
|
saveJobs();
|
|
24118
24426
|
broadcastFn?.(Channels.SCHEDULE_JOBS_UPDATE, jobs);
|
|
24119
24427
|
void fireJob(job, windowState, runtime2).catch((err) => {
|
|
24120
|
-
logger$
|
|
24428
|
+
logger$9.warn("Unexpected error firing job:", err);
|
|
24121
24429
|
}).finally(fireNext);
|
|
24122
24430
|
};
|
|
24123
24431
|
fireNext();
|
|
@@ -24136,8 +24444,12 @@ function registerScheduleHandlers(windowState, runtime2, sendToAll) {
|
|
|
24136
24444
|
tick(windowState, runtime2);
|
|
24137
24445
|
setInterval(() => tick(windowState, runtime2), 6e4);
|
|
24138
24446
|
}, msToNextMinute);
|
|
24139
|
-
electron.ipcMain.handle(Channels.SCHEDULE_GET_ALL, () =>
|
|
24140
|
-
|
|
24447
|
+
electron.ipcMain.handle(Channels.SCHEDULE_GET_ALL, (event) => {
|
|
24448
|
+
assertTrustedIpcSender(event);
|
|
24449
|
+
return jobs;
|
|
24450
|
+
});
|
|
24451
|
+
electron.ipcMain.handle(Channels.SCHEDULE_CREATE, (event, rawJob) => {
|
|
24452
|
+
assertTrustedIpcSender(event);
|
|
24141
24453
|
if (!isValidJobData(rawJob)) {
|
|
24142
24454
|
throw new Error(
|
|
24143
24455
|
"Invalid job data. Required: kitId, kitName, kitIcon, renderedPrompt, schedule, enabled."
|
|
@@ -24154,7 +24466,8 @@ function registerScheduleHandlers(windowState, runtime2, sendToAll) {
|
|
|
24154
24466
|
sendToAll(Channels.SCHEDULE_JOBS_UPDATE, jobs);
|
|
24155
24467
|
return newJob;
|
|
24156
24468
|
});
|
|
24157
|
-
electron.ipcMain.handle(Channels.SCHEDULE_UPDATE, (
|
|
24469
|
+
electron.ipcMain.handle(Channels.SCHEDULE_UPDATE, (event, id, updates) => {
|
|
24470
|
+
assertTrustedIpcSender(event);
|
|
24158
24471
|
if (typeof id !== "string") throw new Error("id must be a string");
|
|
24159
24472
|
const job = jobs.find((j) => j.id === id);
|
|
24160
24473
|
if (!job) return null;
|
|
@@ -24180,7 +24493,8 @@ function registerScheduleHandlers(windowState, runtime2, sendToAll) {
|
|
|
24180
24493
|
sendToAll(Channels.SCHEDULE_JOBS_UPDATE, jobs);
|
|
24181
24494
|
return job;
|
|
24182
24495
|
});
|
|
24183
|
-
electron.ipcMain.handle(Channels.SCHEDULE_DELETE, (
|
|
24496
|
+
electron.ipcMain.handle(Channels.SCHEDULE_DELETE, (event, id) => {
|
|
24497
|
+
assertTrustedIpcSender(event);
|
|
24184
24498
|
if (typeof id !== "string") throw new Error("id must be a string");
|
|
24185
24499
|
const before = jobs.length;
|
|
24186
24500
|
jobs = jobs.filter((j) => j.id !== id);
|
|
@@ -24190,40 +24504,6 @@ function registerScheduleHandlers(windowState, runtime2, sendToAll) {
|
|
|
24190
24504
|
return true;
|
|
24191
24505
|
});
|
|
24192
24506
|
}
|
|
24193
|
-
const trustedIpcSenderIds = /* @__PURE__ */ new Set();
|
|
24194
|
-
function registerTrustedIpcSender(wc) {
|
|
24195
|
-
trustedIpcSenderIds.add(wc.id);
|
|
24196
|
-
wc.once("destroyed", () => trustedIpcSenderIds.delete(wc.id));
|
|
24197
|
-
}
|
|
24198
|
-
function assertTrustedIpcSender(event) {
|
|
24199
|
-
if (!trustedIpcSenderIds.has(event.sender.id)) {
|
|
24200
|
-
throw new Error("Blocked IPC from untrusted renderer");
|
|
24201
|
-
}
|
|
24202
|
-
}
|
|
24203
|
-
function assertString(value, name) {
|
|
24204
|
-
if (typeof value !== "string") throw new Error(`${name} must be a string`);
|
|
24205
|
-
}
|
|
24206
|
-
function assertOptionalString(value, name) {
|
|
24207
|
-
if (value !== void 0 && typeof value !== "string") {
|
|
24208
|
-
throw new Error(`${name} must be a string`);
|
|
24209
|
-
}
|
|
24210
|
-
}
|
|
24211
|
-
function assertNumber(value, name) {
|
|
24212
|
-
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
24213
|
-
throw new Error(`${name} must be a number`);
|
|
24214
|
-
}
|
|
24215
|
-
}
|
|
24216
|
-
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
24217
|
-
function isValidEmail(value) {
|
|
24218
|
-
return EMAIL_RE.test(value.trim());
|
|
24219
|
-
}
|
|
24220
|
-
function getActiveTabInfo(tabManager) {
|
|
24221
|
-
const tab = tabManager.getActiveTab();
|
|
24222
|
-
if (!tab) return null;
|
|
24223
|
-
const wc = tab.view.webContents;
|
|
24224
|
-
if (wc.isDestroyed()) return null;
|
|
24225
|
-
return { tab, wc };
|
|
24226
|
-
}
|
|
24227
24507
|
const SAVE_DEBOUNCE_MS = 250;
|
|
24228
24508
|
const PROFILE_FIELDS = [
|
|
24229
24509
|
"label",
|
|
@@ -24563,24 +24843,29 @@ function sanitizeAutofillUpdates(value) {
|
|
|
24563
24843
|
return updates;
|
|
24564
24844
|
}
|
|
24565
24845
|
function registerAutofillHandlers(windowState) {
|
|
24566
|
-
electron.ipcMain.handle(Channels.AUTOFILL_LIST, () => {
|
|
24846
|
+
electron.ipcMain.handle(Channels.AUTOFILL_LIST, (event) => {
|
|
24847
|
+
assertTrustedIpcSender(event);
|
|
24567
24848
|
return listProfiles();
|
|
24568
24849
|
});
|
|
24569
24850
|
electron.ipcMain.handle(
|
|
24570
24851
|
Channels.AUTOFILL_ADD,
|
|
24571
|
-
(
|
|
24852
|
+
(event, profile) => {
|
|
24853
|
+
assertTrustedIpcSender(event);
|
|
24572
24854
|
return addProfile(sanitizeAutofillProfile(profile));
|
|
24573
24855
|
}
|
|
24574
24856
|
);
|
|
24575
|
-
electron.ipcMain.handle(Channels.AUTOFILL_UPDATE, (
|
|
24857
|
+
electron.ipcMain.handle(Channels.AUTOFILL_UPDATE, (event, id, updates) => {
|
|
24858
|
+
assertTrustedIpcSender(event);
|
|
24576
24859
|
assertString(id, "id");
|
|
24577
24860
|
return updateProfile(id, sanitizeAutofillUpdates(updates));
|
|
24578
24861
|
});
|
|
24579
|
-
electron.ipcMain.handle(Channels.AUTOFILL_DELETE, (
|
|
24862
|
+
electron.ipcMain.handle(Channels.AUTOFILL_DELETE, (event, id) => {
|
|
24863
|
+
assertTrustedIpcSender(event);
|
|
24580
24864
|
assertString(id, "id");
|
|
24581
24865
|
return deleteProfile(id);
|
|
24582
24866
|
});
|
|
24583
|
-
electron.ipcMain.handle(Channels.AUTOFILL_FILL, async (
|
|
24867
|
+
electron.ipcMain.handle(Channels.AUTOFILL_FILL, async (event, profileId) => {
|
|
24868
|
+
assertTrustedIpcSender(event);
|
|
24584
24869
|
assertString(profileId, "profileId");
|
|
24585
24870
|
const profile = getProfile(profileId);
|
|
24586
24871
|
if (!profile) throw new Error("Profile not found");
|
|
@@ -24650,27 +24935,800 @@ function registerPageDiffHandlers(windowState, sendToRendererViews) {
|
|
|
24650
24935
|
electron.ipcMain.on(Channels.PAGE_DIFF_ACTIVITY, (event) => {
|
|
24651
24936
|
const wc = event.sender;
|
|
24652
24937
|
if (!wc || wc.isDestroyed()) return;
|
|
24938
|
+
if (!isManagedTabIpcSender(event, windowState.tabManager)) return;
|
|
24653
24939
|
if (!allowPageEvent(wc.id)) return;
|
|
24654
24940
|
notePageMutationActivity(wc, sendToRendererViews);
|
|
24655
24941
|
});
|
|
24656
24942
|
electron.ipcMain.on(Channels.PAGE_DIFF_DIRTY, (event) => {
|
|
24657
24943
|
const wc = event.sender;
|
|
24658
24944
|
if (!wc || wc.isDestroyed()) return;
|
|
24945
|
+
if (!isManagedTabIpcSender(event, windowState.tabManager)) return;
|
|
24659
24946
|
if (!allowPageEvent(wc.id)) return;
|
|
24660
24947
|
schedulePageSnapshotCapture(wc, sendToRendererViews);
|
|
24661
24948
|
});
|
|
24662
24949
|
}
|
|
24663
|
-
function
|
|
24664
|
-
|
|
24665
|
-
|
|
24666
|
-
|
|
24950
|
+
function renderReportAsMarkdown(report, traces) {
|
|
24951
|
+
const sections = [];
|
|
24952
|
+
sections.push(`# ${report.title}`);
|
|
24953
|
+
sections.push("");
|
|
24954
|
+
sections.push(`*Generated: ${report.generatedAt}*`);
|
|
24955
|
+
sections.push("");
|
|
24956
|
+
sections.push("## Executive Summary");
|
|
24957
|
+
sections.push(report.executiveSummary);
|
|
24958
|
+
sections.push("");
|
|
24959
|
+
for (const section of report.findingsByThread) {
|
|
24960
|
+
sections.push(`## ${section.threadLabel}`);
|
|
24961
|
+
sections.push(section.content);
|
|
24962
|
+
sections.push("");
|
|
24963
|
+
}
|
|
24964
|
+
if (report.contradictions.length > 0) {
|
|
24965
|
+
sections.push("## Contradictions & Discrepancies");
|
|
24966
|
+
for (const c of report.contradictions) {
|
|
24967
|
+
sections.push(`- **Claim:** ${c.claim}`);
|
|
24968
|
+
sections.push(` - Source A: [${c.sourceA.url}](${c.sourceA.url}) — "${c.sourceA.claim}"`);
|
|
24969
|
+
sections.push(` - Source B: [${c.sourceB.url}](${c.sourceB.url}) — "${c.sourceB.claim}"`);
|
|
24970
|
+
sections.push(` - **Resolution:** ${c.resolution}`);
|
|
24971
|
+
}
|
|
24972
|
+
sections.push("");
|
|
24973
|
+
}
|
|
24974
|
+
if (report.gaps.length > 0) {
|
|
24975
|
+
sections.push("## Gaps & Unanswered Questions");
|
|
24976
|
+
for (const gap of report.gaps) {
|
|
24977
|
+
sections.push(`- ${gap}`);
|
|
24978
|
+
}
|
|
24979
|
+
sections.push("");
|
|
24980
|
+
}
|
|
24981
|
+
sections.push("## Source Index");
|
|
24982
|
+
for (const source of report.sourceIndex) {
|
|
24983
|
+
sections.push(
|
|
24984
|
+
`${source.index}. [${source.title}](${source.url}) — accessed ${source.accessedAt}`
|
|
24985
|
+
);
|
|
24986
|
+
sections.push(` > "${source.supportingQuote}"`);
|
|
24987
|
+
}
|
|
24988
|
+
sections.push("");
|
|
24989
|
+
if (traces && traces.length > 0) {
|
|
24990
|
+
sections.push("---");
|
|
24991
|
+
sections.push("");
|
|
24992
|
+
sections.push("## Appendix: Agent Traces");
|
|
24993
|
+
for (const trace of traces) {
|
|
24994
|
+
sections.push(`### ${trace.threadLabel}`);
|
|
24995
|
+
sections.push(`Started: ${trace.startedAt} | Finished: ${trace.finishedAt}`);
|
|
24996
|
+
sections.push(`Tool calls: ${trace.toolCalls.length}`);
|
|
24997
|
+
if (trace.errors.length > 0) {
|
|
24998
|
+
sections.push(`Errors: ${trace.errors.length}`);
|
|
24999
|
+
for (const err of trace.errors) {
|
|
25000
|
+
sections.push(`- [${err.timestamp}] ${err.message}`);
|
|
25001
|
+
}
|
|
25002
|
+
}
|
|
25003
|
+
sections.push("");
|
|
25004
|
+
}
|
|
25005
|
+
}
|
|
25006
|
+
return sections.join("\n");
|
|
25007
|
+
}
|
|
25008
|
+
const logger$8 = createLogger("ResearchIPC");
|
|
25009
|
+
function registerResearchHandlers(getOrchestrator) {
|
|
25010
|
+
electron.ipcMain.handle(Channels.RESEARCH_STATE_GET, () => {
|
|
25011
|
+
return getOrchestrator().getState();
|
|
24667
25012
|
});
|
|
24668
25013
|
electron.ipcMain.handle(
|
|
24669
|
-
Channels.
|
|
24670
|
-
(
|
|
24671
|
-
|
|
24672
|
-
|
|
24673
|
-
|
|
25014
|
+
Channels.RESEARCH_START_BRIEF,
|
|
25015
|
+
async (_event, query) => {
|
|
25016
|
+
try {
|
|
25017
|
+
const trimmedQuery = query.trim();
|
|
25018
|
+
if (!trimmedQuery) {
|
|
25019
|
+
return { accepted: false, reason: "error" };
|
|
25020
|
+
}
|
|
25021
|
+
if (getOrchestrator().getState().phase !== "idle") {
|
|
25022
|
+
return { accepted: false, reason: "busy" };
|
|
25023
|
+
}
|
|
25024
|
+
await getOrchestrator().startBrief(trimmedQuery);
|
|
25025
|
+
return { accepted: true };
|
|
25026
|
+
} catch (err) {
|
|
25027
|
+
logger$8.error("RESEARCH_START_BRIEF failed", err);
|
|
25028
|
+
return { accepted: false, reason: "error" };
|
|
25029
|
+
}
|
|
25030
|
+
}
|
|
25031
|
+
);
|
|
25032
|
+
electron.ipcMain.handle(Channels.RESEARCH_CONFIRM_BRIEF, () => {
|
|
25033
|
+
try {
|
|
25034
|
+
if (isToolGated("research_confirm_brief")) {
|
|
25035
|
+
return { accepted: false, reason: "premium" };
|
|
25036
|
+
}
|
|
25037
|
+
const orchestrator = getOrchestrator();
|
|
25038
|
+
if (orchestrator.getState().phase !== "briefing") {
|
|
25039
|
+
return { accepted: false, reason: "error" };
|
|
25040
|
+
}
|
|
25041
|
+
orchestrator.confirmBrief();
|
|
25042
|
+
return { accepted: true };
|
|
25043
|
+
} catch (err) {
|
|
25044
|
+
logger$8.error("RESEARCH_CONFIRM_BRIEF failed", err);
|
|
25045
|
+
return { accepted: false, reason: "error" };
|
|
25046
|
+
}
|
|
25047
|
+
});
|
|
25048
|
+
electron.ipcMain.handle(
|
|
25049
|
+
Channels.RESEARCH_APPROVE_OBJECTIVES,
|
|
25050
|
+
(_event, options) => {
|
|
25051
|
+
try {
|
|
25052
|
+
if (isToolGated("research_approve_objectives")) {
|
|
25053
|
+
return { accepted: false, reason: "premium" };
|
|
25054
|
+
}
|
|
25055
|
+
const orchestrator = getOrchestrator();
|
|
25056
|
+
const state2 = orchestrator.getState();
|
|
25057
|
+
if (state2.phase !== "awaiting_approval" || !state2.objectives) {
|
|
25058
|
+
return { accepted: false, reason: "error" };
|
|
25059
|
+
}
|
|
25060
|
+
orchestrator.approveObjectives(
|
|
25061
|
+
options.supervisionMode,
|
|
25062
|
+
options.includeTraces
|
|
25063
|
+
);
|
|
25064
|
+
orchestrator.executeSubAgents().catch((err) => {
|
|
25065
|
+
logger$8.error("Background sub-agent execution failed", err);
|
|
25066
|
+
});
|
|
25067
|
+
return { accepted: true };
|
|
25068
|
+
} catch (err) {
|
|
25069
|
+
logger$8.error("RESEARCH_APPROVE_OBJECTIVES failed", err);
|
|
25070
|
+
return { accepted: false, reason: "error" };
|
|
25071
|
+
}
|
|
25072
|
+
}
|
|
25073
|
+
);
|
|
25074
|
+
electron.ipcMain.handle(
|
|
25075
|
+
Channels.RESEARCH_SET_MODE,
|
|
25076
|
+
(_event, mode) => {
|
|
25077
|
+
getOrchestrator().setSupervisionMode(mode);
|
|
25078
|
+
}
|
|
25079
|
+
);
|
|
25080
|
+
electron.ipcMain.handle(
|
|
25081
|
+
Channels.RESEARCH_SET_TRACES,
|
|
25082
|
+
(_event, include) => {
|
|
25083
|
+
getOrchestrator().setIncludeTraces(include);
|
|
25084
|
+
}
|
|
25085
|
+
);
|
|
25086
|
+
electron.ipcMain.handle(Channels.RESEARCH_CANCEL, () => {
|
|
25087
|
+
getOrchestrator().cancel();
|
|
25088
|
+
});
|
|
25089
|
+
electron.ipcMain.handle(Channels.RESEARCH_EXPORT_REPORT, async () => {
|
|
25090
|
+
try {
|
|
25091
|
+
if (isToolGated("research_export_report")) {
|
|
25092
|
+
return { accepted: false, reason: "premium" };
|
|
25093
|
+
}
|
|
25094
|
+
const state2 = getOrchestrator().getState();
|
|
25095
|
+
if (!state2.report) {
|
|
25096
|
+
return { accepted: false, reason: "error", error: "No report to export" };
|
|
25097
|
+
}
|
|
25098
|
+
const markdown = renderReportAsMarkdown(
|
|
25099
|
+
state2.report,
|
|
25100
|
+
state2.includeTraces ? state2.subAgentTraces : void 0
|
|
25101
|
+
);
|
|
25102
|
+
const { filePath: filePath2, canceled } = await electron.dialog.showSaveDialog({
|
|
25103
|
+
title: "Export Research Report",
|
|
25104
|
+
defaultPath: `${state2.report.title.replace(/[^a-zA-Z0-9 _-]/g, "")}.md`,
|
|
25105
|
+
filters: [
|
|
25106
|
+
{ name: "Markdown", extensions: ["md"] },
|
|
25107
|
+
{ name: "All Files", extensions: ["*"] }
|
|
25108
|
+
]
|
|
25109
|
+
});
|
|
25110
|
+
if (canceled || !filePath2) {
|
|
25111
|
+
return { accepted: false, reason: "cancelled" };
|
|
25112
|
+
}
|
|
25113
|
+
await promises.writeFile(filePath2, markdown, "utf-8");
|
|
25114
|
+
return { accepted: true, savedPath: filePath2 };
|
|
25115
|
+
} catch (err) {
|
|
25116
|
+
logger$8.error("RESEARCH_EXPORT_REPORT failed", err);
|
|
25117
|
+
return { accepted: false, reason: "error" };
|
|
25118
|
+
}
|
|
25119
|
+
});
|
|
25120
|
+
}
|
|
25121
|
+
function buildSubAgentSystemPrompt(thread) {
|
|
25122
|
+
const domainBlock = thread.blockedDomains.length > 0 ? `
|
|
25123
|
+
BLOCKED DOMAINS (never visit): ${thread.blockedDomains.join(", ")}` : "";
|
|
25124
|
+
const domainPref = thread.preferredDomains.length > 0 ? `
|
|
25125
|
+
PREFERRED DOMAINS: ${thread.preferredDomains.join(", ")}` : "";
|
|
25126
|
+
return `You are a Vessel research sub-agent assigned to a specific thread.
|
|
25127
|
+
|
|
25128
|
+
YOUR MISSION: ${thread.question}
|
|
25129
|
+
|
|
25130
|
+
SEARCH QUERIES TO START WITH:
|
|
25131
|
+
${thread.searchQueries.map((q) => `- ${q}`).join("\n")}${domainPref}${domainBlock}
|
|
25132
|
+
|
|
25133
|
+
SOURCE BUDGET: You may visit up to ${thread.sourceBudget} sources. Do not exceed this unless the captain explicitly increases it.
|
|
25134
|
+
|
|
25135
|
+
RULES:
|
|
25136
|
+
1. Every finding you report MUST include the source URL and the verbatim extracted quote that supports it.
|
|
25137
|
+
2. Never fabricate. If you cannot find an answer, say so.
|
|
25138
|
+
3. Stay on your thread. Do not wander into other research angles.
|
|
25139
|
+
4. Report findings incrementally. After visiting each source, report what you found.
|
|
25140
|
+
5. If a page is behind a paywall or requires login, note it and move on.
|
|
25141
|
+
6. Prefer primary sources over secondary commentary.
|
|
25142
|
+
7. Do not use emojis.
|
|
25143
|
+
|
|
25144
|
+
When done, report a summary of your execution: pages visited, useful sources found, discarded sources, any errors.`;
|
|
25145
|
+
}
|
|
25146
|
+
function buildSynthesisPrompt(objectives, findings) {
|
|
25147
|
+
const findingsBlock = findings.map(
|
|
25148
|
+
(f) => `
|
|
25149
|
+
### Thread: ${f.threadLabel}
|
|
25150
|
+
Question: ${f.threadQuestion}
|
|
25151
|
+
Execution: ${f.executionSummary}
|
|
25152
|
+
|
|
25153
|
+
Claims:
|
|
25154
|
+
${f.claims.map(
|
|
25155
|
+
(c, i) => `${i + 1}. ${c.claim}
|
|
25156
|
+
Source: ${c.sourceUrl}
|
|
25157
|
+
Title: ${c.sourceTitle}
|
|
25158
|
+
Accessed: ${c.extractedAt}
|
|
25159
|
+
Quote: "${c.extractedQuote}"
|
|
25160
|
+
Relevance: ${c.relevanceNote}`
|
|
25161
|
+
).join("\n")}
|
|
25162
|
+
|
|
25163
|
+
${f.discardedSources.length > 0 ? `Discarded sources:
|
|
25164
|
+
${f.discardedSources.map((d) => `- ${d.url}: ${d.reason}`).join("\n")}` : ""}`
|
|
25165
|
+
).join("\n\n---\n");
|
|
25166
|
+
return `Synthesize the following research findings into a structured JSON Research Report.
|
|
25167
|
+
|
|
25168
|
+
RESEARCH QUESTION: ${objectives.researchQuestion}
|
|
25169
|
+
AUDIENCE: ${objectives.audience}
|
|
25170
|
+
EXPECTED OUTLINE:
|
|
25171
|
+
${objectives.reportOutline.map((s) => `- ${s}`).join("\n")}
|
|
25172
|
+
|
|
25173
|
+
FINDINGS:
|
|
25174
|
+
${findingsBlock}
|
|
25175
|
+
|
|
25176
|
+
Return ONLY valid JSON — no markdown, no code fences, no commentary. The JSON object must have these exact fields:
|
|
25177
|
+
|
|
25178
|
+
{
|
|
25179
|
+
"title": "Report title (string)",
|
|
25180
|
+
"executiveSummary": "2-3 paragraph answer to the research question",
|
|
25181
|
+
"findingsByThread": [
|
|
25182
|
+
{ "threadLabel": "Label", "content": "Section content with claims and numbered citations [1], [2], etc." }
|
|
25183
|
+
],
|
|
25184
|
+
"contradictions": [
|
|
25185
|
+
{
|
|
25186
|
+
"claim": "The disputed claim",
|
|
25187
|
+
"sourceA": { "url": "https://...", "claim": "What source A says" },
|
|
25188
|
+
"sourceB": { "url": "https://...", "claim": "What source B says" },
|
|
25189
|
+
"resolution": "How to resolve the contradiction (or why it cannot be resolved)"
|
|
25190
|
+
}
|
|
25191
|
+
],
|
|
25192
|
+
"gaps": ["Gap or unanswered question 1", "Gap 2"],
|
|
25193
|
+
"sourceIndex": [
|
|
25194
|
+
{
|
|
25195
|
+
"index": 1,
|
|
25196
|
+
"url": "https://...",
|
|
25197
|
+
"title": "Page title",
|
|
25198
|
+
"accessedAt": "ISO timestamp from claim metadata",
|
|
25199
|
+
"supportingQuote": "Verbatim quote from the claim"
|
|
25200
|
+
}
|
|
25201
|
+
]
|
|
25202
|
+
}
|
|
25203
|
+
|
|
25204
|
+
RULES:
|
|
25205
|
+
1. Every factual claim MUST cite its source using the numbered index format [1], [2], etc.
|
|
25206
|
+
2. The sourceIndex numbers must correspond to the [n] citations in the text.
|
|
25207
|
+
3. Do not invent anything. Only use claims from the findings above.
|
|
25208
|
+
4. Omit empty arrays entirely (contradictions, gaps) — do not include "contradictions": [] if there are none.
|
|
25209
|
+
5. Do not use emojis.`;
|
|
25210
|
+
}
|
|
25211
|
+
const logger$7 = createLogger("ResearchOrchestrator");
|
|
25212
|
+
const MAX_THREADS = 5;
|
|
25213
|
+
function clone$1(value) {
|
|
25214
|
+
return structuredClone(value);
|
|
25215
|
+
}
|
|
25216
|
+
class ResearchOrchestrator {
|
|
25217
|
+
constructor(provider, tabManager, runtime2) {
|
|
25218
|
+
this.provider = provider;
|
|
25219
|
+
this.tabManager = tabManager;
|
|
25220
|
+
this.runtime = runtime2;
|
|
25221
|
+
this.state = this.initialState();
|
|
25222
|
+
}
|
|
25223
|
+
provider;
|
|
25224
|
+
tabManager;
|
|
25225
|
+
runtime;
|
|
25226
|
+
state;
|
|
25227
|
+
updateListener = null;
|
|
25228
|
+
// ── state access ──────────────────────────────────────────────
|
|
25229
|
+
initialState() {
|
|
25230
|
+
return {
|
|
25231
|
+
phase: "idle",
|
|
25232
|
+
supervisionMode: "interactive",
|
|
25233
|
+
includeTraces: false,
|
|
25234
|
+
objectives: null,
|
|
25235
|
+
threads: [],
|
|
25236
|
+
threadFindings: [],
|
|
25237
|
+
report: null,
|
|
25238
|
+
subAgentTraces: [],
|
|
25239
|
+
error: null,
|
|
25240
|
+
startedAt: null,
|
|
25241
|
+
originalQuery: null
|
|
25242
|
+
};
|
|
25243
|
+
}
|
|
25244
|
+
getState() {
|
|
25245
|
+
return clone$1(this.state);
|
|
25246
|
+
}
|
|
25247
|
+
setUpdateListener(listener) {
|
|
25248
|
+
this.updateListener = listener;
|
|
25249
|
+
if (listener) listener(this.getState());
|
|
25250
|
+
}
|
|
25251
|
+
emit() {
|
|
25252
|
+
if (this.updateListener) this.updateListener(this.getState());
|
|
25253
|
+
}
|
|
25254
|
+
setPhase(phase) {
|
|
25255
|
+
this.state.phase = phase;
|
|
25256
|
+
this.emit();
|
|
25257
|
+
}
|
|
25258
|
+
// ── supervision / config ──────────────────────────────────────
|
|
25259
|
+
setSupervisionMode(mode) {
|
|
25260
|
+
this.state.supervisionMode = mode;
|
|
25261
|
+
this.emit();
|
|
25262
|
+
}
|
|
25263
|
+
setIncludeTraces(include) {
|
|
25264
|
+
this.state.includeTraces = include;
|
|
25265
|
+
this.emit();
|
|
25266
|
+
}
|
|
25267
|
+
cancel() {
|
|
25268
|
+
this.state = this.initialState();
|
|
25269
|
+
this.emit();
|
|
25270
|
+
}
|
|
25271
|
+
/**
|
|
25272
|
+
* Swap the AI provider used by this orchestrator.
|
|
25273
|
+
* Safe to call while research is in progress — running sub-agents
|
|
25274
|
+
* pick up the new provider on their next LLM call.
|
|
25275
|
+
*/
|
|
25276
|
+
setProvider(provider) {
|
|
25277
|
+
this.provider = provider;
|
|
25278
|
+
}
|
|
25279
|
+
getProvider() {
|
|
25280
|
+
if (!this.provider) {
|
|
25281
|
+
throw new Error("Chat provider not configured - required for Research Desk");
|
|
25282
|
+
}
|
|
25283
|
+
return this.provider;
|
|
25284
|
+
}
|
|
25285
|
+
// ── phase: idle → briefing ────────────────────────────────────
|
|
25286
|
+
async startBrief(userQuery) {
|
|
25287
|
+
const query = userQuery.trim();
|
|
25288
|
+
if (!query) {
|
|
25289
|
+
logger$7.warn("Ignoring empty Research Desk query");
|
|
25290
|
+
return;
|
|
25291
|
+
}
|
|
25292
|
+
if (this.state.phase !== "idle") {
|
|
25293
|
+
logger$7.warn("Research already in progress, ignoring startBrief");
|
|
25294
|
+
return;
|
|
25295
|
+
}
|
|
25296
|
+
this.state = this.initialState();
|
|
25297
|
+
this.state.originalQuery = query;
|
|
25298
|
+
this.state.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
25299
|
+
this.setPhase("briefing");
|
|
25300
|
+
logger$7.info(`Brief started for query: ${query.slice(0, 120)}`);
|
|
25301
|
+
}
|
|
25302
|
+
// ── phase: briefing → planning ─────────────────────────────────
|
|
25303
|
+
confirmBrief() {
|
|
25304
|
+
if (this.state.phase !== "briefing") {
|
|
25305
|
+
logger$7.warn("Not in briefing phase, ignoring confirmBrief");
|
|
25306
|
+
return;
|
|
25307
|
+
}
|
|
25308
|
+
this.setPhase("planning");
|
|
25309
|
+
}
|
|
25310
|
+
// ── phase: planning → awaiting_approval ────────────────────────
|
|
25311
|
+
setObjectives(objectives) {
|
|
25312
|
+
if (this.state.phase !== "planning") {
|
|
25313
|
+
logger$7.warn("Not in planning phase, ignoring setObjectives");
|
|
25314
|
+
return;
|
|
25315
|
+
}
|
|
25316
|
+
this.state.objectives = objectives;
|
|
25317
|
+
this.state.threads = objectives.threads.slice(0, MAX_THREADS);
|
|
25318
|
+
this.setPhase("awaiting_approval");
|
|
25319
|
+
}
|
|
25320
|
+
/**
|
|
25321
|
+
* Parse a planning-phase LLM response into ResearchObjectives.
|
|
25322
|
+
* Expects JSON (optionally wrapped in ```json fences).
|
|
25323
|
+
* Returns true if parsing succeeded and objectives were set.
|
|
25324
|
+
*/
|
|
25325
|
+
parseAndSetObjectives(text) {
|
|
25326
|
+
if (this.state.phase !== "planning") return false;
|
|
25327
|
+
let json = text;
|
|
25328
|
+
const fenceMatch = text.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
|
|
25329
|
+
if (fenceMatch) {
|
|
25330
|
+
json = fenceMatch[1].trim();
|
|
25331
|
+
} else {
|
|
25332
|
+
const objMatch = text.match(/\{[\s\S]*\}/);
|
|
25333
|
+
if (objMatch) json = objMatch[0];
|
|
25334
|
+
}
|
|
25335
|
+
try {
|
|
25336
|
+
const parsed = JSON.parse(json);
|
|
25337
|
+
if (typeof parsed.researchQuestion !== "string" || !parsed.researchQuestion.trim()) {
|
|
25338
|
+
logger$7.warn("Missing researchQuestion in objectives JSON");
|
|
25339
|
+
return false;
|
|
25340
|
+
}
|
|
25341
|
+
if (!Array.isArray(parsed.threads) || parsed.threads.length === 0) {
|
|
25342
|
+
logger$7.warn("Missing or empty threads array in objectives JSON");
|
|
25343
|
+
return false;
|
|
25344
|
+
}
|
|
25345
|
+
const threads = parsed.threads.map((t, i) => {
|
|
25346
|
+
const obj = t;
|
|
25347
|
+
const question = String(obj.question || "").trim();
|
|
25348
|
+
const searchQueries = Array.isArray(obj.searchQueries) ? obj.searchQueries.map((q) => String(q).trim()).filter(Boolean) : [];
|
|
25349
|
+
const sourceBudget = typeof obj.sourceBudget === "number" && Number.isFinite(obj.sourceBudget) ? Math.max(1, Math.floor(obj.sourceBudget)) : 5;
|
|
25350
|
+
return {
|
|
25351
|
+
label: String(obj.label || `Thread ${i + 1}`),
|
|
25352
|
+
question,
|
|
25353
|
+
searchQueries,
|
|
25354
|
+
preferredDomains: Array.isArray(obj.preferredDomains) ? obj.preferredDomains.map((d) => String(d).trim()).filter(Boolean) : [],
|
|
25355
|
+
blockedDomains: Array.isArray(obj.blockedDomains) ? obj.blockedDomains.map((d) => String(d).trim()).filter(Boolean) : [],
|
|
25356
|
+
sourceBudget
|
|
25357
|
+
};
|
|
25358
|
+
}).filter((thread) => thread.question && thread.searchQueries.length > 0).slice(0, MAX_THREADS);
|
|
25359
|
+
if (threads.length === 0) {
|
|
25360
|
+
logger$7.warn("Objectives JSON did not contain any valid research threads");
|
|
25361
|
+
return false;
|
|
25362
|
+
}
|
|
25363
|
+
const objectives = {
|
|
25364
|
+
researchQuestion: String(parsed.researchQuestion).trim(),
|
|
25365
|
+
threads,
|
|
25366
|
+
audience: String(parsed.audience || "general").trim(),
|
|
25367
|
+
reportOutline: Array.isArray(parsed.reportOutline) ? parsed.reportOutline.map((s) => String(s).trim()).filter(Boolean) : [],
|
|
25368
|
+
totalSourceBudget: threads.reduce((sum, t) => sum + t.sourceBudget, 0)
|
|
25369
|
+
};
|
|
25370
|
+
this.setObjectives(objectives);
|
|
25371
|
+
logger$7.info(`Parsed ${objectives.threads.length} threads from objectives`);
|
|
25372
|
+
return true;
|
|
25373
|
+
} catch (err) {
|
|
25374
|
+
logger$7.warn("Failed to parse objectives JSON", err);
|
|
25375
|
+
return false;
|
|
25376
|
+
}
|
|
25377
|
+
}
|
|
25378
|
+
// ── phase: awaiting_approval → executing ───────────────────────
|
|
25379
|
+
approveObjectives(mode, includeTraces) {
|
|
25380
|
+
if (this.state.phase !== "awaiting_approval") {
|
|
25381
|
+
logger$7.warn("Not awaiting approval, ignoring approveObjectives");
|
|
25382
|
+
return;
|
|
25383
|
+
}
|
|
25384
|
+
if (mode) this.state.supervisionMode = mode;
|
|
25385
|
+
if (includeTraces !== void 0) this.state.includeTraces = includeTraces;
|
|
25386
|
+
this.setPhase("executing");
|
|
25387
|
+
}
|
|
25388
|
+
// ── phase: executing → synthesizing ────────────────────────────
|
|
25389
|
+
async executeSubAgents() {
|
|
25390
|
+
if (this.state.phase !== "executing" || !this.state.objectives) return;
|
|
25391
|
+
const tabMutex = new TabMutex();
|
|
25392
|
+
const results = await Promise.all(
|
|
25393
|
+
this.state.threads.map((thread) => {
|
|
25394
|
+
if (this.state.phase !== "executing") return null;
|
|
25395
|
+
return this.runSubAgent(thread, tabMutex).catch((err) => {
|
|
25396
|
+
logger$7.error(`Sub-agent "${thread.label}" failed`, err);
|
|
25397
|
+
return {
|
|
25398
|
+
threadLabel: thread.label,
|
|
25399
|
+
threadQuestion: thread.question,
|
|
25400
|
+
claims: [],
|
|
25401
|
+
discardedSources: [],
|
|
25402
|
+
executionSummary: `Failed: ${String(err)}`
|
|
25403
|
+
};
|
|
25404
|
+
});
|
|
25405
|
+
})
|
|
25406
|
+
);
|
|
25407
|
+
if (this.state.phase !== "executing") return;
|
|
25408
|
+
this.state.threadFindings = results.filter((f) => f !== null);
|
|
25409
|
+
this.setPhase("synthesizing");
|
|
25410
|
+
try {
|
|
25411
|
+
await this.synthesizeReport();
|
|
25412
|
+
} catch (err) {
|
|
25413
|
+
logger$7.error("Auto-synthesis failed", err);
|
|
25414
|
+
this.state.error = `Synthesis failed: ${String(err)}`;
|
|
25415
|
+
this.setPhase("delivered");
|
|
25416
|
+
}
|
|
25417
|
+
}
|
|
25418
|
+
// ── sub-agent loop ─────────────────────────────────────────────
|
|
25419
|
+
async runSubAgent(thread, tabMutex) {
|
|
25420
|
+
const trace = {
|
|
25421
|
+
threadLabel: thread.label,
|
|
25422
|
+
toolCalls: [],
|
|
25423
|
+
errors: [],
|
|
25424
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25425
|
+
finishedAt: ""
|
|
25426
|
+
};
|
|
25427
|
+
const tabId = this.tabManager.createTab();
|
|
25428
|
+
let sourcesConsumed = 0;
|
|
25429
|
+
if (tabId) this.tabManager.switchTab(tabId);
|
|
25430
|
+
const discardedSources = [];
|
|
25431
|
+
let transcript = "";
|
|
25432
|
+
try {
|
|
25433
|
+
const provider = this.getProvider();
|
|
25434
|
+
if (!provider.streamAgentQuery) {
|
|
25435
|
+
throw new Error("Provider does not support agent tool loops");
|
|
25436
|
+
}
|
|
25437
|
+
const systemPrompt = buildSubAgentSystemPrompt(thread);
|
|
25438
|
+
const userMessage = `Begin researching: ${thread.question}
|
|
25439
|
+
|
|
25440
|
+
Start by searching for: ${thread.searchQueries.join(" or ")}`;
|
|
25441
|
+
const actionCtx = {
|
|
25442
|
+
tabManager: this.tabManager,
|
|
25443
|
+
runtime: this.runtime,
|
|
25444
|
+
toolProfile: provider.agentToolProfile,
|
|
25445
|
+
tabId: tabId ?? void 0,
|
|
25446
|
+
_tabMutex: tabMutex
|
|
25447
|
+
};
|
|
25448
|
+
await provider.streamAgentQuery(
|
|
25449
|
+
systemPrompt,
|
|
25450
|
+
userMessage,
|
|
25451
|
+
AGENT_TOOLS,
|
|
25452
|
+
(chunk) => {
|
|
25453
|
+
transcript += chunk;
|
|
25454
|
+
},
|
|
25455
|
+
async (name, args) => {
|
|
25456
|
+
const t0 = Date.now();
|
|
25457
|
+
if (this.state.phase !== "executing") {
|
|
25458
|
+
const msg = "Research cancelled — stopping.";
|
|
25459
|
+
return msg;
|
|
25460
|
+
}
|
|
25461
|
+
if (name === "navigate" || name === "search") {
|
|
25462
|
+
sourcesConsumed++;
|
|
25463
|
+
if (sourcesConsumed > thread.sourceBudget) {
|
|
25464
|
+
const msg = `Source budget (${thread.sourceBudget}) exceeded. Summarize findings and stop.`;
|
|
25465
|
+
trace.toolCalls.push({
|
|
25466
|
+
tool: name,
|
|
25467
|
+
args,
|
|
25468
|
+
result: msg,
|
|
25469
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25470
|
+
durationMs: 0
|
|
25471
|
+
});
|
|
25472
|
+
return msg;
|
|
25473
|
+
}
|
|
25474
|
+
}
|
|
25475
|
+
try {
|
|
25476
|
+
const output = await executeAction(name, args, actionCtx);
|
|
25477
|
+
trace.toolCalls.push({
|
|
25478
|
+
tool: name,
|
|
25479
|
+
args,
|
|
25480
|
+
result: output,
|
|
25481
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25482
|
+
durationMs: Date.now() - t0
|
|
25483
|
+
});
|
|
25484
|
+
return output;
|
|
25485
|
+
} catch (err) {
|
|
25486
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
25487
|
+
if (msg.includes("paywall") || msg.includes("login required") || msg.includes("403")) {
|
|
25488
|
+
discardedSources.push({
|
|
25489
|
+
url: String(args.url || ""),
|
|
25490
|
+
title: String(args.url || "unknown"),
|
|
25491
|
+
reason: msg
|
|
25492
|
+
});
|
|
25493
|
+
}
|
|
25494
|
+
trace.errors.push({
|
|
25495
|
+
message: msg,
|
|
25496
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
25497
|
+
});
|
|
25498
|
+
trace.toolCalls.push({
|
|
25499
|
+
tool: name,
|
|
25500
|
+
args,
|
|
25501
|
+
result: `Error: ${msg}`,
|
|
25502
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25503
|
+
durationMs: Date.now() - t0
|
|
25504
|
+
});
|
|
25505
|
+
return `Error: ${msg}`;
|
|
25506
|
+
}
|
|
25507
|
+
},
|
|
25508
|
+
() => {
|
|
25509
|
+
}
|
|
25510
|
+
);
|
|
25511
|
+
} catch (err) {
|
|
25512
|
+
trace.errors.push({
|
|
25513
|
+
message: String(err),
|
|
25514
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
25515
|
+
});
|
|
25516
|
+
} finally {
|
|
25517
|
+
trace.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
25518
|
+
if (tabId) {
|
|
25519
|
+
try {
|
|
25520
|
+
this.tabManager.closeTab(tabId);
|
|
25521
|
+
} catch (err) {
|
|
25522
|
+
logger$7.warn(`Failed to close sub-agent tab ${tabId}`, err);
|
|
25523
|
+
}
|
|
25524
|
+
}
|
|
25525
|
+
}
|
|
25526
|
+
let claims = [];
|
|
25527
|
+
if (this.state.phase === "executing") {
|
|
25528
|
+
try {
|
|
25529
|
+
claims = await this.extractClaimsFromTranscript(thread, transcript);
|
|
25530
|
+
} catch (err) {
|
|
25531
|
+
logger$7.warn(`Claim extraction failed for "${thread.label}"`, err);
|
|
25532
|
+
}
|
|
25533
|
+
}
|
|
25534
|
+
if (this.state.phase === "executing" && this.state.includeTraces) {
|
|
25535
|
+
this.state.subAgentTraces.push(trace);
|
|
25536
|
+
}
|
|
25537
|
+
const pagesVisited = trace.toolCalls.filter(
|
|
25538
|
+
(t) => ["navigate", "read_page", "search"].includes(t.tool)
|
|
25539
|
+
).length;
|
|
25540
|
+
return {
|
|
25541
|
+
threadLabel: thread.label,
|
|
25542
|
+
threadQuestion: thread.question,
|
|
25543
|
+
claims,
|
|
25544
|
+
discardedSources,
|
|
25545
|
+
executionSummary: `Visited ${pagesVisited} pages (${trace.toolCalls.length} tool calls, ${sourcesConsumed} sources). ${claims.length} claims extracted. ${discardedSources.length} sources discarded.${trace.errors.length > 0 ? ` ${trace.errors.length} errors.` : ""}`
|
|
25546
|
+
};
|
|
25547
|
+
}
|
|
25548
|
+
/**
|
|
25549
|
+
* Extract structured claims from the sub-agent's research transcript.
|
|
25550
|
+
* Makes a follow-up LLM call asking it to parse claims with source URLs and quotes.
|
|
25551
|
+
*/
|
|
25552
|
+
async extractClaimsFromTranscript(thread, transcript) {
|
|
25553
|
+
if (!transcript.trim()) return [];
|
|
25554
|
+
const prompt = `You are a claim extractor. Given a research transcript, extract every factual claim along with its source URL and the exact supporting quote from the page.
|
|
25555
|
+
|
|
25556
|
+
CRITICAL RULES:
|
|
25557
|
+
- Only extract claims that are explicitly supported by a source URL AND a verbatim quote in the transcript.
|
|
25558
|
+
- If a claim has no source URL or no extracted quote, do NOT include it.
|
|
25559
|
+
- Do not fabricate claims. Only use what is explicitly stated in the transcript.
|
|
25560
|
+
- Return ONLY valid JSON — a JSON array of claim objects.
|
|
25561
|
+
|
|
25562
|
+
Each claim object must have these fields:
|
|
25563
|
+
- claim: the factual claim text
|
|
25564
|
+
- sourceUrl: the URL of the source page
|
|
25565
|
+
- sourceTitle: the title of the source page (or "Unknown" if not mentioned)
|
|
25566
|
+
- extractedQuote: the verbatim quote from the page that supports this claim
|
|
25567
|
+
- relevanceNote: a one-sentence note on why this claim matters to the research question
|
|
25568
|
+
|
|
25569
|
+
Return format:
|
|
25570
|
+
\`\`\`json
|
|
25571
|
+
[{"claim": "...", "sourceUrl": "...", "sourceTitle": "...", "extractedQuote": "...", "relevanceNote": "..."}]
|
|
25572
|
+
\`\`\`
|
|
25573
|
+
|
|
25574
|
+
RESEARCH QUESTION: ${thread.question}
|
|
25575
|
+
THREAD LABEL: ${thread.label}
|
|
25576
|
+
|
|
25577
|
+
TRANSCRIPT:
|
|
25578
|
+
${transcript.slice(0, 32e3)}`;
|
|
25579
|
+
let response = "";
|
|
25580
|
+
await this.getProvider().streamQuery(
|
|
25581
|
+
prompt,
|
|
25582
|
+
"Extract the claims.",
|
|
25583
|
+
(chunk) => {
|
|
25584
|
+
response += chunk;
|
|
25585
|
+
},
|
|
25586
|
+
() => {
|
|
25587
|
+
}
|
|
25588
|
+
);
|
|
25589
|
+
let json = response;
|
|
25590
|
+
const fenceMatch = response.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
|
|
25591
|
+
if (fenceMatch) json = fenceMatch[1].trim();
|
|
25592
|
+
else {
|
|
25593
|
+
const arrMatch = response.match(/\[[\s\S]*\]/);
|
|
25594
|
+
if (arrMatch) json = arrMatch[0];
|
|
25595
|
+
}
|
|
25596
|
+
try {
|
|
25597
|
+
const raw = JSON.parse(json);
|
|
25598
|
+
if (!Array.isArray(raw)) return [];
|
|
25599
|
+
return raw.map((item) => {
|
|
25600
|
+
const c = item;
|
|
25601
|
+
return {
|
|
25602
|
+
claim: String(c.claim || "").trim(),
|
|
25603
|
+
sourceUrl: String(c.sourceUrl || "").trim(),
|
|
25604
|
+
sourceTitle: String(c.sourceTitle || c.sourceUrl || "Unknown").trim(),
|
|
25605
|
+
extractedQuote: String(c.extractedQuote || "").trim(),
|
|
25606
|
+
extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25607
|
+
threadLabel: thread.label,
|
|
25608
|
+
relevanceNote: String(c.relevanceNote || "").trim()
|
|
25609
|
+
};
|
|
25610
|
+
}).filter(
|
|
25611
|
+
(claim) => claim.claim && claim.sourceUrl && claim.extractedQuote
|
|
25612
|
+
);
|
|
25613
|
+
} catch {
|
|
25614
|
+
logger$7.warn(`Failed to parse claims JSON for "${thread.label}"`);
|
|
25615
|
+
return [];
|
|
25616
|
+
}
|
|
25617
|
+
}
|
|
25618
|
+
// ── phase: synthesizing → delivered ───────────────────────────
|
|
25619
|
+
async synthesizeReport() {
|
|
25620
|
+
if (this.state.phase !== "synthesizing" || !this.state.objectives) {
|
|
25621
|
+
return null;
|
|
25622
|
+
}
|
|
25623
|
+
const objectives = this.state.objectives;
|
|
25624
|
+
const findings = this.state.threadFindings;
|
|
25625
|
+
const synthesisPrompt = buildSynthesisPrompt(objectives, findings);
|
|
25626
|
+
let response = "";
|
|
25627
|
+
await this.getProvider().streamQuery(
|
|
25628
|
+
synthesisPrompt,
|
|
25629
|
+
"Return ONLY the JSON object now.",
|
|
25630
|
+
(chunk) => {
|
|
25631
|
+
response += chunk;
|
|
25632
|
+
},
|
|
25633
|
+
() => {
|
|
25634
|
+
}
|
|
25635
|
+
);
|
|
25636
|
+
const report = this.parseReportFromJson(response, objectives);
|
|
25637
|
+
this.setReport(report);
|
|
25638
|
+
this.setPhase("delivered");
|
|
25639
|
+
return report;
|
|
25640
|
+
}
|
|
25641
|
+
/**
|
|
25642
|
+
* Parse the LLM's JSON synthesis response into a structured ResearchReport.
|
|
25643
|
+
* Handles both bare JSON and JSON wrapped in markdown fences.
|
|
25644
|
+
*/
|
|
25645
|
+
parseReportFromJson(text, objectives) {
|
|
25646
|
+
let json = text.trim();
|
|
25647
|
+
const fenceMatch = json.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
|
|
25648
|
+
if (fenceMatch) json = fenceMatch[1].trim();
|
|
25649
|
+
const objMatch = json.match(/\{[\s\S]*\}/);
|
|
25650
|
+
if (objMatch) json = objMatch[0];
|
|
25651
|
+
try {
|
|
25652
|
+
const parsed = JSON.parse(json);
|
|
25653
|
+
return {
|
|
25654
|
+
title: String(parsed.title || objectives.researchQuestion).trim(),
|
|
25655
|
+
executiveSummary: String(parsed.executiveSummary || "").trim(),
|
|
25656
|
+
findingsByThread: Array.isArray(parsed.findingsByThread) ? parsed.findingsByThread.map((s) => {
|
|
25657
|
+
const obj = s;
|
|
25658
|
+
return {
|
|
25659
|
+
threadLabel: String(obj.threadLabel || "").trim(),
|
|
25660
|
+
content: String(obj.content || "").trim()
|
|
25661
|
+
};
|
|
25662
|
+
}) : [],
|
|
25663
|
+
contradictions: Array.isArray(parsed.contradictions) ? parsed.contradictions.map((c) => {
|
|
25664
|
+
const obj = c;
|
|
25665
|
+
const sourceA = obj.sourceA ?? {};
|
|
25666
|
+
const sourceB = obj.sourceB ?? {};
|
|
25667
|
+
return {
|
|
25668
|
+
claim: String(obj.claim || "").trim(),
|
|
25669
|
+
sourceA: {
|
|
25670
|
+
url: String(sourceA.url || "").trim(),
|
|
25671
|
+
claim: String(sourceA.claim || "").trim()
|
|
25672
|
+
},
|
|
25673
|
+
sourceB: {
|
|
25674
|
+
url: String(sourceB.url || "").trim(),
|
|
25675
|
+
claim: String(sourceB.claim || "").trim()
|
|
25676
|
+
},
|
|
25677
|
+
resolution: String(obj.resolution || "").trim()
|
|
25678
|
+
};
|
|
25679
|
+
}).filter(
|
|
25680
|
+
(c) => c.claim && c.sourceA.url && c.sourceB.url && c.resolution
|
|
25681
|
+
) : [],
|
|
25682
|
+
gaps: Array.isArray(parsed.gaps) ? parsed.gaps.map((g) => String(g).trim()).filter(Boolean) : [],
|
|
25683
|
+
sourceIndex: Array.isArray(parsed.sourceIndex) ? parsed.sourceIndex.map((s) => {
|
|
25684
|
+
const obj = s;
|
|
25685
|
+
return {
|
|
25686
|
+
index: typeof obj.index === "number" ? obj.index : parseInt(String(obj.index), 10) || 0,
|
|
25687
|
+
url: String(obj.url || "").trim(),
|
|
25688
|
+
title: String(obj.title || "").trim(),
|
|
25689
|
+
accessedAt: String(obj.accessedAt || "").trim(),
|
|
25690
|
+
supportingQuote: String(obj.supportingQuote || "").trim()
|
|
25691
|
+
};
|
|
25692
|
+
}).filter((s) => s.url && s.title) : [],
|
|
25693
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25694
|
+
objectives
|
|
25695
|
+
};
|
|
25696
|
+
} catch (err) {
|
|
25697
|
+
logger$7.warn("Failed to parse synthesis JSON, using minimal report", err);
|
|
25698
|
+
return {
|
|
25699
|
+
title: objectives.researchQuestion,
|
|
25700
|
+
executiveSummary: `Report generation failed: ${String(err)}`,
|
|
25701
|
+
findingsByThread: [],
|
|
25702
|
+
contradictions: [],
|
|
25703
|
+
gaps: ["Report generation failed — JSON parsing error"],
|
|
25704
|
+
sourceIndex: [],
|
|
25705
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25706
|
+
objectives
|
|
25707
|
+
};
|
|
25708
|
+
}
|
|
25709
|
+
}
|
|
25710
|
+
// ── report management ──────────────────────────────────────────
|
|
25711
|
+
setReport(report) {
|
|
25712
|
+
this.state.report = report;
|
|
25713
|
+
this.emit();
|
|
25714
|
+
}
|
|
25715
|
+
// ── reset ──────────────────────────────────────────────────────
|
|
25716
|
+
reset() {
|
|
25717
|
+
this.state = this.initialState();
|
|
25718
|
+
this.emit();
|
|
25719
|
+
}
|
|
25720
|
+
}
|
|
25721
|
+
function registerVaultHandlers() {
|
|
25722
|
+
electron.ipcMain.handle(Channels.VAULT_LIST, (event) => {
|
|
25723
|
+
assertTrustedIpcSender(event);
|
|
25724
|
+
return listEntries$1();
|
|
25725
|
+
});
|
|
25726
|
+
electron.ipcMain.handle(
|
|
25727
|
+
Channels.VAULT_ADD,
|
|
25728
|
+
(event, entry) => {
|
|
25729
|
+
assertTrustedIpcSender(event);
|
|
25730
|
+
if (!entry || typeof entry !== "object") {
|
|
25731
|
+
throw new Error("Invalid vault entry");
|
|
24674
25732
|
}
|
|
24675
25733
|
assertString(entry.label, "label");
|
|
24676
25734
|
assertString(entry.domainPattern, "domainPattern");
|
|
@@ -25802,19 +26860,22 @@ function getSafeBookmarkExportName(name) {
|
|
|
25802
26860
|
return safeName || "folder";
|
|
25803
26861
|
}
|
|
25804
26862
|
function registerBookmarkHandlers() {
|
|
25805
|
-
electron.ipcMain.handle(Channels.BOOKMARKS_GET, () => {
|
|
26863
|
+
electron.ipcMain.handle(Channels.BOOKMARKS_GET, (event) => {
|
|
26864
|
+
assertTrustedIpcSender(event);
|
|
25806
26865
|
return getState();
|
|
25807
26866
|
});
|
|
25808
26867
|
electron.ipcMain.handle(
|
|
25809
26868
|
Channels.FOLDER_CREATE,
|
|
25810
|
-
(
|
|
26869
|
+
(event, name, summary) => {
|
|
26870
|
+
assertTrustedIpcSender(event);
|
|
25811
26871
|
trackBookmarkAction("folder_create");
|
|
25812
26872
|
return createFolderWithSummary(name, summary);
|
|
25813
26873
|
}
|
|
25814
26874
|
);
|
|
25815
26875
|
electron.ipcMain.handle(
|
|
25816
26876
|
Channels.BOOKMARK_SAVE,
|
|
25817
|
-
(
|
|
26877
|
+
(event, url, title, folderId, note, intent, expectedContent, keyFields, agentHints) => {
|
|
26878
|
+
assertTrustedIpcSender(event);
|
|
25818
26879
|
trackBookmarkAction("save");
|
|
25819
26880
|
const result = saveBookmarkWithPolicy(url, title, folderId, note, {
|
|
25820
26881
|
onDuplicate: "update",
|
|
@@ -25835,12 +26896,14 @@ function registerBookmarkHandlers() {
|
|
|
25835
26896
|
);
|
|
25836
26897
|
electron.ipcMain.handle(
|
|
25837
26898
|
Channels.BOOKMARK_UPDATE,
|
|
25838
|
-
(
|
|
26899
|
+
(event, id, updates) => {
|
|
26900
|
+
assertTrustedIpcSender(event);
|
|
25839
26901
|
trackBookmarkAction("save");
|
|
25840
26902
|
return updateBookmark(id, updates);
|
|
25841
26903
|
}
|
|
25842
26904
|
);
|
|
25843
|
-
electron.ipcMain.handle(Channels.BOOKMARK_REMOVE, (
|
|
26905
|
+
electron.ipcMain.handle(Channels.BOOKMARK_REMOVE, (event, id) => {
|
|
26906
|
+
assertTrustedIpcSender(event);
|
|
25844
26907
|
trackBookmarkAction("remove");
|
|
25845
26908
|
return removeBookmark(id);
|
|
25846
26909
|
});
|
|
@@ -25933,22 +26996,26 @@ function registerBookmarkHandlers() {
|
|
|
25933
26996
|
trackBookmarkAction("import");
|
|
25934
26997
|
return importBookmarksFromJson(content);
|
|
25935
26998
|
});
|
|
25936
|
-
electron.ipcMain.handle(Channels.FOLDER_REMOVE, (
|
|
26999
|
+
electron.ipcMain.handle(Channels.FOLDER_REMOVE, (event, id, deleteContents) => {
|
|
27000
|
+
assertTrustedIpcSender(event);
|
|
25937
27001
|
trackBookmarkAction("folder_remove");
|
|
25938
27002
|
return removeFolder(id, deleteContents ?? false);
|
|
25939
27003
|
});
|
|
25940
27004
|
electron.ipcMain.handle(
|
|
25941
27005
|
Channels.FOLDER_RENAME,
|
|
25942
|
-
(
|
|
27006
|
+
(event, id, newName, summary) => {
|
|
27007
|
+
assertTrustedIpcSender(event);
|
|
25943
27008
|
return renameFolder(id, newName, summary);
|
|
25944
27009
|
}
|
|
25945
27010
|
);
|
|
25946
27011
|
}
|
|
25947
27012
|
function registerHistoryHandlers() {
|
|
25948
|
-
electron.ipcMain.handle(Channels.HISTORY_GET, () => {
|
|
27013
|
+
electron.ipcMain.handle(Channels.HISTORY_GET, (event) => {
|
|
27014
|
+
assertTrustedIpcSender(event);
|
|
25949
27015
|
return getState$1();
|
|
25950
27016
|
});
|
|
25951
|
-
electron.ipcMain.handle(Channels.HISTORY_SEARCH, (
|
|
27017
|
+
electron.ipcMain.handle(Channels.HISTORY_SEARCH, (event, query) => {
|
|
27018
|
+
assertTrustedIpcSender(event);
|
|
25952
27019
|
return search(query);
|
|
25953
27020
|
});
|
|
25954
27021
|
electron.ipcMain.handle(Channels.HISTORY_CLEAR, (event) => {
|
|
@@ -26073,10 +27140,12 @@ function registerPremiumHandlers(tabManager, sendToRendererViews) {
|
|
|
26073
27140
|
void handleUrl(currentUrl);
|
|
26074
27141
|
}
|
|
26075
27142
|
};
|
|
26076
|
-
electron.ipcMain.handle(Channels.PREMIUM_GET_STATE, () => {
|
|
27143
|
+
electron.ipcMain.handle(Channels.PREMIUM_GET_STATE, (event) => {
|
|
27144
|
+
assertTrustedIpcSender(event);
|
|
26077
27145
|
return getPremiumState();
|
|
26078
27146
|
});
|
|
26079
|
-
electron.ipcMain.handle(Channels.PREMIUM_ACTIVATION_START, async (
|
|
27147
|
+
electron.ipcMain.handle(Channels.PREMIUM_ACTIVATION_START, async (event, email) => {
|
|
27148
|
+
assertTrustedIpcSender(event);
|
|
26080
27149
|
assertString(email, "email");
|
|
26081
27150
|
if (!isValidEmail(email)) {
|
|
26082
27151
|
return errorResult("Invalid email format");
|
|
@@ -26090,7 +27159,8 @@ function registerPremiumHandlers(tabManager, sendToRendererViews) {
|
|
|
26090
27159
|
});
|
|
26091
27160
|
electron.ipcMain.handle(
|
|
26092
27161
|
Channels.PREMIUM_ACTIVATION_VERIFY,
|
|
26093
|
-
async (
|
|
27162
|
+
async (event, email, code, challengeToken) => {
|
|
27163
|
+
assertTrustedIpcSender(event);
|
|
26094
27164
|
assertString(email, "email");
|
|
26095
27165
|
assertString(code, "code");
|
|
26096
27166
|
assertString(challengeToken, "challengeToken");
|
|
@@ -26112,7 +27182,8 @@ function registerPremiumHandlers(tabManager, sendToRendererViews) {
|
|
|
26112
27182
|
return result;
|
|
26113
27183
|
}
|
|
26114
27184
|
);
|
|
26115
|
-
electron.ipcMain.handle(Channels.PREMIUM_CHECKOUT, async (
|
|
27185
|
+
electron.ipcMain.handle(Channels.PREMIUM_CHECKOUT, async (event, email) => {
|
|
27186
|
+
assertTrustedIpcSender(event);
|
|
26116
27187
|
trackPremiumFunnel("checkout_clicked");
|
|
26117
27188
|
const result = await getCheckoutUrl(email);
|
|
26118
27189
|
if (result.ok && result.url) {
|
|
@@ -26121,19 +27192,22 @@ function registerPremiumHandlers(tabManager, sendToRendererViews) {
|
|
|
26121
27192
|
}
|
|
26122
27193
|
return result;
|
|
26123
27194
|
});
|
|
26124
|
-
electron.ipcMain.handle(Channels.PREMIUM_RESET, () => {
|
|
27195
|
+
electron.ipcMain.handle(Channels.PREMIUM_RESET, (event) => {
|
|
27196
|
+
assertTrustedIpcSender(event);
|
|
26125
27197
|
trackPremiumFunnel("reset");
|
|
26126
27198
|
const state2 = resetPremium();
|
|
26127
27199
|
sendToRendererViews(Channels.PREMIUM_UPDATE, state2);
|
|
26128
27200
|
return state2;
|
|
26129
27201
|
});
|
|
26130
|
-
electron.ipcMain.handle(Channels.PREMIUM_TRACK_CONTEXT, (
|
|
27202
|
+
electron.ipcMain.handle(Channels.PREMIUM_TRACK_CONTEXT, (event, step) => {
|
|
27203
|
+
assertTrustedIpcSender(event);
|
|
26131
27204
|
assertString(step, "step");
|
|
26132
27205
|
if (PREMIUM_TRACKABLE_STEPS.includes(step)) {
|
|
26133
27206
|
trackPremiumFunnel(step);
|
|
26134
27207
|
}
|
|
26135
27208
|
});
|
|
26136
|
-
electron.ipcMain.handle(Channels.PREMIUM_PORTAL, async () => {
|
|
27209
|
+
electron.ipcMain.handle(Channels.PREMIUM_PORTAL, async (event) => {
|
|
27210
|
+
assertTrustedIpcSender(event);
|
|
26137
27211
|
trackPremiumFunnel("portal_opened");
|
|
26138
27212
|
const result = await getPortalUrl();
|
|
26139
27213
|
if (result.ok && result.url) {
|
|
@@ -26143,18 +27217,22 @@ function registerPremiumHandlers(tabManager, sendToRendererViews) {
|
|
|
26143
27217
|
});
|
|
26144
27218
|
}
|
|
26145
27219
|
function registerSessionHandlers(tabManager) {
|
|
26146
|
-
electron.ipcMain.handle(Channels.SESSION_LIST, () => {
|
|
27220
|
+
electron.ipcMain.handle(Channels.SESSION_LIST, (event) => {
|
|
27221
|
+
assertTrustedIpcSender(event);
|
|
26147
27222
|
return listNamedSessions();
|
|
26148
27223
|
});
|
|
26149
|
-
electron.ipcMain.handle(Channels.SESSION_SAVE, async (
|
|
27224
|
+
electron.ipcMain.handle(Channels.SESSION_SAVE, async (event, name) => {
|
|
27225
|
+
assertTrustedIpcSender(event);
|
|
26150
27226
|
assertString(name, "name");
|
|
26151
27227
|
return await saveNamedSession(tabManager, name);
|
|
26152
27228
|
});
|
|
26153
|
-
electron.ipcMain.handle(Channels.SESSION_LOAD, async (
|
|
27229
|
+
electron.ipcMain.handle(Channels.SESSION_LOAD, async (event, name) => {
|
|
27230
|
+
assertTrustedIpcSender(event);
|
|
26154
27231
|
assertString(name, "name");
|
|
26155
27232
|
return await loadNamedSession(tabManager, name);
|
|
26156
27233
|
});
|
|
26157
|
-
electron.ipcMain.handle(Channels.SESSION_DELETE, (
|
|
27234
|
+
electron.ipcMain.handle(Channels.SESSION_DELETE, (event, name) => {
|
|
27235
|
+
assertTrustedIpcSender(event);
|
|
26158
27236
|
assertString(name, "name");
|
|
26159
27237
|
return deleteNamedSession(name);
|
|
26160
27238
|
});
|
|
@@ -26312,6 +27390,10 @@ function snapshot() {
|
|
|
26312
27390
|
function emit() {
|
|
26313
27391
|
broadcaster?.(Channels.PERMISSIONS_GET, snapshot());
|
|
26314
27392
|
}
|
|
27393
|
+
function getDecision(origin, permission) {
|
|
27394
|
+
const existing = records.find((r) => r.origin === origin && r.permission === permission);
|
|
27395
|
+
return existing?.decision ?? sessionDecisions.get(key(origin, permission)) ?? null;
|
|
27396
|
+
}
|
|
26315
27397
|
function save(origin, permission, decision) {
|
|
26316
27398
|
const k = key(origin, permission);
|
|
26317
27399
|
const existing = records.find((r) => key(r.origin, r.permission) === k);
|
|
@@ -26344,6 +27426,12 @@ function setPermissionBroadcaster(fn) {
|
|
|
26344
27426
|
broadcaster = fn;
|
|
26345
27427
|
}
|
|
26346
27428
|
function installPermissionHandler() {
|
|
27429
|
+
electron.session.defaultSession.setPermissionCheckHandler((webContents, permission, requestingOrigin) => {
|
|
27430
|
+
if (!ALLOWED_PERMISSION_TYPES.has(permission)) return false;
|
|
27431
|
+
const origin = parseOrigin(requestingOrigin || webContents.getURL());
|
|
27432
|
+
if (!origin) return false;
|
|
27433
|
+
return getDecision(origin, permission) === "allow";
|
|
27434
|
+
});
|
|
26347
27435
|
electron.session.defaultSession.setPermissionRequestHandler((webContents, permission, callback, details) => {
|
|
26348
27436
|
if (!ALLOWED_PERMISSION_TYPES.has(permission)) {
|
|
26349
27437
|
callback(false);
|
|
@@ -26355,19 +27443,21 @@ function installPermissionHandler() {
|
|
|
26355
27443
|
return;
|
|
26356
27444
|
}
|
|
26357
27445
|
const k = key(origin, permission);
|
|
26358
|
-
const
|
|
26359
|
-
if (
|
|
26360
|
-
callback(
|
|
26361
|
-
return;
|
|
26362
|
-
}
|
|
26363
|
-
const sessionDecision = sessionDecisions.get(k);
|
|
26364
|
-
if (sessionDecision) {
|
|
26365
|
-
callback(sessionDecision === "allow");
|
|
27446
|
+
const decision = getDecision(origin, permission);
|
|
27447
|
+
if (decision) {
|
|
27448
|
+
callback(decision === "allow");
|
|
26366
27449
|
return;
|
|
26367
27450
|
}
|
|
26368
27451
|
const result = electron.dialog.showMessageBoxSync({
|
|
26369
27452
|
type: "question",
|
|
26370
|
-
buttons: [
|
|
27453
|
+
buttons: [
|
|
27454
|
+
"Deny Once",
|
|
27455
|
+
"Deny Until Quit",
|
|
27456
|
+
"Always Deny",
|
|
27457
|
+
"Allow Once",
|
|
27458
|
+
"Allow Until Quit",
|
|
27459
|
+
"Always Allow"
|
|
27460
|
+
],
|
|
26371
27461
|
defaultId: 0,
|
|
26372
27462
|
cancelId: 0,
|
|
26373
27463
|
title: "Site permission request",
|
|
@@ -26375,20 +27465,29 @@ function installPermissionHandler() {
|
|
|
26375
27465
|
detail: "Temporary choices are safer for camera, microphone, location, and clipboard access. Persistent choices can be cleared in Settings > Privacy."
|
|
26376
27466
|
});
|
|
26377
27467
|
if (result === 1) {
|
|
26378
|
-
|
|
27468
|
+
sessionDecisions.set(k, "deny");
|
|
27469
|
+
callback(false);
|
|
26379
27470
|
return;
|
|
26380
27471
|
}
|
|
26381
27472
|
if (result === 2) {
|
|
27473
|
+
save(origin, permission, "deny");
|
|
27474
|
+
callback(false);
|
|
27475
|
+
return;
|
|
27476
|
+
}
|
|
27477
|
+
if (result === 3) {
|
|
27478
|
+
callback(true);
|
|
27479
|
+
return;
|
|
27480
|
+
}
|
|
27481
|
+
if (result === 4) {
|
|
26382
27482
|
sessionDecisions.set(k, "allow");
|
|
26383
27483
|
callback(true);
|
|
26384
27484
|
return;
|
|
26385
27485
|
}
|
|
26386
|
-
if (result ===
|
|
27486
|
+
if (result === 5) {
|
|
26387
27487
|
save(origin, permission, "allow");
|
|
26388
27488
|
callback(true);
|
|
26389
27489
|
return;
|
|
26390
27490
|
}
|
|
26391
|
-
save(origin, permission, "deny");
|
|
26392
27491
|
callback(false);
|
|
26393
27492
|
});
|
|
26394
27493
|
}
|
|
@@ -26483,6 +27582,18 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
26483
27582
|
const requireTrusted = (event) => {
|
|
26484
27583
|
assertTrustedIpcSender(event);
|
|
26485
27584
|
};
|
|
27585
|
+
let researchOrchestrator = null;
|
|
27586
|
+
const getResearchOrchestrator = () => {
|
|
27587
|
+
if (!researchOrchestrator) {
|
|
27588
|
+
const settings2 = loadSettings();
|
|
27589
|
+
const provider = settings2.chatProvider ? createProvider(settings2.chatProvider) : null;
|
|
27590
|
+
researchOrchestrator = new ResearchOrchestrator(provider, tabManager, runtime2);
|
|
27591
|
+
researchOrchestrator.setUpdateListener((state2) => {
|
|
27592
|
+
sendToRendererViews(Channels.RESEARCH_STATE_UPDATE, state2);
|
|
27593
|
+
});
|
|
27594
|
+
}
|
|
27595
|
+
return researchOrchestrator;
|
|
27596
|
+
};
|
|
26486
27597
|
electron.ipcMain.handle(Channels.OPEN_PRIVATE_WINDOW, (event) => {
|
|
26487
27598
|
requireTrusted(event);
|
|
26488
27599
|
createPrivateWindow();
|
|
@@ -26491,7 +27602,10 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
26491
27602
|
requireTrusted(event);
|
|
26492
27603
|
createSecondaryWindow();
|
|
26493
27604
|
});
|
|
26494
|
-
electron.ipcMain.handle(Channels.IS_PRIVATE_MODE, () =>
|
|
27605
|
+
electron.ipcMain.handle(Channels.IS_PRIVATE_MODE, (event) => {
|
|
27606
|
+
requireTrusted(event);
|
|
27607
|
+
return false;
|
|
27608
|
+
});
|
|
26495
27609
|
let sidebarResizeRecoveryTimer = null;
|
|
26496
27610
|
let sidebarResizeActive = false;
|
|
26497
27611
|
let runtimeUpdateTimer = null;
|
|
@@ -26572,37 +27686,45 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
26572
27686
|
onAIStreamIdle(() => {
|
|
26573
27687
|
sendToRendererViews(Channels.AI_STREAM_IDLE);
|
|
26574
27688
|
});
|
|
26575
|
-
electron.ipcMain.handle(Channels.TAB_CREATE, (
|
|
27689
|
+
electron.ipcMain.handle(Channels.TAB_CREATE, (event, url) => {
|
|
27690
|
+
requireTrusted(event);
|
|
26576
27691
|
const id = tabManager.createTab(url || loadSettings().defaultUrl);
|
|
26577
27692
|
layoutViews(windowState);
|
|
26578
27693
|
return id;
|
|
26579
27694
|
});
|
|
26580
|
-
electron.ipcMain.handle(Channels.TAB_CLOSE, (
|
|
27695
|
+
electron.ipcMain.handle(Channels.TAB_CLOSE, (event, id) => {
|
|
27696
|
+
requireTrusted(event);
|
|
26581
27697
|
tabManager.closeTab(id);
|
|
26582
27698
|
layoutViews(windowState);
|
|
26583
27699
|
});
|
|
26584
|
-
electron.ipcMain.handle(Channels.TAB_SWITCH, (
|
|
27700
|
+
electron.ipcMain.handle(Channels.TAB_SWITCH, (event, id) => {
|
|
27701
|
+
requireTrusted(event);
|
|
26585
27702
|
tabManager.switchTab(id);
|
|
26586
27703
|
layoutViews(windowState);
|
|
26587
27704
|
});
|
|
26588
27705
|
electron.ipcMain.handle(
|
|
26589
27706
|
Channels.TAB_NAVIGATE,
|
|
26590
|
-
(
|
|
27707
|
+
(event, id, url, postBody) => {
|
|
27708
|
+
requireTrusted(event);
|
|
26591
27709
|
assertString(id, "tabId");
|
|
26592
27710
|
assertString(url, "url");
|
|
26593
27711
|
return tabManager.navigateTab(id, url, postBody);
|
|
26594
27712
|
}
|
|
26595
27713
|
);
|
|
26596
|
-
electron.ipcMain.handle(Channels.TAB_BACK, (
|
|
27714
|
+
electron.ipcMain.handle(Channels.TAB_BACK, (event, id) => {
|
|
27715
|
+
requireTrusted(event);
|
|
26597
27716
|
tabManager.goBack(id);
|
|
26598
27717
|
});
|
|
26599
|
-
electron.ipcMain.handle(Channels.TAB_FORWARD, (
|
|
27718
|
+
electron.ipcMain.handle(Channels.TAB_FORWARD, (event, id) => {
|
|
27719
|
+
requireTrusted(event);
|
|
26600
27720
|
tabManager.goForward(id);
|
|
26601
27721
|
});
|
|
26602
|
-
electron.ipcMain.handle(Channels.TAB_RELOAD, (
|
|
27722
|
+
electron.ipcMain.handle(Channels.TAB_RELOAD, (event, id) => {
|
|
27723
|
+
requireTrusted(event);
|
|
26603
27724
|
tabManager.reloadTab(id);
|
|
26604
27725
|
});
|
|
26605
|
-
electron.ipcMain.handle(Channels.TAB_TOGGLE_AD_BLOCK, (
|
|
27726
|
+
electron.ipcMain.handle(Channels.TAB_TOGGLE_AD_BLOCK, (event, id) => {
|
|
27727
|
+
requireTrusted(event);
|
|
26606
27728
|
assertString(id, "id");
|
|
26607
27729
|
const tab = tabManager.getTab(id);
|
|
26608
27730
|
if (!tab) return null;
|
|
@@ -26610,87 +27732,108 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
26610
27732
|
tab.setAdBlockingEnabled(newState);
|
|
26611
27733
|
return newState;
|
|
26612
27734
|
});
|
|
26613
|
-
electron.ipcMain.handle(Channels.TAB_ZOOM_IN, (
|
|
27735
|
+
electron.ipcMain.handle(Channels.TAB_ZOOM_IN, (event, id) => {
|
|
27736
|
+
requireTrusted(event);
|
|
26614
27737
|
assertString(id, "id");
|
|
26615
27738
|
tabManager.zoomIn(id);
|
|
26616
27739
|
});
|
|
26617
|
-
electron.ipcMain.handle(Channels.TAB_ZOOM_OUT, (
|
|
27740
|
+
electron.ipcMain.handle(Channels.TAB_ZOOM_OUT, (event, id) => {
|
|
27741
|
+
requireTrusted(event);
|
|
26618
27742
|
assertString(id, "id");
|
|
26619
27743
|
tabManager.zoomOut(id);
|
|
26620
27744
|
});
|
|
26621
|
-
electron.ipcMain.handle(Channels.TAB_ZOOM_RESET, (
|
|
27745
|
+
electron.ipcMain.handle(Channels.TAB_ZOOM_RESET, (event, id) => {
|
|
27746
|
+
requireTrusted(event);
|
|
26622
27747
|
assertString(id, "id");
|
|
26623
27748
|
tabManager.zoomReset(id);
|
|
26624
27749
|
});
|
|
26625
|
-
electron.ipcMain.handle(Channels.TAB_REOPEN_CLOSED, () => {
|
|
27750
|
+
electron.ipcMain.handle(Channels.TAB_REOPEN_CLOSED, (event) => {
|
|
27751
|
+
requireTrusted(event);
|
|
26626
27752
|
const id = tabManager.reopenClosedTab();
|
|
26627
27753
|
if (id) layoutViews(windowState);
|
|
26628
27754
|
return id;
|
|
26629
27755
|
});
|
|
26630
|
-
electron.ipcMain.handle(Channels.TAB_DUPLICATE, (
|
|
27756
|
+
electron.ipcMain.handle(Channels.TAB_DUPLICATE, (event, id) => {
|
|
27757
|
+
requireTrusted(event);
|
|
26631
27758
|
assertString(id, "id");
|
|
26632
27759
|
const newId = tabManager.duplicateTab(id);
|
|
26633
27760
|
if (newId) layoutViews(windowState);
|
|
26634
27761
|
return newId;
|
|
26635
27762
|
});
|
|
26636
|
-
electron.ipcMain.handle(Channels.TAB_PIN, (
|
|
27763
|
+
electron.ipcMain.handle(Channels.TAB_PIN, (event, id) => {
|
|
27764
|
+
requireTrusted(event);
|
|
26637
27765
|
assertString(id, "id");
|
|
26638
27766
|
tabManager.pinTab(id);
|
|
26639
27767
|
});
|
|
26640
|
-
electron.ipcMain.handle(Channels.TAB_UNPIN, (
|
|
27768
|
+
electron.ipcMain.handle(Channels.TAB_UNPIN, (event, id) => {
|
|
27769
|
+
requireTrusted(event);
|
|
26641
27770
|
assertString(id, "id");
|
|
26642
27771
|
tabManager.unpinTab(id);
|
|
26643
27772
|
});
|
|
26644
|
-
electron.ipcMain.handle(Channels.TAB_GROUP_CREATE, (
|
|
27773
|
+
electron.ipcMain.handle(Channels.TAB_GROUP_CREATE, (event, id) => {
|
|
27774
|
+
requireTrusted(event);
|
|
26645
27775
|
assertString(id, "id");
|
|
26646
27776
|
return tabManager.createGroupFromTab(id);
|
|
26647
27777
|
});
|
|
26648
|
-
electron.ipcMain.handle(Channels.TAB_GROUP_ADD_TAB, (
|
|
27778
|
+
electron.ipcMain.handle(Channels.TAB_GROUP_ADD_TAB, (event, id, groupId) => {
|
|
27779
|
+
requireTrusted(event);
|
|
26649
27780
|
assertString(id, "id");
|
|
26650
27781
|
assertString(groupId, "groupId");
|
|
26651
27782
|
tabManager.assignTabToGroup(id, groupId);
|
|
26652
27783
|
});
|
|
26653
|
-
electron.ipcMain.handle(Channels.TAB_GROUP_REMOVE_TAB, (
|
|
27784
|
+
electron.ipcMain.handle(Channels.TAB_GROUP_REMOVE_TAB, (event, id) => {
|
|
27785
|
+
requireTrusted(event);
|
|
26654
27786
|
assertString(id, "id");
|
|
26655
27787
|
tabManager.removeTabFromGroup(id);
|
|
26656
27788
|
});
|
|
26657
|
-
electron.ipcMain.handle(Channels.TAB_GROUP_TOGGLE_COLLAPSED, (
|
|
27789
|
+
electron.ipcMain.handle(Channels.TAB_GROUP_TOGGLE_COLLAPSED, (event, groupId) => {
|
|
27790
|
+
requireTrusted(event);
|
|
26658
27791
|
assertString(groupId, "groupId");
|
|
26659
27792
|
return tabManager.toggleGroupCollapsed(groupId);
|
|
26660
27793
|
});
|
|
26661
27794
|
electron.ipcMain.handle(
|
|
26662
27795
|
Channels.TAB_GROUP_SET_COLOR,
|
|
26663
|
-
(
|
|
27796
|
+
(event, groupId, color) => {
|
|
27797
|
+
requireTrusted(event);
|
|
26664
27798
|
assertString(groupId, "groupId");
|
|
26665
27799
|
assertString(color, "color");
|
|
26666
27800
|
tabManager.setGroupColor(groupId, color);
|
|
26667
27801
|
}
|
|
26668
27802
|
);
|
|
26669
|
-
electron.ipcMain.handle(Channels.TAB_TOGGLE_MUTE, (
|
|
27803
|
+
electron.ipcMain.handle(Channels.TAB_TOGGLE_MUTE, (event, id) => {
|
|
27804
|
+
requireTrusted(event);
|
|
26670
27805
|
assertString(id, "id");
|
|
26671
27806
|
return tabManager.toggleMuted(id);
|
|
26672
27807
|
});
|
|
26673
|
-
electron.ipcMain.handle(Channels.TAB_PRINT, (
|
|
27808
|
+
electron.ipcMain.handle(Channels.TAB_PRINT, (event, id) => {
|
|
27809
|
+
requireTrusted(event);
|
|
26674
27810
|
assertString(id, "id");
|
|
26675
27811
|
tabManager.printTab(id);
|
|
26676
27812
|
});
|
|
26677
|
-
electron.ipcMain.handle(Channels.TAB_PRINT_TO_PDF, (
|
|
27813
|
+
electron.ipcMain.handle(Channels.TAB_PRINT_TO_PDF, (event, id) => {
|
|
27814
|
+
requireTrusted(event);
|
|
26678
27815
|
assertString(id, "id");
|
|
26679
27816
|
return tabManager.saveTabAsPdf(id);
|
|
26680
27817
|
});
|
|
26681
|
-
electron.ipcMain.on(Channels.TAB_CONTEXT_MENU, (
|
|
27818
|
+
electron.ipcMain.on(Channels.TAB_CONTEXT_MENU, (event, id) => {
|
|
27819
|
+
requireTrusted(event);
|
|
26682
27820
|
assertString(id, "id");
|
|
26683
27821
|
showTabContextMenu(tabManager, id, mainWindow, () => layoutViews(windowState));
|
|
26684
27822
|
});
|
|
26685
|
-
electron.ipcMain.on(Channels.TAB_GROUP_CONTEXT_MENU, (
|
|
27823
|
+
electron.ipcMain.on(Channels.TAB_GROUP_CONTEXT_MENU, (event, groupId) => {
|
|
27824
|
+
requireTrusted(event);
|
|
26686
27825
|
assertString(groupId, "groupId");
|
|
26687
27826
|
showGroupContextMenu(tabManager, groupId, mainWindow);
|
|
26688
27827
|
});
|
|
26689
|
-
electron.ipcMain.handle(Channels.TAB_STATE_GET, () =>
|
|
26690
|
-
|
|
26691
|
-
|
|
26692
|
-
|
|
26693
|
-
|
|
27828
|
+
electron.ipcMain.handle(Channels.TAB_STATE_GET, (event) => {
|
|
27829
|
+
requireTrusted(event);
|
|
27830
|
+
return {
|
|
27831
|
+
tabs: tabManager.getAllStates(),
|
|
27832
|
+
activeId: tabManager.getActiveTabId() || ""
|
|
27833
|
+
};
|
|
27834
|
+
});
|
|
27835
|
+
electron.ipcMain.handle(Channels.AI_QUERY, async (event, query, history) => {
|
|
27836
|
+
requireTrusted(event);
|
|
26694
27837
|
const settings2 = loadSettings();
|
|
26695
27838
|
const chatConfig = settings2.chatProvider;
|
|
26696
27839
|
if (!chatConfig) {
|
|
@@ -26719,7 +27862,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
26719
27862
|
() => sendToRendererViews(Channels.AI_STREAM_END, "completed"),
|
|
26720
27863
|
tabManager,
|
|
26721
27864
|
runtime2,
|
|
26722
|
-
history
|
|
27865
|
+
history,
|
|
27866
|
+
researchOrchestrator
|
|
26723
27867
|
);
|
|
26724
27868
|
} catch (err) {
|
|
26725
27869
|
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
@@ -26733,10 +27877,12 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
26733
27877
|
})();
|
|
26734
27878
|
return { accepted: true };
|
|
26735
27879
|
});
|
|
26736
|
-
electron.ipcMain.handle(Channels.AI_CANCEL, () => {
|
|
27880
|
+
electron.ipcMain.handle(Channels.AI_CANCEL, (event) => {
|
|
27881
|
+
requireTrusted(event);
|
|
26737
27882
|
activeChatProvider?.cancel();
|
|
26738
27883
|
});
|
|
26739
|
-
electron.ipcMain.handle(Channels.AI_FETCH_MODELS, async (
|
|
27884
|
+
electron.ipcMain.handle(Channels.AI_FETCH_MODELS, async (event, config) => {
|
|
27885
|
+
requireTrusted(event);
|
|
26740
27886
|
try {
|
|
26741
27887
|
if (!config || typeof config !== "object" || !("id" in config)) {
|
|
26742
27888
|
return errorResult("Invalid provider configuration", { models: [] });
|
|
@@ -26748,12 +27894,14 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
26748
27894
|
return errorResult(getErrorMessage(err), { models: [] });
|
|
26749
27895
|
}
|
|
26750
27896
|
});
|
|
26751
|
-
electron.ipcMain.handle(Channels.CONTENT_EXTRACT, async () => {
|
|
27897
|
+
electron.ipcMain.handle(Channels.CONTENT_EXTRACT, async (event) => {
|
|
27898
|
+
requireTrusted(event);
|
|
26752
27899
|
const activeTab = tabManager.getActiveTab();
|
|
26753
27900
|
if (!activeTab) return null;
|
|
26754
27901
|
return extractContent(activeTab.view.webContents);
|
|
26755
27902
|
});
|
|
26756
|
-
electron.ipcMain.handle(Channels.READER_MODE_TOGGLE, async () => {
|
|
27903
|
+
electron.ipcMain.handle(Channels.READER_MODE_TOGGLE, async (event) => {
|
|
27904
|
+
requireTrusted(event);
|
|
26757
27905
|
const activeTab = tabManager.getActiveTab();
|
|
26758
27906
|
if (!activeTab) return;
|
|
26759
27907
|
if (activeTab.state.isReaderMode) {
|
|
@@ -26773,7 +27921,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
26773
27921
|
);
|
|
26774
27922
|
}
|
|
26775
27923
|
});
|
|
26776
|
-
electron.ipcMain.handle(Channels.SIDEBAR_TOGGLE, () => {
|
|
27924
|
+
electron.ipcMain.handle(Channels.SIDEBAR_TOGGLE, (event) => {
|
|
27925
|
+
requireTrusted(event);
|
|
26777
27926
|
windowState.uiState.sidebarOpen = !windowState.uiState.sidebarOpen;
|
|
26778
27927
|
layoutViews(windowState);
|
|
26779
27928
|
return {
|
|
@@ -26781,7 +27930,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
26781
27930
|
width: windowState.uiState.sidebarWidth
|
|
26782
27931
|
};
|
|
26783
27932
|
});
|
|
26784
|
-
electron.ipcMain.handle(Channels.SIDEBAR_NAVIGATE, (
|
|
27933
|
+
electron.ipcMain.handle(Channels.SIDEBAR_NAVIGATE, (event, tab) => {
|
|
27934
|
+
requireTrusted(event);
|
|
26785
27935
|
assertString(tab, "tab");
|
|
26786
27936
|
if (!windowState.uiState.sidebarOpen) {
|
|
26787
27937
|
windowState.uiState.sidebarOpen = true;
|
|
@@ -26795,7 +27945,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
26795
27945
|
width: windowState.uiState.sidebarWidth
|
|
26796
27946
|
};
|
|
26797
27947
|
});
|
|
26798
|
-
electron.ipcMain.handle(Channels.SIDEBAR_RESIZE_START, () => {
|
|
27948
|
+
electron.ipcMain.handle(Channels.SIDEBAR_RESIZE_START, (event) => {
|
|
27949
|
+
requireTrusted(event);
|
|
26799
27950
|
sidebarResizeActive = true;
|
|
26800
27951
|
clearSidebarResizeRecoveryTimer();
|
|
26801
27952
|
const [width, height] = windowState.mainWindow.getContentSize();
|
|
@@ -26810,14 +27961,16 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
26810
27961
|
});
|
|
26811
27962
|
scheduleSidebarResizeRecovery();
|
|
26812
27963
|
});
|
|
26813
|
-
electron.ipcMain.handle(Channels.SIDEBAR_RESIZE, (
|
|
27964
|
+
electron.ipcMain.handle(Channels.SIDEBAR_RESIZE, (event, width) => {
|
|
27965
|
+
requireTrusted(event);
|
|
26814
27966
|
assertNumber(width, "width");
|
|
26815
27967
|
const clamped = Math.max(240, Math.min(800, Math.round(width)));
|
|
26816
27968
|
windowState.uiState.sidebarWidth = clamped;
|
|
26817
27969
|
resizeSidebarViews(windowState);
|
|
26818
27970
|
return clamped;
|
|
26819
27971
|
});
|
|
26820
|
-
electron.ipcMain.handle(Channels.SIDEBAR_RESIZE_COMMIT, () => {
|
|
27972
|
+
electron.ipcMain.handle(Channels.SIDEBAR_RESIZE_COMMIT, (event) => {
|
|
27973
|
+
requireTrusted(event);
|
|
26821
27974
|
sidebarResizeActive = false;
|
|
26822
27975
|
clearSidebarResizeRecoveryTimer();
|
|
26823
27976
|
setSetting("sidebarWidth", windowState.uiState.sidebarWidth);
|
|
@@ -26825,7 +27978,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
26825
27978
|
});
|
|
26826
27979
|
electron.ipcMain.on(
|
|
26827
27980
|
Channels.RENDERER_VIEW_READY,
|
|
26828
|
-
(
|
|
27981
|
+
(event, view) => {
|
|
27982
|
+
requireTrusted(event);
|
|
26829
27983
|
if (view !== "sidebar") return;
|
|
26830
27984
|
if (!windowState.uiState.sidebarOpen) {
|
|
26831
27985
|
windowState.uiState.sidebarOpen = true;
|
|
@@ -26833,12 +27987,14 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
26833
27987
|
}
|
|
26834
27988
|
}
|
|
26835
27989
|
);
|
|
26836
|
-
electron.ipcMain.handle(Channels.FOCUS_MODE_TOGGLE, () => {
|
|
27990
|
+
electron.ipcMain.handle(Channels.FOCUS_MODE_TOGGLE, (event) => {
|
|
27991
|
+
requireTrusted(event);
|
|
26837
27992
|
windowState.uiState.focusMode = !windowState.uiState.focusMode;
|
|
26838
27993
|
layoutViews(windowState);
|
|
26839
27994
|
return windowState.uiState.focusMode;
|
|
26840
27995
|
});
|
|
26841
|
-
electron.ipcMain.handle(Channels.SETTINGS_VISIBILITY, (
|
|
27996
|
+
electron.ipcMain.handle(Channels.SETTINGS_VISIBILITY, (event, open) => {
|
|
27997
|
+
requireTrusted(event);
|
|
26842
27998
|
windowState.uiState.settingsOpen = open;
|
|
26843
27999
|
if (open) {
|
|
26844
28000
|
windowState.uiState.sidebarOpen = false;
|
|
@@ -26846,10 +28002,14 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
26846
28002
|
layoutViews(windowState);
|
|
26847
28003
|
return windowState.uiState.settingsOpen;
|
|
26848
28004
|
});
|
|
26849
|
-
electron.ipcMain.handle(Channels.SETTINGS_GET, () => {
|
|
28005
|
+
electron.ipcMain.handle(Channels.SETTINGS_GET, (event) => {
|
|
28006
|
+
requireTrusted(event);
|
|
26850
28007
|
return getRendererSettings();
|
|
26851
28008
|
});
|
|
26852
|
-
electron.ipcMain.handle(Channels.SETTINGS_HEALTH_GET, () =>
|
|
28009
|
+
electron.ipcMain.handle(Channels.SETTINGS_HEALTH_GET, (event) => {
|
|
28010
|
+
requireTrusted(event);
|
|
28011
|
+
return getRuntimeHealth();
|
|
28012
|
+
});
|
|
26853
28013
|
electron.ipcMain.handle(Channels.MCP_REGENERATE_TOKEN, (event) => {
|
|
26854
28014
|
requireTrusted(event);
|
|
26855
28015
|
return regenerateMcpAuthToken();
|
|
@@ -26870,11 +28030,20 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
26870
28030
|
await stopMcpServer();
|
|
26871
28031
|
await startMcpServer(tabManager, runtime2, updatedSettings.mcpPort);
|
|
26872
28032
|
}
|
|
28033
|
+
if (key2 === "chatProvider" && researchOrchestrator) {
|
|
28034
|
+
try {
|
|
28035
|
+
researchOrchestrator.setProvider(createProvider(value));
|
|
28036
|
+
} catch {
|
|
28037
|
+
}
|
|
28038
|
+
}
|
|
26873
28039
|
const rendererSettings = getRendererSettings();
|
|
26874
28040
|
sendToRendererViews(Channels.SETTINGS_UPDATE, rendererSettings);
|
|
26875
28041
|
return rendererSettings;
|
|
26876
28042
|
});
|
|
26877
|
-
electron.ipcMain.handle(Channels.AGENT_RUNTIME_GET, () =>
|
|
28043
|
+
electron.ipcMain.handle(Channels.AGENT_RUNTIME_GET, (event) => {
|
|
28044
|
+
requireTrusted(event);
|
|
28045
|
+
return runtime2.getState();
|
|
28046
|
+
});
|
|
26878
28047
|
electron.ipcMain.handle(Channels.AGENT_PAUSE, (event) => {
|
|
26879
28048
|
requireTrusted(event);
|
|
26880
28049
|
return runtime2.pause();
|
|
@@ -26905,24 +28074,27 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
26905
28074
|
);
|
|
26906
28075
|
electron.ipcMain.handle(
|
|
26907
28076
|
Channels.AGENT_CHECKPOINT_CREATE,
|
|
26908
|
-
(
|
|
26909
|
-
|
|
26910
|
-
|
|
26911
|
-
|
|
26912
|
-
(_, checkpointId) => runtime2.restoreCheckpoint(checkpointId)
|
|
26913
|
-
);
|
|
26914
|
-
electron.ipcMain.handle(
|
|
26915
|
-
Channels.AGENT_CHECKPOINT_UPDATE_NOTE,
|
|
26916
|
-
(_, checkpointId, note) => runtime2.updateCheckpointNote(checkpointId, note || "")
|
|
26917
|
-
);
|
|
26918
|
-
electron.ipcMain.handle(
|
|
26919
|
-
Channels.AGENT_UNDO_LAST_ACTION,
|
|
26920
|
-
() => runtime2.undoLastAction()
|
|
26921
|
-
);
|
|
26922
|
-
electron.ipcMain.handle(
|
|
26923
|
-
Channels.AGENT_SESSION_CAPTURE,
|
|
26924
|
-
(_, note) => runtime2.captureSession(note)
|
|
28077
|
+
(event, name, note) => {
|
|
28078
|
+
requireTrusted(event);
|
|
28079
|
+
return runtime2.createCheckpoint(name, note);
|
|
28080
|
+
}
|
|
26925
28081
|
);
|
|
28082
|
+
electron.ipcMain.handle(Channels.AGENT_CHECKPOINT_RESTORE, (event, checkpointId) => {
|
|
28083
|
+
requireTrusted(event);
|
|
28084
|
+
return runtime2.restoreCheckpoint(checkpointId);
|
|
28085
|
+
});
|
|
28086
|
+
electron.ipcMain.handle(Channels.AGENT_CHECKPOINT_UPDATE_NOTE, (event, checkpointId, note) => {
|
|
28087
|
+
requireTrusted(event);
|
|
28088
|
+
return runtime2.updateCheckpointNote(checkpointId, note || "");
|
|
28089
|
+
});
|
|
28090
|
+
electron.ipcMain.handle(Channels.AGENT_UNDO_LAST_ACTION, (event) => {
|
|
28091
|
+
requireTrusted(event);
|
|
28092
|
+
return runtime2.undoLastAction();
|
|
28093
|
+
});
|
|
28094
|
+
electron.ipcMain.handle(Channels.AGENT_SESSION_CAPTURE, (event, note) => {
|
|
28095
|
+
requireTrusted(event);
|
|
28096
|
+
return runtime2.captureSession(note);
|
|
28097
|
+
});
|
|
26926
28098
|
electron.ipcMain.handle(
|
|
26927
28099
|
Channels.AGENT_SESSION_RESTORE,
|
|
26928
28100
|
(event, snapshot2) => {
|
|
@@ -26931,7 +28103,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
26931
28103
|
}
|
|
26932
28104
|
);
|
|
26933
28105
|
registerBookmarkHandlers();
|
|
26934
|
-
electron.ipcMain.handle(Channels.HIGHLIGHT_CAPTURE, async () => {
|
|
28106
|
+
electron.ipcMain.handle(Channels.HIGHLIGHT_CAPTURE, async (event) => {
|
|
28107
|
+
requireTrusted(event);
|
|
26935
28108
|
try {
|
|
26936
28109
|
const activeTab = tabManager.getActiveTab();
|
|
26937
28110
|
if (!activeTab) {
|
|
@@ -26975,10 +28148,12 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
26975
28148
|
logger$4.warn("Failed to persist auto-highlight selection:", err);
|
|
26976
28149
|
}
|
|
26977
28150
|
});
|
|
26978
|
-
electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_COUNT, () => {
|
|
28151
|
+
electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_COUNT, (event) => {
|
|
28152
|
+
requireTrusted(event);
|
|
26979
28153
|
return getActiveHighlightCountSafe();
|
|
26980
28154
|
});
|
|
26981
|
-
electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_SCROLL, (
|
|
28155
|
+
electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_SCROLL, (event, index) => {
|
|
28156
|
+
requireTrusted(event);
|
|
26982
28157
|
const info = getActiveTabInfo(tabManager);
|
|
26983
28158
|
if (!info) return false;
|
|
26984
28159
|
try {
|
|
@@ -26988,7 +28163,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
26988
28163
|
return false;
|
|
26989
28164
|
}
|
|
26990
28165
|
});
|
|
26991
|
-
electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_REMOVE, async (
|
|
28166
|
+
electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_REMOVE, async (event, index) => {
|
|
28167
|
+
requireTrusted(event);
|
|
26992
28168
|
const info = getActiveTabInfo(tabManager);
|
|
26993
28169
|
if (!info) return false;
|
|
26994
28170
|
try {
|
|
@@ -27002,7 +28178,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
27002
28178
|
return false;
|
|
27003
28179
|
}
|
|
27004
28180
|
});
|
|
27005
|
-
electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_CLEAR, async () => {
|
|
28181
|
+
electron.ipcMain.handle(Channels.HIGHLIGHT_NAV_CLEAR, async (event) => {
|
|
28182
|
+
requireTrusted(event);
|
|
27006
28183
|
const info = getActiveTabInfo(tabManager);
|
|
27007
28184
|
if (!info) return false;
|
|
27008
28185
|
try {
|
|
@@ -27017,22 +28194,27 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
27017
28194
|
}
|
|
27018
28195
|
});
|
|
27019
28196
|
const findBridge = createFindInPageBridge(tabManager, chromeView);
|
|
27020
|
-
electron.ipcMain.handle(Channels.FIND_IN_PAGE_START, (
|
|
28197
|
+
electron.ipcMain.handle(Channels.FIND_IN_PAGE_START, (event, text, options) => {
|
|
28198
|
+
requireTrusted(event);
|
|
27021
28199
|
return findBridge.start(text, options);
|
|
27022
28200
|
});
|
|
27023
|
-
electron.ipcMain.handle(Channels.FIND_IN_PAGE_NEXT, (
|
|
28201
|
+
electron.ipcMain.handle(Channels.FIND_IN_PAGE_NEXT, (event, forward) => {
|
|
28202
|
+
requireTrusted(event);
|
|
27024
28203
|
return findBridge.next(forward);
|
|
27025
28204
|
});
|
|
27026
|
-
electron.ipcMain.handle(Channels.FIND_IN_PAGE_STOP, (
|
|
28205
|
+
electron.ipcMain.handle(Channels.FIND_IN_PAGE_STOP, (event, action) => {
|
|
28206
|
+
requireTrusted(event);
|
|
27027
28207
|
findBridge.stop(action);
|
|
27028
28208
|
});
|
|
27029
28209
|
registerHistoryHandlers();
|
|
27030
|
-
electron.ipcMain.handle(Channels.DEVTOOLS_PANEL_TOGGLE, () => {
|
|
28210
|
+
electron.ipcMain.handle(Channels.DEVTOOLS_PANEL_TOGGLE, (event) => {
|
|
28211
|
+
requireTrusted(event);
|
|
27031
28212
|
windowState.uiState.devtoolsPanelOpen = !windowState.uiState.devtoolsPanelOpen;
|
|
27032
28213
|
layoutViews(windowState);
|
|
27033
28214
|
return { open: windowState.uiState.devtoolsPanelOpen };
|
|
27034
28215
|
});
|
|
27035
|
-
electron.ipcMain.handle(Channels.DEVTOOLS_PANEL_RESIZE, (
|
|
28216
|
+
electron.ipcMain.handle(Channels.DEVTOOLS_PANEL_RESIZE, (event, height) => {
|
|
28217
|
+
requireTrusted(event);
|
|
27036
28218
|
const clamped = Math.max(MIN_DEVTOOLS_PANEL, Math.min(MAX_DEVTOOLS_PANEL, Math.round(height)));
|
|
27037
28219
|
windowState.uiState.devtoolsPanelHeight = clamped;
|
|
27038
28220
|
layoutViews(windowState);
|
|
@@ -27045,7 +28227,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
27045
28227
|
registerHumanVaultHandlers();
|
|
27046
28228
|
registerWindowControlHandlers(mainWindow);
|
|
27047
28229
|
registerCodexHandlers();
|
|
27048
|
-
electron.ipcMain.handle(Channels.AUTOMATION_GET_INSTALLED, () => {
|
|
28230
|
+
electron.ipcMain.handle(Channels.AUTOMATION_GET_INSTALLED, (event) => {
|
|
28231
|
+
requireTrusted(event);
|
|
27049
28232
|
return getInstalledKits();
|
|
27050
28233
|
});
|
|
27051
28234
|
electron.ipcMain.handle(Channels.AUTOMATION_INSTALL_FROM_FILE, async (event) => {
|
|
@@ -27060,6 +28243,7 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
27060
28243
|
registerScheduleHandlers(windowState, runtime2, sendToRendererViews);
|
|
27061
28244
|
registerAutofillHandlers(windowState);
|
|
27062
28245
|
registerPageDiffHandlers(windowState, sendToRendererViews);
|
|
28246
|
+
registerResearchHandlers(() => getResearchOrchestrator());
|
|
27063
28247
|
electron.ipcMain.handle(Channels.CLEAR_BROWSING_DATA, async (event, options) => {
|
|
27064
28248
|
requireTrusted(event);
|
|
27065
28249
|
const { cache, cookies, history, localStorage: clearLs, timeRange } = options;
|
|
@@ -27078,7 +28262,10 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
27078
28262
|
});
|
|
27079
28263
|
setDownloadBroadcaster(sendToRendererViews);
|
|
27080
28264
|
setPermissionBroadcaster(sendToRendererViews);
|
|
27081
|
-
electron.ipcMain.handle(Channels.DOWNLOADS_GET, () =>
|
|
28265
|
+
electron.ipcMain.handle(Channels.DOWNLOADS_GET, (event) => {
|
|
28266
|
+
requireTrusted(event);
|
|
28267
|
+
return listDownloads();
|
|
28268
|
+
});
|
|
27082
28269
|
electron.ipcMain.handle(Channels.DOWNLOADS_CLEAR, (event) => {
|
|
27083
28270
|
requireTrusted(event);
|
|
27084
28271
|
clearDownloads();
|
|
@@ -27092,7 +28279,10 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
27092
28279
|
requireTrusted(event);
|
|
27093
28280
|
return showDownloadInFolder(id);
|
|
27094
28281
|
});
|
|
27095
|
-
electron.ipcMain.handle(Channels.PERMISSIONS_GET, () =>
|
|
28282
|
+
electron.ipcMain.handle(Channels.PERMISSIONS_GET, (event) => {
|
|
28283
|
+
requireTrusted(event);
|
|
28284
|
+
return listPermissions();
|
|
28285
|
+
});
|
|
27096
28286
|
electron.ipcMain.handle(Channels.PERMISSIONS_CLEAR, (event) => {
|
|
27097
28287
|
requireTrusted(event);
|
|
27098
28288
|
clearPermissions();
|
|
@@ -27103,12 +28293,16 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
27103
28293
|
clearPermissionsForOrigin(origin);
|
|
27104
28294
|
return true;
|
|
27105
28295
|
});
|
|
27106
|
-
electron.ipcMain.handle(Channels.UPDATES_CHECK, () =>
|
|
28296
|
+
electron.ipcMain.handle(Channels.UPDATES_CHECK, (event) => {
|
|
28297
|
+
requireTrusted(event);
|
|
28298
|
+
return checkForUpdates();
|
|
28299
|
+
});
|
|
27107
28300
|
electron.ipcMain.handle(Channels.UPDATES_OPEN_DOWNLOAD, (event) => {
|
|
27108
28301
|
requireTrusted(event);
|
|
27109
28302
|
return openUpdateDownload();
|
|
27110
28303
|
});
|
|
27111
|
-
electron.ipcMain.handle(Channels.TAB_TOGGLE_PIP, async () => {
|
|
28304
|
+
electron.ipcMain.handle(Channels.TAB_TOGGLE_PIP, async (event) => {
|
|
28305
|
+
requireTrusted(event);
|
|
27112
28306
|
return togglePictureInPicture(tabManager);
|
|
27113
28307
|
});
|
|
27114
28308
|
}
|
|
@@ -27496,6 +28690,10 @@ ${lines.join("\n")}
|
|
|
27496
28690
|
}
|
|
27497
28691
|
const MAX_TRANSCRIPT_TEXT_LENGTH = 8e3;
|
|
27498
28692
|
const PERSIST_DEBOUNCE_MS = 500;
|
|
28693
|
+
const INTERRUPTED_ACTION_STATUSES = /* @__PURE__ */ new Set([
|
|
28694
|
+
"running",
|
|
28695
|
+
"waiting-approval"
|
|
28696
|
+
]);
|
|
27499
28697
|
const logger$3 = createLogger("Runtime");
|
|
27500
28698
|
function clone(value) {
|
|
27501
28699
|
return JSON.parse(JSON.stringify(value));
|
|
@@ -27518,6 +28716,15 @@ function getRuntimeStatePath() {
|
|
|
27518
28716
|
return path$1.join(electron.app.getPath("userData"), "vessel-agent-runtime.json");
|
|
27519
28717
|
}
|
|
27520
28718
|
function sanitizePersistence(persisted) {
|
|
28719
|
+
const recoveredAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
28720
|
+
const actions = Array.isArray(persisted?.actions) ? persisted.actions.slice(-120).map(
|
|
28721
|
+
(action) => INTERRUPTED_ACTION_STATUSES.has(action.status) ? {
|
|
28722
|
+
...action,
|
|
28723
|
+
status: "failed",
|
|
28724
|
+
finishedAt: action.finishedAt ?? recoveredAt,
|
|
28725
|
+
error: action.error ?? "Action was interrupted before the previous Vessel session ended."
|
|
28726
|
+
} : action
|
|
28727
|
+
) : [];
|
|
27521
28728
|
return {
|
|
27522
28729
|
session: persisted?.session ?? null,
|
|
27523
28730
|
supervisor: {
|
|
@@ -27526,7 +28733,7 @@ function sanitizePersistence(persisted) {
|
|
|
27526
28733
|
pendingApprovals: [],
|
|
27527
28734
|
lastError: persisted?.supervisor?.lastError
|
|
27528
28735
|
},
|
|
27529
|
-
actions
|
|
28736
|
+
actions,
|
|
27530
28737
|
checkpoints: Array.isArray(persisted?.checkpoints) ? persisted.checkpoints.slice(-20) : [],
|
|
27531
28738
|
transcript: [],
|
|
27532
28739
|
mcpStatus: "stopped",
|