@quanta-intellect/vessel-browser 0.1.97 → 0.1.101
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",
|
|
@@ -25086,6 +25098,9 @@ function registerResearchHandlers(getOrchestrator) {
|
|
|
25086
25098
|
electron.ipcMain.handle(Channels.RESEARCH_CANCEL, () => {
|
|
25087
25099
|
getOrchestrator().cancel();
|
|
25088
25100
|
});
|
|
25101
|
+
electron.ipcMain.handle(Channels.RESEARCH_STOP_AND_SYNTHESIZE, () => {
|
|
25102
|
+
getOrchestrator().stopAndSynthesizeCurrentFindings();
|
|
25103
|
+
});
|
|
25089
25104
|
electron.ipcMain.handle(Channels.RESEARCH_EXPORT_REPORT, async () => {
|
|
25090
25105
|
try {
|
|
25091
25106
|
if (isToolGated("research_export_report")) {
|
|
@@ -25213,6 +25228,96 @@ const MAX_THREADS = 5;
|
|
|
25213
25228
|
function clone$1(value) {
|
|
25214
25229
|
return structuredClone(value);
|
|
25215
25230
|
}
|
|
25231
|
+
function normalizeSourceDomain(value) {
|
|
25232
|
+
const trimmed = value.trim().toLowerCase();
|
|
25233
|
+
if (!trimmed) return "";
|
|
25234
|
+
try {
|
|
25235
|
+
return new URL(
|
|
25236
|
+
trimmed.includes("://") ? trimmed : `https://${trimmed}`
|
|
25237
|
+
).hostname.replace(/^www\./, "");
|
|
25238
|
+
} catch {
|
|
25239
|
+
return trimmed.replace(/^www\./, "");
|
|
25240
|
+
}
|
|
25241
|
+
}
|
|
25242
|
+
function mergeBlockedSourceDomains(thread) {
|
|
25243
|
+
const globalBlocked = loadSettings().sourceDoNotAllowList.map(normalizeSourceDomain).filter(Boolean);
|
|
25244
|
+
if (globalBlocked.length === 0) return thread;
|
|
25245
|
+
const blockedDomains = Array.from(
|
|
25246
|
+
/* @__PURE__ */ new Set([
|
|
25247
|
+
...thread.blockedDomains.map(normalizeSourceDomain).filter(Boolean),
|
|
25248
|
+
...globalBlocked
|
|
25249
|
+
])
|
|
25250
|
+
);
|
|
25251
|
+
return {
|
|
25252
|
+
...thread,
|
|
25253
|
+
blockedDomains
|
|
25254
|
+
};
|
|
25255
|
+
}
|
|
25256
|
+
function matchesSourceDomain(hostname, domain) {
|
|
25257
|
+
return hostname === domain || hostname.endsWith(`.${domain}`);
|
|
25258
|
+
}
|
|
25259
|
+
function getBlockedSourceNavigation(url, blockedDomains) {
|
|
25260
|
+
if (typeof url !== "string" || blockedDomains.length === 0) return null;
|
|
25261
|
+
try {
|
|
25262
|
+
const hostname = new URL(url).hostname.toLowerCase().replace(/^www\./, "");
|
|
25263
|
+
return blockedDomains.find(
|
|
25264
|
+
(domain) => matchesSourceDomain(hostname, normalizeSourceDomain(domain))
|
|
25265
|
+
) ?? null;
|
|
25266
|
+
} catch {
|
|
25267
|
+
return null;
|
|
25268
|
+
}
|
|
25269
|
+
}
|
|
25270
|
+
function buildFallbackSourceIndex(findings) {
|
|
25271
|
+
const seen = /* @__PURE__ */ new Set();
|
|
25272
|
+
const sources = [];
|
|
25273
|
+
for (const claim of findings.flatMap((finding) => finding.claims)) {
|
|
25274
|
+
if (!claim.sourceUrl || seen.has(claim.sourceUrl)) continue;
|
|
25275
|
+
seen.add(claim.sourceUrl);
|
|
25276
|
+
sources.push({
|
|
25277
|
+
index: sources.length + 1,
|
|
25278
|
+
url: claim.sourceUrl,
|
|
25279
|
+
title: claim.sourceTitle || claim.sourceUrl,
|
|
25280
|
+
accessedAt: claim.extractedAt,
|
|
25281
|
+
supportingQuote: claim.extractedQuote
|
|
25282
|
+
});
|
|
25283
|
+
}
|
|
25284
|
+
return sources;
|
|
25285
|
+
}
|
|
25286
|
+
function citationForClaim(claim, sourceIndex) {
|
|
25287
|
+
const index = sourceIndex.find((source) => source.url === claim.sourceUrl)?.index ?? 0;
|
|
25288
|
+
return index > 0 ? `[${index}]` : "";
|
|
25289
|
+
}
|
|
25290
|
+
function buildFallbackFindingsByThread(findings, sourceIndex = buildFallbackSourceIndex(findings)) {
|
|
25291
|
+
return findings.map((finding) => {
|
|
25292
|
+
const claimLines = finding.claims.map((claim) => {
|
|
25293
|
+
const citation = citationForClaim(claim, sourceIndex);
|
|
25294
|
+
return citation ? `${claim.claim} ${citation}` : claim.claim;
|
|
25295
|
+
});
|
|
25296
|
+
return {
|
|
25297
|
+
threadLabel: finding.threadLabel,
|
|
25298
|
+
content: claimLines.length > 0 ? claimLines.join("\n\n") : `No citeable claims were extracted for this thread. ${finding.executionSummary}`
|
|
25299
|
+
};
|
|
25300
|
+
});
|
|
25301
|
+
}
|
|
25302
|
+
function buildFallbackReport(objectives, findings, reason) {
|
|
25303
|
+
const sourceIndex = buildFallbackSourceIndex(findings);
|
|
25304
|
+
const findingsByThread = buildFallbackFindingsByThread(findings, sourceIndex);
|
|
25305
|
+
const claimCount = findings.reduce(
|
|
25306
|
+
(sum, finding) => sum + finding.claims.length,
|
|
25307
|
+
0
|
|
25308
|
+
);
|
|
25309
|
+
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.`;
|
|
25310
|
+
return {
|
|
25311
|
+
title: objectives.researchQuestion,
|
|
25312
|
+
executiveSummary,
|
|
25313
|
+
findingsByThread,
|
|
25314
|
+
contradictions: [],
|
|
25315
|
+
gaps: [`Final synthesis JSON could not be parsed: ${reason}`],
|
|
25316
|
+
sourceIndex,
|
|
25317
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25318
|
+
objectives
|
|
25319
|
+
};
|
|
25320
|
+
}
|
|
25216
25321
|
class ResearchOrchestrator {
|
|
25217
25322
|
constructor(provider, tabManager, runtime2) {
|
|
25218
25323
|
this.provider = provider;
|
|
@@ -25225,6 +25330,8 @@ class ResearchOrchestrator {
|
|
|
25225
25330
|
runtime;
|
|
25226
25331
|
state;
|
|
25227
25332
|
updateListener = null;
|
|
25333
|
+
stopRequested = false;
|
|
25334
|
+
synthesizeAfterStop = false;
|
|
25228
25335
|
// ── state access ──────────────────────────────────────────────
|
|
25229
25336
|
initialState() {
|
|
25230
25337
|
return {
|
|
@@ -25233,6 +25340,7 @@ class ResearchOrchestrator {
|
|
|
25233
25340
|
includeTraces: false,
|
|
25234
25341
|
objectives: null,
|
|
25235
25342
|
threads: [],
|
|
25343
|
+
threadProgress: [],
|
|
25236
25344
|
threadFindings: [],
|
|
25237
25345
|
report: null,
|
|
25238
25346
|
subAgentTraces: [],
|
|
@@ -25265,9 +25373,30 @@ class ResearchOrchestrator {
|
|
|
25265
25373
|
this.emit();
|
|
25266
25374
|
}
|
|
25267
25375
|
cancel() {
|
|
25376
|
+
this.stopRequested = true;
|
|
25377
|
+
this.synthesizeAfterStop = false;
|
|
25378
|
+
this.provider?.cancel();
|
|
25268
25379
|
this.state = this.initialState();
|
|
25269
25380
|
this.emit();
|
|
25270
25381
|
}
|
|
25382
|
+
stopAndSynthesizeCurrentFindings() {
|
|
25383
|
+
if (this.state.phase !== "executing") {
|
|
25384
|
+
logger$7.warn("Not executing, ignoring stopAndSynthesizeCurrentFindings");
|
|
25385
|
+
return;
|
|
25386
|
+
}
|
|
25387
|
+
this.stopRequested = true;
|
|
25388
|
+
this.synthesizeAfterStop = true;
|
|
25389
|
+
this.state.threadProgress = this.state.threadProgress.map(
|
|
25390
|
+
(progress) => progress.status === "completed" || progress.status === "failed" ? progress : {
|
|
25391
|
+
...progress,
|
|
25392
|
+
status: "stopping",
|
|
25393
|
+
message: "Stopping and preparing to synthesize current findings",
|
|
25394
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
25395
|
+
}
|
|
25396
|
+
);
|
|
25397
|
+
this.emit();
|
|
25398
|
+
this.provider?.cancel();
|
|
25399
|
+
}
|
|
25271
25400
|
/**
|
|
25272
25401
|
* Swap the AI provider used by this orchestrator.
|
|
25273
25402
|
* Safe to call while research is in progress — running sub-agents
|
|
@@ -25313,8 +25442,12 @@ class ResearchOrchestrator {
|
|
|
25313
25442
|
logger$7.warn("Not in planning phase, ignoring setObjectives");
|
|
25314
25443
|
return;
|
|
25315
25444
|
}
|
|
25316
|
-
|
|
25317
|
-
this.state.
|
|
25445
|
+
const threads = objectives.threads.slice(0, MAX_THREADS).map(mergeBlockedSourceDomains);
|
|
25446
|
+
this.state.objectives = {
|
|
25447
|
+
...objectives,
|
|
25448
|
+
threads
|
|
25449
|
+
};
|
|
25450
|
+
this.state.threads = threads;
|
|
25318
25451
|
this.setPhase("awaiting_approval");
|
|
25319
25452
|
}
|
|
25320
25453
|
/**
|
|
@@ -25383,8 +25516,28 @@ class ResearchOrchestrator {
|
|
|
25383
25516
|
}
|
|
25384
25517
|
if (mode) this.state.supervisionMode = mode;
|
|
25385
25518
|
if (includeTraces !== void 0) this.state.includeTraces = includeTraces;
|
|
25519
|
+
this.stopRequested = false;
|
|
25520
|
+
this.synthesizeAfterStop = false;
|
|
25521
|
+
this.state.threadFindings = [];
|
|
25522
|
+
this.state.threadProgress = this.state.threads.map((thread) => ({
|
|
25523
|
+
threadLabel: thread.label,
|
|
25524
|
+
status: "queued",
|
|
25525
|
+
message: "Queued",
|
|
25526
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
25527
|
+
}));
|
|
25386
25528
|
this.setPhase("executing");
|
|
25387
25529
|
}
|
|
25530
|
+
updateThreadProgress(threadLabel, status, message) {
|
|
25531
|
+
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
25532
|
+
const existingIndex = this.state.threadProgress.findIndex(
|
|
25533
|
+
(progress) => progress.threadLabel === threadLabel
|
|
25534
|
+
);
|
|
25535
|
+
const next = { threadLabel, status, message, updatedAt };
|
|
25536
|
+
this.state.threadProgress = existingIndex >= 0 ? this.state.threadProgress.map(
|
|
25537
|
+
(progress, index) => index === existingIndex ? next : progress
|
|
25538
|
+
) : [...this.state.threadProgress, next];
|
|
25539
|
+
this.emit();
|
|
25540
|
+
}
|
|
25388
25541
|
// ── phase: executing → synthesizing ────────────────────────────
|
|
25389
25542
|
async executeSubAgents() {
|
|
25390
25543
|
if (this.state.phase !== "executing" || !this.state.objectives) return;
|
|
@@ -25404,8 +25557,20 @@ class ResearchOrchestrator {
|
|
|
25404
25557
|
});
|
|
25405
25558
|
})
|
|
25406
25559
|
);
|
|
25560
|
+
const shouldSynthesize = this.synthesizeAfterStop;
|
|
25407
25561
|
if (this.state.phase !== "executing") return;
|
|
25408
25562
|
this.state.threadFindings = results.filter((f) => f !== null);
|
|
25563
|
+
this.stopRequested = false;
|
|
25564
|
+
this.synthesizeAfterStop = false;
|
|
25565
|
+
if (!shouldSynthesize) {
|
|
25566
|
+
for (const finding of this.state.threadFindings) {
|
|
25567
|
+
this.updateThreadProgress(
|
|
25568
|
+
finding.threadLabel,
|
|
25569
|
+
finding.claims.length > 0 ? "completed" : "failed",
|
|
25570
|
+
finding.claims.length > 0 ? `${finding.claims.length} claim${finding.claims.length === 1 ? "" : "s"} extracted` : "No citeable claims extracted"
|
|
25571
|
+
);
|
|
25572
|
+
}
|
|
25573
|
+
}
|
|
25409
25574
|
this.setPhase("synthesizing");
|
|
25410
25575
|
try {
|
|
25411
25576
|
await this.synthesizeReport();
|
|
@@ -25426,6 +25591,7 @@ class ResearchOrchestrator {
|
|
|
25426
25591
|
};
|
|
25427
25592
|
const tabId = this.tabManager.createTab();
|
|
25428
25593
|
let sourcesConsumed = 0;
|
|
25594
|
+
this.updateThreadProgress(thread.label, "running", "Researching sources");
|
|
25429
25595
|
if (tabId) this.tabManager.switchTab(tabId);
|
|
25430
25596
|
const discardedSources = [];
|
|
25431
25597
|
let transcript = "";
|
|
@@ -25454,10 +25620,32 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
|
|
|
25454
25620
|
},
|
|
25455
25621
|
async (name, args) => {
|
|
25456
25622
|
const t0 = Date.now();
|
|
25457
|
-
if (this.state.phase !== "executing") {
|
|
25623
|
+
if (this.state.phase !== "executing" || this.stopRequested) {
|
|
25458
25624
|
const msg = "Research cancelled — stopping.";
|
|
25459
25625
|
return msg;
|
|
25460
25626
|
}
|
|
25627
|
+
if (name === "navigate") {
|
|
25628
|
+
const blockedDomain = getBlockedSourceNavigation(
|
|
25629
|
+
args.url,
|
|
25630
|
+
thread.blockedDomains
|
|
25631
|
+
);
|
|
25632
|
+
if (blockedDomain) {
|
|
25633
|
+
const msg = `Source skipped: ${String(args.url)} matches the Research Desk source do-not-allow list (${blockedDomain}). Choose a different source.`;
|
|
25634
|
+
discardedSources.push({
|
|
25635
|
+
url: String(args.url || ""),
|
|
25636
|
+
title: String(args.url || "excluded source"),
|
|
25637
|
+
reason: msg
|
|
25638
|
+
});
|
|
25639
|
+
trace.toolCalls.push({
|
|
25640
|
+
tool: name,
|
|
25641
|
+
args,
|
|
25642
|
+
result: msg,
|
|
25643
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25644
|
+
durationMs: 0
|
|
25645
|
+
});
|
|
25646
|
+
return msg;
|
|
25647
|
+
}
|
|
25648
|
+
}
|
|
25461
25649
|
if (name === "navigate" || name === "search") {
|
|
25462
25650
|
sourcesConsumed++;
|
|
25463
25651
|
if (sourcesConsumed > thread.sourceBudget) {
|
|
@@ -25513,6 +25701,9 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
|
|
|
25513
25701
|
message: String(err),
|
|
25514
25702
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
25515
25703
|
});
|
|
25704
|
+
if (this.state.phase === "executing") {
|
|
25705
|
+
this.updateThreadProgress(thread.label, "stopping", "Stopping thread");
|
|
25706
|
+
}
|
|
25516
25707
|
} finally {
|
|
25517
25708
|
trace.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
25518
25709
|
if (tabId) {
|
|
@@ -25537,6 +25728,13 @@ Start by searching for: ${thread.searchQueries.join(" or ")}`;
|
|
|
25537
25728
|
const pagesVisited = trace.toolCalls.filter(
|
|
25538
25729
|
(t) => ["navigate", "read_page", "search"].includes(t.tool)
|
|
25539
25730
|
).length;
|
|
25731
|
+
if (this.state.phase === "executing") {
|
|
25732
|
+
this.updateThreadProgress(
|
|
25733
|
+
thread.label,
|
|
25734
|
+
claims.length > 0 ? "completed" : this.stopRequested ? "stopping" : "failed",
|
|
25735
|
+
claims.length > 0 ? `${claims.length} claim${claims.length === 1 ? "" : "s"} extracted` : this.stopRequested ? "Stopped before citeable claims were extracted" : "No citeable claims extracted"
|
|
25736
|
+
);
|
|
25737
|
+
}
|
|
25540
25738
|
return {
|
|
25541
25739
|
threadLabel: thread.label,
|
|
25542
25740
|
threadQuestion: thread.question,
|
|
@@ -25633,7 +25831,7 @@ ${transcript.slice(0, 32e3)}`;
|
|
|
25633
25831
|
() => {
|
|
25634
25832
|
}
|
|
25635
25833
|
);
|
|
25636
|
-
const report = this.parseReportFromJson(response, objectives);
|
|
25834
|
+
const report = this.parseReportFromJson(response, objectives, findings);
|
|
25637
25835
|
this.setReport(report);
|
|
25638
25836
|
this.setPhase("delivered");
|
|
25639
25837
|
return report;
|
|
@@ -25642,7 +25840,7 @@ ${transcript.slice(0, 32e3)}`;
|
|
|
25642
25840
|
* Parse the LLM's JSON synthesis response into a structured ResearchReport.
|
|
25643
25841
|
* Handles both bare JSON and JSON wrapped in markdown fences.
|
|
25644
25842
|
*/
|
|
25645
|
-
parseReportFromJson(text, objectives) {
|
|
25843
|
+
parseReportFromJson(text, objectives, findings) {
|
|
25646
25844
|
let json = text.trim();
|
|
25647
25845
|
const fenceMatch = json.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
|
|
25648
25846
|
if (fenceMatch) json = fenceMatch[1].trim();
|
|
@@ -25650,16 +25848,27 @@ ${transcript.slice(0, 32e3)}`;
|
|
|
25650
25848
|
if (objMatch) json = objMatch[0];
|
|
25651
25849
|
try {
|
|
25652
25850
|
const parsed = JSON.parse(json);
|
|
25851
|
+
const sourceIndex = Array.isArray(parsed.sourceIndex) ? parsed.sourceIndex.map((s) => {
|
|
25852
|
+
const obj = s;
|
|
25853
|
+
return {
|
|
25854
|
+
index: typeof obj.index === "number" ? obj.index : parseInt(String(obj.index), 10) || 0,
|
|
25855
|
+
url: String(obj.url || "").trim(),
|
|
25856
|
+
title: String(obj.title || "").trim(),
|
|
25857
|
+
accessedAt: String(obj.accessedAt || "").trim(),
|
|
25858
|
+
supportingQuote: String(obj.supportingQuote || "").trim()
|
|
25859
|
+
};
|
|
25860
|
+
}).filter((s) => s.url && s.title) : [];
|
|
25861
|
+
const findingsByThread = Array.isArray(parsed.findingsByThread) ? parsed.findingsByThread.map((s) => {
|
|
25862
|
+
const obj = s;
|
|
25863
|
+
return {
|
|
25864
|
+
threadLabel: String(obj.threadLabel || "").trim(),
|
|
25865
|
+
content: String(obj.content || "").trim()
|
|
25866
|
+
};
|
|
25867
|
+
}) : [];
|
|
25653
25868
|
return {
|
|
25654
25869
|
title: String(parsed.title || objectives.researchQuestion).trim(),
|
|
25655
25870
|
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
|
-
}) : [],
|
|
25871
|
+
findingsByThread: findingsByThread.length > 0 ? findingsByThread : buildFallbackFindingsByThread(findings),
|
|
25663
25872
|
contradictions: Array.isArray(parsed.contradictions) ? parsed.contradictions.map((c) => {
|
|
25664
25873
|
const obj = c;
|
|
25665
25874
|
const sourceA = obj.sourceA ?? {};
|
|
@@ -25680,31 +25889,13 @@ ${transcript.slice(0, 32e3)}`;
|
|
|
25680
25889
|
(c) => c.claim && c.sourceA.url && c.sourceB.url && c.resolution
|
|
25681
25890
|
) : [],
|
|
25682
25891
|
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) : [],
|
|
25892
|
+
sourceIndex: sourceIndex.length > 0 ? sourceIndex : buildFallbackSourceIndex(findings),
|
|
25693
25893
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25694
25894
|
objectives
|
|
25695
25895
|
};
|
|
25696
25896
|
} 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
|
-
};
|
|
25897
|
+
logger$7.warn("Failed to parse synthesis JSON, using sourced fallback report", err);
|
|
25898
|
+
return buildFallbackReport(objectives, findings, String(err));
|
|
25708
25899
|
}
|
|
25709
25900
|
}
|
|
25710
25901
|
// ── report management ──────────────────────────────────────────
|
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: {
|