@quanta-intellect/vessel-browser 0.1.99 → 0.1.103
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/out/main/index.js
CHANGED
|
@@ -65,6 +65,7 @@ const defaults = {
|
|
|
65
65
|
chatProvider: null,
|
|
66
66
|
maxToolIterations: 200,
|
|
67
67
|
domainPolicy: { allowedDomains: [], blockedDomains: [] },
|
|
68
|
+
sourceDoNotAllowList: [],
|
|
68
69
|
downloadPath: "",
|
|
69
70
|
telemetryEnabled: true,
|
|
70
71
|
defaultSearchEngine: "duckduckgo",
|
|
@@ -231,6 +232,11 @@ function sanitizePort(value) {
|
|
|
231
232
|
function sanitizeReasoningEffortLevel$1(value) {
|
|
232
233
|
return value === "low" || value === "medium" || value === "high" || value === "max" || value === "off" ? value : "off";
|
|
233
234
|
}
|
|
235
|
+
function sanitizeStringList(value) {
|
|
236
|
+
return Array.isArray(value) ? Array.from(
|
|
237
|
+
new Set(value.map((item) => String(item).trim()).filter(Boolean))
|
|
238
|
+
) : [];
|
|
239
|
+
}
|
|
234
240
|
function sanitizeChatProvider(provider) {
|
|
235
241
|
return provider ? {
|
|
236
242
|
...provider,
|
|
@@ -252,6 +258,9 @@ function loadSettings() {
|
|
|
252
258
|
mergeChatProviderSecret(parsed.chatProvider ?? null)
|
|
253
259
|
),
|
|
254
260
|
mcpPort: sanitizePort(parsed.mcpPort ?? defaults.mcpPort),
|
|
261
|
+
sourceDoNotAllowList: sanitizeStringList(
|
|
262
|
+
parsed.sourceDoNotAllowList ?? defaults.sourceDoNotAllowList
|
|
263
|
+
),
|
|
255
264
|
agentTranscriptMode: parsed.agentTranscriptMode === "off" || parsed.agentTranscriptMode === "summary" || parsed.agentTranscriptMode === "full" ? parsed.agentTranscriptMode : parsed.showAgentTranscript === false ? "off" : defaults.agentTranscriptMode
|
|
256
265
|
};
|
|
257
266
|
} catch (error) {
|
|
@@ -296,6 +305,8 @@ function setSetting(key2, value) {
|
|
|
296
305
|
loadSettings();
|
|
297
306
|
if (key2 === "mcpPort") {
|
|
298
307
|
settings.mcpPort = sanitizePort(value);
|
|
308
|
+
} else if (key2 === "sourceDoNotAllowList") {
|
|
309
|
+
settings.sourceDoNotAllowList = sanitizeStringList(value);
|
|
299
310
|
} else if (key2 === "chatProvider") {
|
|
300
311
|
const nextProvider = value;
|
|
301
312
|
if (!nextProvider) {
|
|
@@ -3564,6 +3575,7 @@ const Channels = {
|
|
|
3564
3575
|
RESEARCH_SET_MODE: "research:set-mode",
|
|
3565
3576
|
RESEARCH_SET_TRACES: "research:set-traces",
|
|
3566
3577
|
RESEARCH_CANCEL: "research:cancel",
|
|
3578
|
+
RESEARCH_STOP_AND_SYNTHESIZE: "research:stop-and-synthesize",
|
|
3567
3579
|
RESEARCH_EXPORT_REPORT: "research:export-report",
|
|
3568
3580
|
// Codex OAuth
|
|
3569
3581
|
CODEX_START_AUTH: "codex:start-auth",
|
|
@@ -4789,7 +4801,14 @@ const TELEMETRY_PROPERTY_ALLOWLIST = {
|
|
|
4789
4801
|
bookmark_action: /* @__PURE__ */ new Set(["action"]),
|
|
4790
4802
|
vault_action: /* @__PURE__ */ new Set(["action"]),
|
|
4791
4803
|
extraction_failed: /* @__PURE__ */ new Set(["reason"]),
|
|
4792
|
-
premium_funnel: /* @__PURE__ */ new Set([
|
|
4804
|
+
premium_funnel: /* @__PURE__ */ new Set([
|
|
4805
|
+
"step",
|
|
4806
|
+
"status",
|
|
4807
|
+
"reason",
|
|
4808
|
+
"source",
|
|
4809
|
+
"previous_status",
|
|
4810
|
+
"new_status"
|
|
4811
|
+
])
|
|
4793
4812
|
};
|
|
4794
4813
|
function getDeviceIdPath() {
|
|
4795
4814
|
return path.join(electron.app.getPath("userData"), ".vessel-device-id");
|
|
@@ -25086,6 +25105,9 @@ function registerResearchHandlers(getOrchestrator) {
|
|
|
25086
25105
|
electron.ipcMain.handle(Channels.RESEARCH_CANCEL, () => {
|
|
25087
25106
|
getOrchestrator().cancel();
|
|
25088
25107
|
});
|
|
25108
|
+
electron.ipcMain.handle(Channels.RESEARCH_STOP_AND_SYNTHESIZE, () => {
|
|
25109
|
+
getOrchestrator().stopAndSynthesizeCurrentFindings();
|
|
25110
|
+
});
|
|
25089
25111
|
electron.ipcMain.handle(Channels.RESEARCH_EXPORT_REPORT, async () => {
|
|
25090
25112
|
try {
|
|
25091
25113
|
if (isToolGated("research_export_report")) {
|
|
@@ -25213,6 +25235,96 @@ const MAX_THREADS = 5;
|
|
|
25213
25235
|
function clone$1(value) {
|
|
25214
25236
|
return structuredClone(value);
|
|
25215
25237
|
}
|
|
25238
|
+
function normalizeSourceDomain(value) {
|
|
25239
|
+
const trimmed = value.trim().toLowerCase();
|
|
25240
|
+
if (!trimmed) return "";
|
|
25241
|
+
try {
|
|
25242
|
+
return new URL(
|
|
25243
|
+
trimmed.includes("://") ? trimmed : `https://${trimmed}`
|
|
25244
|
+
).hostname.replace(/^www\./, "");
|
|
25245
|
+
} catch {
|
|
25246
|
+
return trimmed.replace(/^www\./, "");
|
|
25247
|
+
}
|
|
25248
|
+
}
|
|
25249
|
+
function mergeBlockedSourceDomains(thread) {
|
|
25250
|
+
const globalBlocked = loadSettings().sourceDoNotAllowList.map(normalizeSourceDomain).filter(Boolean);
|
|
25251
|
+
if (globalBlocked.length === 0) return thread;
|
|
25252
|
+
const blockedDomains = Array.from(
|
|
25253
|
+
/* @__PURE__ */ new Set([
|
|
25254
|
+
...thread.blockedDomains.map(normalizeSourceDomain).filter(Boolean),
|
|
25255
|
+
...globalBlocked
|
|
25256
|
+
])
|
|
25257
|
+
);
|
|
25258
|
+
return {
|
|
25259
|
+
...thread,
|
|
25260
|
+
blockedDomains
|
|
25261
|
+
};
|
|
25262
|
+
}
|
|
25263
|
+
function matchesSourceDomain(hostname, domain) {
|
|
25264
|
+
return hostname === domain || hostname.endsWith(`.${domain}`);
|
|
25265
|
+
}
|
|
25266
|
+
function getBlockedSourceNavigation(url, blockedDomains) {
|
|
25267
|
+
if (typeof url !== "string" || blockedDomains.length === 0) return null;
|
|
25268
|
+
try {
|
|
25269
|
+
const hostname = new URL(url).hostname.toLowerCase().replace(/^www\./, "");
|
|
25270
|
+
return blockedDomains.find(
|
|
25271
|
+
(domain) => matchesSourceDomain(hostname, normalizeSourceDomain(domain))
|
|
25272
|
+
) ?? null;
|
|
25273
|
+
} catch {
|
|
25274
|
+
return null;
|
|
25275
|
+
}
|
|
25276
|
+
}
|
|
25277
|
+
function buildFallbackSourceIndex(findings) {
|
|
25278
|
+
const seen = /* @__PURE__ */ new Set();
|
|
25279
|
+
const sources = [];
|
|
25280
|
+
for (const claim of findings.flatMap((finding) => finding.claims)) {
|
|
25281
|
+
if (!claim.sourceUrl || seen.has(claim.sourceUrl)) continue;
|
|
25282
|
+
seen.add(claim.sourceUrl);
|
|
25283
|
+
sources.push({
|
|
25284
|
+
index: sources.length + 1,
|
|
25285
|
+
url: claim.sourceUrl,
|
|
25286
|
+
title: claim.sourceTitle || claim.sourceUrl,
|
|
25287
|
+
accessedAt: claim.extractedAt,
|
|
25288
|
+
supportingQuote: claim.extractedQuote
|
|
25289
|
+
});
|
|
25290
|
+
}
|
|
25291
|
+
return sources;
|
|
25292
|
+
}
|
|
25293
|
+
function citationForClaim(claim, sourceIndex) {
|
|
25294
|
+
const index = sourceIndex.find((source) => source.url === claim.sourceUrl)?.index ?? 0;
|
|
25295
|
+
return index > 0 ? `[${index}]` : "";
|
|
25296
|
+
}
|
|
25297
|
+
function buildFallbackFindingsByThread(findings, sourceIndex = buildFallbackSourceIndex(findings)) {
|
|
25298
|
+
return findings.map((finding) => {
|
|
25299
|
+
const claimLines = finding.claims.map((claim) => {
|
|
25300
|
+
const citation = citationForClaim(claim, sourceIndex);
|
|
25301
|
+
return citation ? `${claim.claim} ${citation}` : claim.claim;
|
|
25302
|
+
});
|
|
25303
|
+
return {
|
|
25304
|
+
threadLabel: finding.threadLabel,
|
|
25305
|
+
content: claimLines.length > 0 ? claimLines.join("\n\n") : `No citeable claims were extracted for this thread. ${finding.executionSummary}`
|
|
25306
|
+
};
|
|
25307
|
+
});
|
|
25308
|
+
}
|
|
25309
|
+
function buildFallbackReport(objectives, findings, reason) {
|
|
25310
|
+
const sourceIndex = buildFallbackSourceIndex(findings);
|
|
25311
|
+
const findingsByThread = buildFallbackFindingsByThread(findings, sourceIndex);
|
|
25312
|
+
const claimCount = findings.reduce(
|
|
25313
|
+
(sum, finding) => sum + finding.claims.length,
|
|
25314
|
+
0
|
|
25315
|
+
);
|
|
25316
|
+
const executiveSummary = claimCount > 0 ? `The model's final synthesis response could not be parsed, so Vessel generated this sourced fallback from ${claimCount} extracted claim${claimCount === 1 ? "" : "s"} across ${sourceIndex.length} source${sourceIndex.length === 1 ? "" : "s"}.` : `The model's final synthesis response could not be parsed, and no citeable claims were extracted from the research threads.`;
|
|
25317
|
+
return {
|
|
25318
|
+
title: objectives.researchQuestion,
|
|
25319
|
+
executiveSummary,
|
|
25320
|
+
findingsByThread,
|
|
25321
|
+
contradictions: [],
|
|
25322
|
+
gaps: [`Final synthesis JSON could not be parsed: ${reason}`],
|
|
25323
|
+
sourceIndex,
|
|
25324
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25325
|
+
objectives
|
|
25326
|
+
};
|
|
25327
|
+
}
|
|
25216
25328
|
class ResearchOrchestrator {
|
|
25217
25329
|
constructor(provider, tabManager, runtime2) {
|
|
25218
25330
|
this.provider = provider;
|
|
@@ -25225,6 +25337,8 @@ class ResearchOrchestrator {
|
|
|
25225
25337
|
runtime;
|
|
25226
25338
|
state;
|
|
25227
25339
|
updateListener = null;
|
|
25340
|
+
stopRequested = false;
|
|
25341
|
+
synthesizeAfterStop = false;
|
|
25228
25342
|
// ── state access ──────────────────────────────────────────────
|
|
25229
25343
|
initialState() {
|
|
25230
25344
|
return {
|
|
@@ -25233,6 +25347,7 @@ class ResearchOrchestrator {
|
|
|
25233
25347
|
includeTraces: false,
|
|
25234
25348
|
objectives: null,
|
|
25235
25349
|
threads: [],
|
|
25350
|
+
threadProgress: [],
|
|
25236
25351
|
threadFindings: [],
|
|
25237
25352
|
report: null,
|
|
25238
25353
|
subAgentTraces: [],
|
|
@@ -25265,9 +25380,30 @@ class ResearchOrchestrator {
|
|
|
25265
25380
|
this.emit();
|
|
25266
25381
|
}
|
|
25267
25382
|
cancel() {
|
|
25383
|
+
this.stopRequested = true;
|
|
25384
|
+
this.synthesizeAfterStop = false;
|
|
25385
|
+
this.provider?.cancel();
|
|
25268
25386
|
this.state = this.initialState();
|
|
25269
25387
|
this.emit();
|
|
25270
25388
|
}
|
|
25389
|
+
stopAndSynthesizeCurrentFindings() {
|
|
25390
|
+
if (this.state.phase !== "executing") {
|
|
25391
|
+
logger$7.warn("Not executing, ignoring stopAndSynthesizeCurrentFindings");
|
|
25392
|
+
return;
|
|
25393
|
+
}
|
|
25394
|
+
this.stopRequested = true;
|
|
25395
|
+
this.synthesizeAfterStop = true;
|
|
25396
|
+
this.state.threadProgress = this.state.threadProgress.map(
|
|
25397
|
+
(progress) => progress.status === "completed" || progress.status === "failed" ? progress : {
|
|
25398
|
+
...progress,
|
|
25399
|
+
status: "stopping",
|
|
25400
|
+
message: "Stopping and preparing to synthesize current findings",
|
|
25401
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
25402
|
+
}
|
|
25403
|
+
);
|
|
25404
|
+
this.emit();
|
|
25405
|
+
this.provider?.cancel();
|
|
25406
|
+
}
|
|
25271
25407
|
/**
|
|
25272
25408
|
* Swap the AI provider used by this orchestrator.
|
|
25273
25409
|
* Safe to call while research is in progress — running sub-agents
|
|
@@ -25313,8 +25449,12 @@ class ResearchOrchestrator {
|
|
|
25313
25449
|
logger$7.warn("Not in planning phase, ignoring setObjectives");
|
|
25314
25450
|
return;
|
|
25315
25451
|
}
|
|
25316
|
-
|
|
25317
|
-
this.state.
|
|
25452
|
+
const threads = objectives.threads.slice(0, MAX_THREADS).map(mergeBlockedSourceDomains);
|
|
25453
|
+
this.state.objectives = {
|
|
25454
|
+
...objectives,
|
|
25455
|
+
threads
|
|
25456
|
+
};
|
|
25457
|
+
this.state.threads = threads;
|
|
25318
25458
|
this.setPhase("awaiting_approval");
|
|
25319
25459
|
}
|
|
25320
25460
|
/**
|
|
@@ -25383,8 +25523,28 @@ class ResearchOrchestrator {
|
|
|
25383
25523
|
}
|
|
25384
25524
|
if (mode) this.state.supervisionMode = mode;
|
|
25385
25525
|
if (includeTraces !== void 0) this.state.includeTraces = includeTraces;
|
|
25526
|
+
this.stopRequested = false;
|
|
25527
|
+
this.synthesizeAfterStop = false;
|
|
25528
|
+
this.state.threadFindings = [];
|
|
25529
|
+
this.state.threadProgress = this.state.threads.map((thread) => ({
|
|
25530
|
+
threadLabel: thread.label,
|
|
25531
|
+
status: "queued",
|
|
25532
|
+
message: "Queued",
|
|
25533
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
25534
|
+
}));
|
|
25386
25535
|
this.setPhase("executing");
|
|
25387
25536
|
}
|
|
25537
|
+
updateThreadProgress(threadLabel, status, message) {
|
|
25538
|
+
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
25539
|
+
const existingIndex = this.state.threadProgress.findIndex(
|
|
25540
|
+
(progress) => progress.threadLabel === threadLabel
|
|
25541
|
+
);
|
|
25542
|
+
const next = { threadLabel, status, message, updatedAt };
|
|
25543
|
+
this.state.threadProgress = existingIndex >= 0 ? this.state.threadProgress.map(
|
|
25544
|
+
(progress, index) => index === existingIndex ? next : progress
|
|
25545
|
+
) : [...this.state.threadProgress, next];
|
|
25546
|
+
this.emit();
|
|
25547
|
+
}
|
|
25388
25548
|
// ── phase: executing → synthesizing ────────────────────────────
|
|
25389
25549
|
async executeSubAgents() {
|
|
25390
25550
|
if (this.state.phase !== "executing" || !this.state.objectives) return;
|
|
@@ -25404,8 +25564,20 @@ class ResearchOrchestrator {
|
|
|
25404
25564
|
});
|
|
25405
25565
|
})
|
|
25406
25566
|
);
|
|
25567
|
+
const shouldSynthesize = this.synthesizeAfterStop;
|
|
25407
25568
|
if (this.state.phase !== "executing") return;
|
|
25408
25569
|
this.state.threadFindings = results.filter((f) => f !== null);
|
|
25570
|
+
this.stopRequested = false;
|
|
25571
|
+
this.synthesizeAfterStop = false;
|
|
25572
|
+
if (!shouldSynthesize) {
|
|
25573
|
+
for (const finding of this.state.threadFindings) {
|
|
25574
|
+
this.updateThreadProgress(
|
|
25575
|
+
finding.threadLabel,
|
|
25576
|
+
finding.claims.length > 0 ? "completed" : "failed",
|
|
25577
|
+
finding.claims.length > 0 ? `${finding.claims.length} claim${finding.claims.length === 1 ? "" : "s"} extracted` : "No citeable claims extracted"
|
|
25578
|
+
);
|
|
25579
|
+
}
|
|
25580
|
+
}
|
|
25409
25581
|
this.setPhase("synthesizing");
|
|
25410
25582
|
try {
|
|
25411
25583
|
await this.synthesizeReport();
|
|
@@ -25426,6 +25598,7 @@ class ResearchOrchestrator {
|
|
|
25426
25598
|
};
|
|
25427
25599
|
const tabId = this.tabManager.createTab();
|
|
25428
25600
|
let sourcesConsumed = 0;
|
|
25601
|
+
this.updateThreadProgress(thread.label, "running", "Researching sources");
|
|
25429
25602
|
if (tabId) this.tabManager.switchTab(tabId);
|
|
25430
25603
|
const discardedSources = [];
|
|
25431
25604
|
let transcript = "";
|
|
@@ -25454,10 +25627,32 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
|
|
|
25454
25627
|
},
|
|
25455
25628
|
async (name, args) => {
|
|
25456
25629
|
const t0 = Date.now();
|
|
25457
|
-
if (this.state.phase !== "executing") {
|
|
25630
|
+
if (this.state.phase !== "executing" || this.stopRequested) {
|
|
25458
25631
|
const msg = "Research cancelled — stopping.";
|
|
25459
25632
|
return msg;
|
|
25460
25633
|
}
|
|
25634
|
+
if (name === "navigate") {
|
|
25635
|
+
const blockedDomain = getBlockedSourceNavigation(
|
|
25636
|
+
args.url,
|
|
25637
|
+
thread.blockedDomains
|
|
25638
|
+
);
|
|
25639
|
+
if (blockedDomain) {
|
|
25640
|
+
const msg = `Source skipped: ${String(args.url)} matches the Research Desk source do-not-allow list (${blockedDomain}). Choose a different source.`;
|
|
25641
|
+
discardedSources.push({
|
|
25642
|
+
url: String(args.url || ""),
|
|
25643
|
+
title: String(args.url || "excluded source"),
|
|
25644
|
+
reason: msg
|
|
25645
|
+
});
|
|
25646
|
+
trace.toolCalls.push({
|
|
25647
|
+
tool: name,
|
|
25648
|
+
args,
|
|
25649
|
+
result: msg,
|
|
25650
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25651
|
+
durationMs: 0
|
|
25652
|
+
});
|
|
25653
|
+
return msg;
|
|
25654
|
+
}
|
|
25655
|
+
}
|
|
25461
25656
|
if (name === "navigate" || name === "search") {
|
|
25462
25657
|
sourcesConsumed++;
|
|
25463
25658
|
if (sourcesConsumed > thread.sourceBudget) {
|
|
@@ -25513,6 +25708,9 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
|
|
|
25513
25708
|
message: String(err),
|
|
25514
25709
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
25515
25710
|
});
|
|
25711
|
+
if (this.state.phase === "executing") {
|
|
25712
|
+
this.updateThreadProgress(thread.label, "stopping", "Stopping thread");
|
|
25713
|
+
}
|
|
25516
25714
|
} finally {
|
|
25517
25715
|
trace.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
25518
25716
|
if (tabId) {
|
|
@@ -25537,6 +25735,13 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
|
|
|
25537
25735
|
const pagesVisited = trace.toolCalls.filter(
|
|
25538
25736
|
(t) => ["navigate", "read_page", "search"].includes(t.tool)
|
|
25539
25737
|
).length;
|
|
25738
|
+
if (this.state.phase === "executing") {
|
|
25739
|
+
this.updateThreadProgress(
|
|
25740
|
+
thread.label,
|
|
25741
|
+
claims.length > 0 ? "completed" : this.stopRequested ? "stopping" : "failed",
|
|
25742
|
+
claims.length > 0 ? `${claims.length} claim${claims.length === 1 ? "" : "s"} extracted` : this.stopRequested ? "Stopped before citeable claims were extracted" : "No citeable claims extracted"
|
|
25743
|
+
);
|
|
25744
|
+
}
|
|
25540
25745
|
return {
|
|
25541
25746
|
threadLabel: thread.label,
|
|
25542
25747
|
threadQuestion: thread.question,
|
|
@@ -25633,7 +25838,7 @@ ${transcript.slice(0, 32e3)}`;
|
|
|
25633
25838
|
() => {
|
|
25634
25839
|
}
|
|
25635
25840
|
);
|
|
25636
|
-
const report = this.parseReportFromJson(response, objectives);
|
|
25841
|
+
const report = this.parseReportFromJson(response, objectives, findings);
|
|
25637
25842
|
this.setReport(report);
|
|
25638
25843
|
this.setPhase("delivered");
|
|
25639
25844
|
return report;
|
|
@@ -25642,7 +25847,7 @@ ${transcript.slice(0, 32e3)}`;
|
|
|
25642
25847
|
* Parse the LLM's JSON synthesis response into a structured ResearchReport.
|
|
25643
25848
|
* Handles both bare JSON and JSON wrapped in markdown fences.
|
|
25644
25849
|
*/
|
|
25645
|
-
parseReportFromJson(text, objectives) {
|
|
25850
|
+
parseReportFromJson(text, objectives, findings) {
|
|
25646
25851
|
let json = text.trim();
|
|
25647
25852
|
const fenceMatch = json.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
|
|
25648
25853
|
if (fenceMatch) json = fenceMatch[1].trim();
|
|
@@ -25650,16 +25855,27 @@ ${transcript.slice(0, 32e3)}`;
|
|
|
25650
25855
|
if (objMatch) json = objMatch[0];
|
|
25651
25856
|
try {
|
|
25652
25857
|
const parsed = JSON.parse(json);
|
|
25858
|
+
const sourceIndex = Array.isArray(parsed.sourceIndex) ? parsed.sourceIndex.map((s) => {
|
|
25859
|
+
const obj = s;
|
|
25860
|
+
return {
|
|
25861
|
+
index: typeof obj.index === "number" ? obj.index : parseInt(String(obj.index), 10) || 0,
|
|
25862
|
+
url: String(obj.url || "").trim(),
|
|
25863
|
+
title: String(obj.title || "").trim(),
|
|
25864
|
+
accessedAt: String(obj.accessedAt || "").trim(),
|
|
25865
|
+
supportingQuote: String(obj.supportingQuote || "").trim()
|
|
25866
|
+
};
|
|
25867
|
+
}).filter((s) => s.url && s.title) : [];
|
|
25868
|
+
const findingsByThread = Array.isArray(parsed.findingsByThread) ? parsed.findingsByThread.map((s) => {
|
|
25869
|
+
const obj = s;
|
|
25870
|
+
return {
|
|
25871
|
+
threadLabel: String(obj.threadLabel || "").trim(),
|
|
25872
|
+
content: String(obj.content || "").trim()
|
|
25873
|
+
};
|
|
25874
|
+
}) : [];
|
|
25653
25875
|
return {
|
|
25654
25876
|
title: String(parsed.title || objectives.researchQuestion).trim(),
|
|
25655
25877
|
executiveSummary: String(parsed.executiveSummary || "").trim(),
|
|
25656
|
-
findingsByThread:
|
|
25657
|
-
const obj = s;
|
|
25658
|
-
return {
|
|
25659
|
-
threadLabel: String(obj.threadLabel || "").trim(),
|
|
25660
|
-
content: String(obj.content || "").trim()
|
|
25661
|
-
};
|
|
25662
|
-
}) : [],
|
|
25878
|
+
findingsByThread: findingsByThread.length > 0 ? findingsByThread : buildFallbackFindingsByThread(findings),
|
|
25663
25879
|
contradictions: Array.isArray(parsed.contradictions) ? parsed.contradictions.map((c) => {
|
|
25664
25880
|
const obj = c;
|
|
25665
25881
|
const sourceA = obj.sourceA ?? {};
|
|
@@ -25680,31 +25896,13 @@ ${transcript.slice(0, 32e3)}`;
|
|
|
25680
25896
|
(c) => c.claim && c.sourceA.url && c.sourceB.url && c.resolution
|
|
25681
25897
|
) : [],
|
|
25682
25898
|
gaps: Array.isArray(parsed.gaps) ? parsed.gaps.map((g) => String(g).trim()).filter(Boolean) : [],
|
|
25683
|
-
sourceIndex:
|
|
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) : [],
|
|
25899
|
+
sourceIndex: sourceIndex.length > 0 ? sourceIndex : buildFallbackSourceIndex(findings),
|
|
25693
25900
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25694
25901
|
objectives
|
|
25695
25902
|
};
|
|
25696
25903
|
} catch (err) {
|
|
25697
|
-
logger$7.warn("Failed to parse synthesis JSON, using
|
|
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
|
-
};
|
|
25904
|
+
logger$7.warn("Failed to parse synthesis JSON, using sourced fallback report", err);
|
|
25905
|
+
return buildFallbackReport(objectives, findings, String(err));
|
|
25708
25906
|
}
|
|
25709
25907
|
}
|
|
25710
25908
|
// ── report management ──────────────────────────────────────────
|
|
@@ -27075,6 +27273,14 @@ const PREMIUM_TRACKABLE_STEPS = [
|
|
|
27075
27273
|
];
|
|
27076
27274
|
const premiumApiOrigin = process.env.VESSEL_PREMIUM_API ? new URL(process.env.VESSEL_PREMIUM_API).origin : "https://vesselpremium.quantaintellect.com";
|
|
27077
27275
|
function registerPremiumHandlers(tabManager, sendToRendererViews) {
|
|
27276
|
+
const trackPremiumStatusChange = (previousStatus, nextStatus, source) => {
|
|
27277
|
+
if (previousStatus === nextStatus) return;
|
|
27278
|
+
trackPremiumFunnel("premium_status_changed", {
|
|
27279
|
+
previous_status: previousStatus,
|
|
27280
|
+
new_status: nextStatus,
|
|
27281
|
+
source
|
|
27282
|
+
});
|
|
27283
|
+
};
|
|
27078
27284
|
const watchPremiumCheckoutTab = (tabId) => {
|
|
27079
27285
|
const tab = tabManager.getTab(tabId);
|
|
27080
27286
|
const wc = tab?.view.webContents;
|
|
@@ -27112,13 +27318,26 @@ function registerPremiumHandlers(tabManager, sendToRendererViews) {
|
|
|
27112
27318
|
return;
|
|
27113
27319
|
}
|
|
27114
27320
|
trackPremiumFunnel("auto_activation_attempted");
|
|
27321
|
+
trackPremiumFunnel("premium_verify_started", {
|
|
27322
|
+
source: "checkout_auto"
|
|
27323
|
+
});
|
|
27324
|
+
const previousStatus = getPremiumState().status;
|
|
27115
27325
|
const state2 = await verifySubscription(sessionId);
|
|
27116
27326
|
if (isPremiumActiveState(state2)) {
|
|
27117
27327
|
sendToRendererViews(Channels.PREMIUM_UPDATE, state2);
|
|
27328
|
+
trackPremiumFunnel("premium_verify_succeeded", {
|
|
27329
|
+
status: state2.status,
|
|
27330
|
+
source: "checkout_auto"
|
|
27331
|
+
});
|
|
27332
|
+
trackPremiumStatusChange(previousStatus, state2.status, "checkout_auto");
|
|
27118
27333
|
trackPremiumFunnel("auto_activation_succeeded", {
|
|
27119
27334
|
status: state2.status
|
|
27120
27335
|
});
|
|
27121
27336
|
} else {
|
|
27337
|
+
trackPremiumFunnel("premium_verify_failed", {
|
|
27338
|
+
status: state2.status,
|
|
27339
|
+
source: "checkout_auto"
|
|
27340
|
+
});
|
|
27122
27341
|
trackPremiumFunnel("auto_activation_failed", {
|
|
27123
27342
|
status: state2.status
|
|
27124
27343
|
});
|
|
@@ -27151,9 +27370,19 @@ function registerPremiumHandlers(tabManager, sendToRendererViews) {
|
|
|
27151
27370
|
return errorResult("Invalid email format");
|
|
27152
27371
|
}
|
|
27153
27372
|
trackPremiumFunnel("activation_attempted");
|
|
27373
|
+
trackPremiumFunnel("activation_code_requested", {
|
|
27374
|
+
source: "settings"
|
|
27375
|
+
});
|
|
27154
27376
|
const result = await requestActivationCode(email);
|
|
27155
27377
|
if (!result.ok) {
|
|
27378
|
+
trackPremiumFunnel("activation_code_failed", {
|
|
27379
|
+
source: "settings"
|
|
27380
|
+
});
|
|
27156
27381
|
trackPremiumFunnel("activation_failed");
|
|
27382
|
+
} else {
|
|
27383
|
+
trackPremiumFunnel("activation_code_sent", {
|
|
27384
|
+
source: "settings"
|
|
27385
|
+
});
|
|
27157
27386
|
}
|
|
27158
27387
|
return result;
|
|
27159
27388
|
});
|
|
@@ -27170,13 +27399,30 @@ function registerPremiumHandlers(tabManager, sendToRendererViews) {
|
|
|
27170
27399
|
});
|
|
27171
27400
|
}
|
|
27172
27401
|
trackPremiumFunnel("activation_attempted");
|
|
27402
|
+
trackPremiumFunnel("premium_verify_started", {
|
|
27403
|
+
source: "settings_code"
|
|
27404
|
+
});
|
|
27405
|
+
const previousStatus = getPremiumState().status;
|
|
27173
27406
|
const result = await verifyActivationCode(email, code, challengeToken);
|
|
27174
27407
|
if (result.ok) {
|
|
27408
|
+
trackPremiumFunnel("premium_verify_succeeded", {
|
|
27409
|
+
status: result.state.status,
|
|
27410
|
+
source: "settings_code"
|
|
27411
|
+
});
|
|
27175
27412
|
trackPremiumFunnel("activation_succeeded", {
|
|
27176
27413
|
status: result.state.status
|
|
27177
27414
|
});
|
|
27415
|
+
trackPremiumStatusChange(
|
|
27416
|
+
previousStatus,
|
|
27417
|
+
result.state.status,
|
|
27418
|
+
"settings_code"
|
|
27419
|
+
);
|
|
27178
27420
|
sendToRendererViews(Channels.PREMIUM_UPDATE, result.state);
|
|
27179
27421
|
} else {
|
|
27422
|
+
trackPremiumFunnel("premium_verify_failed", {
|
|
27423
|
+
status: result.state.status,
|
|
27424
|
+
source: "settings_code"
|
|
27425
|
+
});
|
|
27180
27426
|
trackPremiumFunnel("activation_failed", { status: result.state.status });
|
|
27181
27427
|
}
|
|
27182
27428
|
return result;
|
|
@@ -27189,6 +27435,8 @@ function registerPremiumHandlers(tabManager, sendToRendererViews) {
|
|
|
27189
27435
|
if (result.ok && result.url) {
|
|
27190
27436
|
const tabId = tabManager.createTab(result.url);
|
|
27191
27437
|
watchPremiumCheckoutTab(tabId);
|
|
27438
|
+
} else {
|
|
27439
|
+
trackPremiumFunnel("checkout_open_failed");
|
|
27192
27440
|
}
|
|
27193
27441
|
return result;
|
|
27194
27442
|
});
|
package/out/preload/index.js
CHANGED
|
@@ -204,6 +204,7 @@ const Channels = {
|
|
|
204
204
|
RESEARCH_SET_MODE: "research:set-mode",
|
|
205
205
|
RESEARCH_SET_TRACES: "research:set-traces",
|
|
206
206
|
RESEARCH_CANCEL: "research:cancel",
|
|
207
|
+
RESEARCH_STOP_AND_SYNTHESIZE: "research:stop-and-synthesize",
|
|
207
208
|
RESEARCH_EXPORT_REPORT: "research:export-report",
|
|
208
209
|
// Codex OAuth
|
|
209
210
|
CODEX_START_AUTH: "codex:start-auth",
|
|
@@ -332,6 +333,7 @@ const api = {
|
|
|
332
333
|
setMode: (mode) => electron.ipcRenderer.invoke(Channels.RESEARCH_SET_MODE, mode),
|
|
333
334
|
setTraces: (include) => electron.ipcRenderer.invoke(Channels.RESEARCH_SET_TRACES, include),
|
|
334
335
|
cancel: () => electron.ipcRenderer.invoke(Channels.RESEARCH_CANCEL),
|
|
336
|
+
stopAndSynthesize: () => electron.ipcRenderer.invoke(Channels.RESEARCH_STOP_AND_SYNTHESIZE),
|
|
335
337
|
exportReport: () => electron.ipcRenderer.invoke(Channels.RESEARCH_EXPORT_REPORT)
|
|
336
338
|
},
|
|
337
339
|
content: {
|