research-copilot 0.2.11 → 0.2.14
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/app/out/main/index.mjs +239 -49
- package/app/out/preload/index.js +2 -0
- package/app/out/renderer/assets/{MilkdownMarkdownEditor-BqfydyHs.js → MilkdownMarkdownEditor-BX4GvUB0.js} +50 -50
- package/app/out/renderer/assets/{arc-B102x0uC.js → arc-BV5qVZeX.js} +1 -1
- package/app/out/renderer/assets/{blockDiagram-c4efeb88-hlEjcPtb.js → blockDiagram-c4efeb88-B4EHe6ol.js} +8 -8
- package/app/out/renderer/assets/{c4Diagram-c83219d4-Dc1nfavC.js → c4Diagram-c83219d4-iDfyIJbZ.js} +3 -3
- package/app/out/renderer/assets/{channel-ZMdB8bH1.js → channel-Cpwy9r1z.js} +1 -1
- package/app/out/renderer/assets/{classDiagram-beda092f-Gbpax7vO.js → classDiagram-beda092f-D053CRJR.js} +6 -6
- package/app/out/renderer/assets/{classDiagram-v2-2358418a-_KwHXPjs.js → classDiagram-v2-2358418a-B7s6a7vM.js} +10 -10
- package/app/out/renderer/assets/{clone-DDahKPIj.js → clone-pbZD71xt.js} +1 -1
- package/app/out/renderer/assets/{createText-1719965b-BNW0X7Oe.js → createText-1719965b-D25dhEwh.js} +2 -2
- package/app/out/renderer/assets/{edges-96097737-G9l1oUoZ.js → edges-96097737-BydHfBnP.js} +3 -3
- package/app/out/renderer/assets/{erDiagram-0228fc6a-BUs4XtQY.js → erDiagram-0228fc6a-4tnYtIdv.js} +5 -5
- package/app/out/renderer/assets/{flowDb-c6c81e3f-CQ-jcycx.js → flowDb-c6c81e3f-CY6JAmVt.js} +1 -1
- package/app/out/renderer/assets/{flowDiagram-50d868cf-uu8ab--w.js → flowDiagram-50d868cf-DF_znPBp.js} +12 -12
- package/app/out/renderer/assets/{flowDiagram-v2-4f6560a1-CAw9BkoT.js → flowDiagram-v2-4f6560a1-CFOGKf9t.js} +12 -12
- package/app/out/renderer/assets/{flowchart-elk-definition-6af322e1-BjQ21EHx.js → flowchart-elk-definition-6af322e1-YLx5Qxt0.js} +6 -6
- package/app/out/renderer/assets/{ganttDiagram-a2739b55-CP8oCEhK.js → ganttDiagram-a2739b55-qZme1oAP.js} +3 -3
- package/app/out/renderer/assets/{gitGraphDiagram-82fe8481-BwBuEj3Z.js → gitGraphDiagram-82fe8481-DFYMRsVI.js} +2 -2
- package/app/out/renderer/assets/{graph-Cm7VMO24.js → graph-BXAfTdpJ.js} +1 -1
- package/app/out/renderer/assets/{index-5325376f-rxU_OBcM.js → index-5325376f-BHpHsMhb.js} +6 -6
- package/app/out/renderer/assets/{index-CXW3LNrw.js → index-BA2tAI_M.js} +6 -6
- package/app/out/renderer/assets/{index-qYYoWrK0.js → index-BE8bYtp7.js} +3 -3
- package/app/out/renderer/assets/{index-C90yL_Oq.js → index-BG7PX3k8.js} +114 -72
- package/app/out/renderer/assets/{index-21ZazB27.js → index-BO9tGk26.js} +3 -3
- package/app/out/renderer/assets/{index-PcwO-UQr.js → index-BgOxdzKV.js} +4 -4
- package/app/out/renderer/assets/{index-CnSaLake.js → index-Bj9LQiuY.js} +6 -6
- package/app/out/renderer/assets/{index-Cnl3rlb4.js → index-Bx4kt3Ep.js} +3 -3
- package/app/out/renderer/assets/{index-JxH0rooB.js → index-C1UejWJF.js} +6 -6
- package/app/out/renderer/assets/{index-B0bfGE4l.js → index-CQ3AUQjH.js} +3 -3
- package/app/out/renderer/assets/{index-YHTq32NV.js → index-CQ7kmaED.js} +3 -3
- package/app/out/renderer/assets/{index-D075LvBi.js → index-CUHnEfWQ.js} +3 -3
- package/app/out/renderer/assets/{index-UvRVROtt.js → index-CXKb_B6Q.js} +3 -3
- package/app/out/renderer/assets/{index-CdJBtB9B.js → index-CkhHGu8F.js} +6 -6
- package/app/out/renderer/assets/{index-B86Rm5VH.js → index-DM53DzJy.js} +5 -5
- package/app/out/renderer/assets/{index-DQ1LKaqr.js → index-DPsH0d7l.js} +6 -6
- package/app/out/renderer/assets/{index-CZmZWnTr.js → index-DQAavBDm.js} +3 -3
- package/app/out/renderer/assets/{index-CmuzUeEb.js → index-DcR5rCVe.js} +3 -3
- package/app/out/renderer/assets/{index-BOSzW1Fp.js → index-DdJol6dy.js} +3 -3
- package/app/out/renderer/assets/{index-CYcmNTWS.js → index-DmFXl1ev.js} +1 -1
- package/app/out/renderer/assets/{index-BurA4U4Q.js → index-Dr-t9DZm.js} +4 -4
- package/app/out/renderer/assets/{index-DwyuGyq2.js → index-ZTBCB5Su.js} +3 -3
- package/app/out/renderer/assets/{index-BireTC8B.js → index-rmxKZnZp.js} +6 -6
- package/app/out/renderer/assets/{index-DnIy5Txo.js → index-uewGaFvs.js} +3 -3
- package/app/out/renderer/assets/{infoDiagram-8eee0895-DX3y3-A9.js → infoDiagram-8eee0895-ByEFQmpu.js} +2 -2
- package/app/out/renderer/assets/{journeyDiagram-c64418c1-CWFt3XGT.js → journeyDiagram-c64418c1-3-T1DDmD.js} +4 -4
- package/app/out/renderer/assets/{layout-BlEAfCmy.js → layout-CQUDsAvc.js} +2 -2
- package/app/out/renderer/assets/{line-C8wQDOie.js → line-DxIlbhab.js} +1 -1
- package/app/out/renderer/assets/{linear-B-vozVTM.js → linear-Bn3sUUKK.js} +1 -1
- package/app/out/renderer/assets/{mindmap-definition-8da855dc-BCWxRP64.js → mindmap-definition-8da855dc-Csu-LGhp.js} +3 -3
- package/app/out/renderer/assets/{pieDiagram-a8764435-CjTt_TYd.js → pieDiagram-a8764435-CuLtSzv9.js} +3 -3
- package/app/out/renderer/assets/{quadrantDiagram-1e28029f-D1Ke9L8a.js → quadrantDiagram-1e28029f-Bl59fpd9.js} +3 -3
- package/app/out/renderer/assets/{requirementDiagram-08caed73-qZ5fBq-x.js → requirementDiagram-08caed73-DC3vhRtq.js} +5 -5
- package/app/out/renderer/assets/{sankeyDiagram-a04cb91d-B-KtJYmc.js → sankeyDiagram-a04cb91d-BMU-49qE.js} +2 -2
- package/app/out/renderer/assets/{sequenceDiagram-c5b8d532-BjqWnQP_.js → sequenceDiagram-c5b8d532-Bkcjg7DD.js} +3 -3
- package/app/out/renderer/assets/{stateDiagram-1ecb1508-DQo_1-ne.js → stateDiagram-1ecb1508-C1C8BcZQ.js} +6 -6
- package/app/out/renderer/assets/{stateDiagram-v2-c2b004d7-BNb4d-sS.js → stateDiagram-v2-c2b004d7-DSiaz6V7.js} +10 -10
- package/app/out/renderer/assets/{styles-b4e223ce-pjL5kdCz.js → styles-b4e223ce-DR414-dj.js} +1 -1
- package/app/out/renderer/assets/{styles-ca3715f6-CtyHB9Sz.js → styles-ca3715f6-CoJirLYO.js} +1 -1
- package/app/out/renderer/assets/{styles-d45a18b0-r6zLUSmM.js → styles-d45a18b0-Dbanopw_.js} +4 -4
- package/app/out/renderer/assets/{svgDrawCommon-b86b1483-CbHYm_eu.js → svgDrawCommon-b86b1483-RvKK2C6p.js} +1 -1
- package/app/out/renderer/assets/{timeline-definition-faaaa080-DvoW_Frb.js → timeline-definition-faaaa080-BG9xYjlZ.js} +3 -3
- package/app/out/renderer/assets/{xychartDiagram-f5964ef8-C-G_0ZnR.js → xychartDiagram-f5964ef8-kiztprx8.js} +5 -5
- package/app/out/renderer/index.html +1 -1
- package/package.json +1 -1
package/app/out/main/index.mjs
CHANGED
|
@@ -633,6 +633,43 @@ function registerUsageHandlers(handle, getCtx, loadUsageTotals2, resetUsageTotal
|
|
|
633
633
|
return resetUsageTotals2(baseDir);
|
|
634
634
|
});
|
|
635
635
|
}
|
|
636
|
+
const LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
637
|
+
const inFlightLogin = /* @__PURE__ */ new Map();
|
|
638
|
+
function loginErrorMessage(raw, label) {
|
|
639
|
+
const msg = (raw instanceof Error ? raw.message : String(raw ?? "")).trim();
|
|
640
|
+
return msg || `${label} OAuth login failed`;
|
|
641
|
+
}
|
|
642
|
+
async function runLoginWithTimeout(key, label, runLogin, onSuccess) {
|
|
643
|
+
if (inFlightLogin.has(key)) {
|
|
644
|
+
return { success: false, error: `${label} sign-in is already in progress. Cancel it first.` };
|
|
645
|
+
}
|
|
646
|
+
let rejectManual = () => {
|
|
647
|
+
};
|
|
648
|
+
const manualPromise = new Promise((_, reject) => {
|
|
649
|
+
rejectManual = reject;
|
|
650
|
+
});
|
|
651
|
+
manualPromise.catch(() => {
|
|
652
|
+
});
|
|
653
|
+
const attempt = {
|
|
654
|
+
abort: (reason) => rejectManual(new Error(reason))
|
|
655
|
+
};
|
|
656
|
+
inFlightLogin.set(key, attempt);
|
|
657
|
+
const timer = setTimeout(() => {
|
|
658
|
+
attempt.abort(
|
|
659
|
+
`${label} sign-in timed out after ${Math.round(LOGIN_TIMEOUT_MS / 6e4)} minutes. Please try again.`
|
|
660
|
+
);
|
|
661
|
+
}, LOGIN_TIMEOUT_MS);
|
|
662
|
+
try {
|
|
663
|
+
const creds = await runLogin(() => manualPromise);
|
|
664
|
+
onSuccess(creds);
|
|
665
|
+
return { success: true };
|
|
666
|
+
} catch (err) {
|
|
667
|
+
return { success: false, error: loginErrorMessage(err, label) };
|
|
668
|
+
} finally {
|
|
669
|
+
clearTimeout(timer);
|
|
670
|
+
if (inFlightLogin.get(key) === attempt) inFlightLogin.delete(key);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
636
673
|
function registerAuthHandlers(handleRaw) {
|
|
637
674
|
handleRaw("auth:get-anthropic-status", () => {
|
|
638
675
|
const hasApiKey = !!(process.env.ANTHROPIC_API_KEY || "").trim();
|
|
@@ -659,8 +696,10 @@ function registerAuthHandlers(handleRaw) {
|
|
|
659
696
|
handleRaw("auth:anthropic-sub-login", async () => {
|
|
660
697
|
const { loginAnthropic } = await import("@mariozechner/pi-ai/oauth");
|
|
661
698
|
const { shell: shell2 } = await import("electron");
|
|
662
|
-
|
|
663
|
-
|
|
699
|
+
return runLoginWithTimeout(
|
|
700
|
+
"anthropic-sub",
|
|
701
|
+
"Anthropic",
|
|
702
|
+
(onManualCodeInput) => loginAnthropic({
|
|
664
703
|
onAuth: (info) => {
|
|
665
704
|
shell2.openExternal(info.url);
|
|
666
705
|
},
|
|
@@ -670,13 +709,17 @@ function registerAuthHandlers(handleRaw) {
|
|
|
670
709
|
},
|
|
671
710
|
onProgress: (msg) => {
|
|
672
711
|
console.log("[OAuth Anthropic]", msg);
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
712
|
+
},
|
|
713
|
+
onManualCodeInput
|
|
714
|
+
}),
|
|
715
|
+
(creds) => saveAnthropicSubCredentials(creds)
|
|
716
|
+
);
|
|
717
|
+
});
|
|
718
|
+
handleRaw("auth:anthropic-sub-cancel", () => {
|
|
719
|
+
const attempt = inFlightLogin.get("anthropic-sub");
|
|
720
|
+
if (!attempt) return { success: false, error: "No Anthropic sign-in in progress" };
|
|
721
|
+
attempt.abort("Anthropic sign-in cancelled.");
|
|
722
|
+
return { success: true };
|
|
680
723
|
});
|
|
681
724
|
handleRaw("auth:anthropic-sub-logout", () => {
|
|
682
725
|
clearAnthropicSubCredentials();
|
|
@@ -704,8 +747,10 @@ function registerAuthHandlers(handleRaw) {
|
|
|
704
747
|
handleRaw("auth:openai-codex-login", async () => {
|
|
705
748
|
const { loginOpenAICodex } = await import("@mariozechner/pi-ai/oauth");
|
|
706
749
|
const { shell: shell2 } = await import("electron");
|
|
707
|
-
|
|
708
|
-
|
|
750
|
+
return runLoginWithTimeout(
|
|
751
|
+
"openai-codex",
|
|
752
|
+
"ChatGPT",
|
|
753
|
+
(onManualCodeInput) => loginOpenAICodex({
|
|
709
754
|
onAuth: (info) => {
|
|
710
755
|
shell2.openExternal(info.url);
|
|
711
756
|
},
|
|
@@ -715,13 +760,17 @@ function registerAuthHandlers(handleRaw) {
|
|
|
715
760
|
},
|
|
716
761
|
onProgress: (msg) => {
|
|
717
762
|
console.log("[OAuth]", msg);
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
763
|
+
},
|
|
764
|
+
onManualCodeInput
|
|
765
|
+
}),
|
|
766
|
+
(creds) => saveCodexCredentials(creds)
|
|
767
|
+
);
|
|
768
|
+
});
|
|
769
|
+
handleRaw("auth:openai-codex-cancel", () => {
|
|
770
|
+
const attempt = inFlightLogin.get("openai-codex");
|
|
771
|
+
if (!attempt) return { success: false, error: "No ChatGPT sign-in in progress" };
|
|
772
|
+
attempt.abort("ChatGPT sign-in cancelled.");
|
|
773
|
+
return { success: true };
|
|
725
774
|
});
|
|
726
775
|
handleRaw("auth:openai-codex-logout", () => {
|
|
727
776
|
clearCodexCredentials();
|
|
@@ -1255,6 +1304,32 @@ function readLatestSessionSummary(projectPath, sessionId) {
|
|
|
1255
1304
|
});
|
|
1256
1305
|
return readJson(join(dir, files[0]), null);
|
|
1257
1306
|
}
|
|
1307
|
+
function readOrphanMessages(projectPath, sessionId, cutoffMs) {
|
|
1308
|
+
const file = join(projectPath, PATHS.sessions, `${sessionId}.jsonl`);
|
|
1309
|
+
if (!existsSync(file)) return [];
|
|
1310
|
+
const result = [];
|
|
1311
|
+
let raw;
|
|
1312
|
+
try {
|
|
1313
|
+
raw = readFileSync(file, "utf-8");
|
|
1314
|
+
} catch {
|
|
1315
|
+
return [];
|
|
1316
|
+
}
|
|
1317
|
+
for (const line of raw.split("\n")) {
|
|
1318
|
+
if (!line) continue;
|
|
1319
|
+
let msg;
|
|
1320
|
+
try {
|
|
1321
|
+
msg = JSON.parse(line);
|
|
1322
|
+
} catch {
|
|
1323
|
+
continue;
|
|
1324
|
+
}
|
|
1325
|
+
if (!msg || typeof msg !== "object") continue;
|
|
1326
|
+
if (msg.role !== "user" && msg.role !== "assistant") continue;
|
|
1327
|
+
if (typeof msg.timestamp !== "number" || msg.timestamp <= cutoffMs) continue;
|
|
1328
|
+
if (typeof msg.content !== "string" || msg.content.length === 0) continue;
|
|
1329
|
+
result.push({ role: msg.role, content: msg.content, timestamp: msg.timestamp });
|
|
1330
|
+
}
|
|
1331
|
+
return result;
|
|
1332
|
+
}
|
|
1258
1333
|
function generateCiteKey$1(authors, year, title) {
|
|
1259
1334
|
const firstAuthor = authors[0] || "unknown";
|
|
1260
1335
|
const lastName = firstAuthor.split(/\s+/).pop()?.toLowerCase() || "unknown";
|
|
@@ -2619,29 +2694,30 @@ If conversation history contains previous literature-search results with coverag
|
|
|
2619
2694
|
## Rules
|
|
2620
2695
|
|
|
2621
2696
|
1. You MUST provide a \`relevanceJustification\` for EVERY paper explaining WHY it received that score
|
|
2622
|
-
2. After scoring all papers, perform a FORCED RANKING: cut the bottom 30% — papers in the bottom 30% get excluded from
|
|
2697
|
+
2. After scoring all papers, perform a FORCED RANKING: cut the bottom 30% — papers in the bottom 30% get excluded from scoredPapers even if their score is above threshold
|
|
2623
2698
|
3. Auto-save threshold is **>= 7**. Papers scoring 7+ are saved to the local library. Be decisive: if a paper is meaningfully relevant (not just tangential), score it >= 7.
|
|
2624
2699
|
4. Approve only if at least 3 papers score >= 7 AND coverage >= 0.5. If confidence is low or critical coverage is missing, request targeted refinement.
|
|
2625
2700
|
5. If not approved, suggest at most 2-3 **targeted refinement queries** for specific missing sub-topics — NOT broad re-searches. These queries run through the FULL search pipeline again, so be selective. CRITICAL: Your refinement queries MUST be DIFFERENT from the "Queries used" listed at the bottom — the system will reject duplicate queries. Use different terminology, synonyms, or narrower/broader scope to find what the original queries missed
|
|
2626
2701
|
6. Track cumulative coverage across sub-topics
|
|
2627
|
-
7. Output size guard: include AT MOST 25
|
|
2702
|
+
7. Output size guard: include AT MOST 25 scoredPapers. If there are any reasonably relevant papers, include at least 3 (do NOT return an empty list unless ZERO papers are even tangentially relevant).
|
|
2628
2703
|
|
|
2629
|
-
##
|
|
2704
|
+
## CRITICAL: Compact output contract
|
|
2630
2705
|
|
|
2631
|
-
|
|
2632
|
-
- id, title, authors (full array), abstract (full text if possible; may truncate if very long), year, url
|
|
2633
|
-
- source (e.g. "semantic_scholar", "arxiv", "openalex", "dblp", "local")
|
|
2634
|
-
- relevanceScore (your 0-10 rating), relevanceJustification (1-2 sentence explanation)
|
|
2635
|
-
- doi (string or null), venue (string or null), citationCount (number or null)
|
|
2706
|
+
The caller already holds the full paper metadata (title, authors, abstract, venue, doi, year, url, citationCount, source). You MUST NOT echo any of it back. Your ONLY job is to return scoring decisions keyed by the paper's **index number** — the 1-based number shown at the start of each paper line in the input (e.g. \`1. [semantic_scholar] "Foo..."\` → \`index: 1\`).
|
|
2636
2707
|
|
|
2637
|
-
|
|
2708
|
+
- Output ONLY integers for \`index\`, integers 0-10 for \`relevanceScore\`, and a short 1-2 sentence \`relevanceJustification\`.
|
|
2709
|
+
- Do NOT include title, authors, abstract, venue, doi, year, url, source, or citationCount fields in scoredPapers. Those will be merged by the caller.
|
|
2710
|
+
- Every \`index\` MUST refer to a paper actually shown in the input. Do not invent indices.
|
|
2711
|
+
- Do not wrap the JSON in markdown fences unless strictly necessary; raw JSON is preferred.
|
|
2712
|
+
|
|
2713
|
+
Keeping the output compact is REQUIRED — echoing metadata caused past responses to be truncated, making the entire review unparseable.
|
|
2638
2714
|
|
|
2639
2715
|
## Output JSON
|
|
2640
2716
|
|
|
2641
2717
|
{
|
|
2642
2718
|
"approved": boolean,
|
|
2643
|
-
"
|
|
2644
|
-
{ "
|
|
2719
|
+
"scoredPapers": [
|
|
2720
|
+
{ "index": 1, "relevanceScore": 9, "relevanceJustification": "1-2 sentence explanation" }
|
|
2645
2721
|
],
|
|
2646
2722
|
"confidence": number,
|
|
2647
2723
|
"coverage": {
|
|
@@ -3427,20 +3503,46 @@ Additional context: ${extraContext}` : `Research request: ${query}`;
|
|
|
3427
3503
|
try {
|
|
3428
3504
|
const reviewText = await ctx.callLlm(REVIEWER_SYSTEM, reviewInput);
|
|
3429
3505
|
const parsed = safeJsonParse(reviewText);
|
|
3430
|
-
if (!parsed) {
|
|
3506
|
+
if (!parsed || !Array.isArray(parsed.scoredPapers)) {
|
|
3507
|
+
const fallbackCap = ctx.settings?.researchIntensity?.reviewCap ?? 25;
|
|
3431
3508
|
review = {
|
|
3432
3509
|
approved: true,
|
|
3433
|
-
relevantPapers: deduplicated.slice(0,
|
|
3434
|
-
|
|
3435
|
-
|
|
3510
|
+
relevantPapers: deduplicated.slice(0, fallbackCap).map((p) => ({
|
|
3511
|
+
...p,
|
|
3512
|
+
relevanceScore: 7,
|
|
3513
|
+
relevanceJustification: "Review parsing failed; included with default score 7 so the paper is still saved. Re-review manually."
|
|
3514
|
+
})),
|
|
3515
|
+
confidence: 0.3,
|
|
3516
|
+
coverage: { score: 0.3, coveredTopics: [], missingTopics: [], gaps: ["Review parsing failed"] },
|
|
3436
3517
|
issues: ["Review parsing failed"],
|
|
3437
3518
|
additionalQueries: null
|
|
3438
3519
|
};
|
|
3439
3520
|
pipelineWarnings.push(
|
|
3440
|
-
"LLM review parsing failed — papers
|
|
3521
|
+
"LLM review parsing failed — papers saved with default relevance score of 7. Relevance scores are NOT accurate. Re-review the saved papers manually."
|
|
3441
3522
|
);
|
|
3442
3523
|
} else {
|
|
3443
|
-
|
|
3524
|
+
const seenIndices = /* @__PURE__ */ new Set();
|
|
3525
|
+
const merged = [];
|
|
3526
|
+
for (const s of parsed.scoredPapers) {
|
|
3527
|
+
const idx = (Number(s?.index) | 0) - 1;
|
|
3528
|
+
if (idx < 0 || idx >= deduplicated.length) continue;
|
|
3529
|
+
if (seenIndices.has(idx)) continue;
|
|
3530
|
+
seenIndices.add(idx);
|
|
3531
|
+
const score = Number(s?.relevanceScore);
|
|
3532
|
+
merged.push({
|
|
3533
|
+
...deduplicated[idx],
|
|
3534
|
+
relevanceScore: Number.isFinite(score) ? score : 0,
|
|
3535
|
+
relevanceJustification: typeof s?.relevanceJustification === "string" ? s.relevanceJustification : ""
|
|
3536
|
+
});
|
|
3537
|
+
}
|
|
3538
|
+
review = {
|
|
3539
|
+
approved: parsed.approved ?? false,
|
|
3540
|
+
relevantPapers: merged,
|
|
3541
|
+
confidence: typeof parsed.confidence === "number" ? parsed.confidence : 0.5,
|
|
3542
|
+
coverage: parsed.coverage ?? { score: 0.5, coveredTopics: [], missingTopics: [], gaps: [] },
|
|
3543
|
+
issues: Array.isArray(parsed.issues) ? parsed.issues : [],
|
|
3544
|
+
additionalQueries: parsed.additionalQueries ?? null
|
|
3545
|
+
};
|
|
3444
3546
|
}
|
|
3445
3547
|
} catch (err) {
|
|
3446
3548
|
return toAgentResult("literature-search", toolError("EXECUTION_FAILED", `Review step failed: ${err.message}`, {
|
|
@@ -6511,6 +6613,7 @@ function getWikiRoot() {
|
|
|
6511
6613
|
return join(homedir(), ".research-pilot", "paper-wiki");
|
|
6512
6614
|
}
|
|
6513
6615
|
const GENERATOR_VERSION = 3;
|
|
6616
|
+
const HASH_SCHEMA_VERSION = 2;
|
|
6514
6617
|
function isValidArxivId(arxivId) {
|
|
6515
6618
|
const bare = arxivId.replace(/^https?:\/\/arxiv\.org\/abs\//, "").replace(/v\d+$/, "");
|
|
6516
6619
|
if (/^\d{4}\.\d{4,}$/.test(bare)) return true;
|
|
@@ -6529,6 +6632,18 @@ function computeCanonicalKey(artifact) {
|
|
|
6529
6632
|
return { canonicalKey: `title:${title}:${artifact.year ?? "nd"}`, keySource: "title+year" };
|
|
6530
6633
|
}
|
|
6531
6634
|
function computeSemanticHash(artifact) {
|
|
6635
|
+
const projection = {
|
|
6636
|
+
title: artifact.title,
|
|
6637
|
+
authors: artifact.authors,
|
|
6638
|
+
abstract: artifact.abstract,
|
|
6639
|
+
year: artifact.year,
|
|
6640
|
+
venue: artifact.venue,
|
|
6641
|
+
doi: artifact.doi,
|
|
6642
|
+
arxivId: artifact.arxivId
|
|
6643
|
+
};
|
|
6644
|
+
return createHash("sha256").update(JSON.stringify(projection)).digest("hex").slice(0, 16);
|
|
6645
|
+
}
|
|
6646
|
+
function computeSemanticHashV1(artifact) {
|
|
6532
6647
|
const projection = {
|
|
6533
6648
|
title: artifact.title,
|
|
6534
6649
|
authors: artifact.authors,
|
|
@@ -6544,6 +6659,10 @@ function computeSemanticHash(artifact) {
|
|
|
6544
6659
|
};
|
|
6545
6660
|
return createHash("sha256").update(JSON.stringify(projection)).digest("hex").slice(0, 16);
|
|
6546
6661
|
}
|
|
6662
|
+
function canSilentRestampLegacyWatermark(priorWatermark, artifact) {
|
|
6663
|
+
if ((priorWatermark.hashSchemaVersion ?? 1) >= HASH_SCHEMA_VERSION) return false;
|
|
6664
|
+
return priorWatermark.semanticHash === computeSemanticHashV1(artifact);
|
|
6665
|
+
}
|
|
6547
6666
|
function canonicalKeyToSlug(canonicalKey) {
|
|
6548
6667
|
return canonicalKey.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 120);
|
|
6549
6668
|
}
|
|
@@ -6629,6 +6748,25 @@ function markPaperProcessed(entry) {
|
|
|
6629
6748
|
const content = Array.from(existing.values()).map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
6630
6749
|
safeWriteFile(path2, content);
|
|
6631
6750
|
}
|
|
6751
|
+
function restampProcessedBatch(updates) {
|
|
6752
|
+
if (updates.length === 0) return;
|
|
6753
|
+
const existing = readProcessedWatermark();
|
|
6754
|
+
let changed = 0;
|
|
6755
|
+
for (const upd of updates) {
|
|
6756
|
+
const entry = existing.get(upd.canonicalKey);
|
|
6757
|
+
if (!entry) continue;
|
|
6758
|
+
existing.set(upd.canonicalKey, {
|
|
6759
|
+
...entry,
|
|
6760
|
+
semanticHash: upd.semanticHash,
|
|
6761
|
+
hashSchemaVersion: upd.hashSchemaVersion
|
|
6762
|
+
});
|
|
6763
|
+
changed++;
|
|
6764
|
+
}
|
|
6765
|
+
if (changed === 0) return;
|
|
6766
|
+
const path2 = processedPath();
|
|
6767
|
+
const content = Array.from(existing.values()).map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
6768
|
+
safeWriteFile(path2, content);
|
|
6769
|
+
}
|
|
6632
6770
|
function markFulltextFailure(canonicalKey) {
|
|
6633
6771
|
const existing = readProcessedWatermark();
|
|
6634
6772
|
const entry = existing.get(canonicalKey);
|
|
@@ -8731,6 +8869,14 @@ function buildSessionSummaryContext(summary) {
|
|
|
8731
8869
|
];
|
|
8732
8870
|
return lines.join("\n");
|
|
8733
8871
|
}
|
|
8872
|
+
function buildRecentConversationContext(messages) {
|
|
8873
|
+
const lines = ["## Recent Conversation (resumed from prior session)", ""];
|
|
8874
|
+
for (const msg of messages) {
|
|
8875
|
+
const speaker = msg.role === "user" ? "User" : "Assistant";
|
|
8876
|
+
lines.push(`**${speaker}:** ${msg.content}`, "");
|
|
8877
|
+
}
|
|
8878
|
+
return lines.join("\n").trimEnd();
|
|
8879
|
+
}
|
|
8734
8880
|
function writeExplainSnapshot(projectPath, snapshot) {
|
|
8735
8881
|
const explainDir = join(projectPath, PATHS.explainDir);
|
|
8736
8882
|
mkdirSync(explainDir, { recursive: true });
|
|
@@ -8875,12 +9021,13 @@ async function createCoordinator(config) {
|
|
|
8875
9021
|
const skillsCatalog = buildSkillsCatalogPrompt(skills);
|
|
8876
9022
|
const baseSystemPrompt = SYSTEM_PROMPT + (skillsCatalog ? "\n\n" + skillsCatalog : "");
|
|
8877
9023
|
let compactionSummary;
|
|
9024
|
+
let bootstrapDone = false;
|
|
8878
9025
|
const agent = new Agent({
|
|
8879
9026
|
initialState: {
|
|
8880
9027
|
systemPrompt: baseSystemPrompt,
|
|
8881
9028
|
model: piModel ?? void 0,
|
|
8882
9029
|
tools: allTools,
|
|
8883
|
-
thinkingLevel: reasoningEffort === "high" ? "high" : reasoningEffort === "medium" ? "medium" : "low"
|
|
9030
|
+
thinkingLevel: reasoningEffort === "max" ? "xhigh" : reasoningEffort === "high" ? "high" : reasoningEffort === "medium" ? "medium" : "low"
|
|
8884
9031
|
},
|
|
8885
9032
|
sessionId,
|
|
8886
9033
|
getApiKey: resolveApiKey,
|
|
@@ -9078,6 +9225,22 @@ ${historyText}`,
|
|
|
9078
9225
|
const mentionContext = buildMentionContext(mentions);
|
|
9079
9226
|
const latestSummary = readLatestSessionSummary(projectPath, sessionId);
|
|
9080
9227
|
const summaryContext = latestSummary ? buildSessionSummaryContext(latestSummary) : "";
|
|
9228
|
+
let bootstrapContext = "";
|
|
9229
|
+
if (!bootstrapDone) {
|
|
9230
|
+
bootstrapDone = true;
|
|
9231
|
+
try {
|
|
9232
|
+
const cutoffMs = latestSummary ? Date.parse(latestSummary.createdAt) || 0 : 0;
|
|
9233
|
+
const orphans = readOrphanMessages(projectPath, sessionId, cutoffMs);
|
|
9234
|
+
if (orphans.length > 0) {
|
|
9235
|
+
bootstrapContext = buildRecentConversationContext(orphans);
|
|
9236
|
+
if (debug) {
|
|
9237
|
+
console.log(`[Bootstrap] Recovered ${orphans.length} orphan message(s) from prior session`);
|
|
9238
|
+
}
|
|
9239
|
+
}
|
|
9240
|
+
} catch (err) {
|
|
9241
|
+
if (debug) console.warn("[Bootstrap] Failed to read orphan messages:", err);
|
|
9242
|
+
}
|
|
9243
|
+
}
|
|
9081
9244
|
const persistence = classifyPersistenceDecision(message);
|
|
9082
9245
|
const explain = {
|
|
9083
9246
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -9119,6 +9282,7 @@ ${agentMdContent}`;
|
|
|
9119
9282
|
agent.state.systemPrompt = enrichedSystem;
|
|
9120
9283
|
const contextParts = [];
|
|
9121
9284
|
if (summaryContext) contextParts.push(summaryContext);
|
|
9285
|
+
if (bootstrapContext) contextParts.push(bootstrapContext);
|
|
9122
9286
|
if (skillSummariesPrompt) contextParts.push(skillSummariesPrompt);
|
|
9123
9287
|
if (mentionContext) contextParts.push(mentionContext);
|
|
9124
9288
|
let userMessage = contextParts.length > 0 ? `${contextParts.join("\n\n")}
|
|
@@ -9162,6 +9326,23 @@ ${message}` : message;
|
|
|
9162
9326
|
}
|
|
9163
9327
|
}
|
|
9164
9328
|
writeExplainSnapshot(projectPath, explain);
|
|
9329
|
+
const stopReason = lastMsg && "stopReason" in lastMsg ? lastMsg.stopReason : void 0;
|
|
9330
|
+
const errorMessage = lastMsg && "errorMessage" in lastMsg ? lastMsg.errorMessage : void 0;
|
|
9331
|
+
if (stopReason === "aborted") {
|
|
9332
|
+
return { success: false, error: "Generation stopped by user." };
|
|
9333
|
+
}
|
|
9334
|
+
if (stopReason === "error") {
|
|
9335
|
+
return {
|
|
9336
|
+
success: false,
|
|
9337
|
+
error: errorMessage || "The LLM request failed. This usually indicates an issue on the model server or with authentication. If you are using a Claude or ChatGPT subscription, try signing out and back in via Settings. Otherwise verify your API key, model selection, and network connection."
|
|
9338
|
+
};
|
|
9339
|
+
}
|
|
9340
|
+
if (!responseText && responseImages.length === 0) {
|
|
9341
|
+
return {
|
|
9342
|
+
success: false,
|
|
9343
|
+
error: `The model returned an empty response (stopReason=${stopReason ?? "unknown"}). This usually indicates a transient issue with the LLM server. Please try again. If the problem persists and you are using a Claude or ChatGPT subscription, try signing out and back in via Settings.`
|
|
9344
|
+
};
|
|
9345
|
+
}
|
|
9165
9346
|
turnCount++;
|
|
9166
9347
|
turnHistory.push({
|
|
9167
9348
|
userMessage: message.slice(0, 300),
|
|
@@ -11131,6 +11312,7 @@ function scanForNewContent(projectPaths) {
|
|
|
11131
11312
|
const processed = readProcessedWatermark();
|
|
11132
11313
|
const toProcess = [];
|
|
11133
11314
|
const provenanceOnly = [];
|
|
11315
|
+
const restamps = [];
|
|
11134
11316
|
const existingProvenance = readProvenance();
|
|
11135
11317
|
const provenanceIndex = /* @__PURE__ */ new Map();
|
|
11136
11318
|
for (const entry of existingProvenance) {
|
|
@@ -11189,6 +11371,20 @@ function scanForNewContent(projectPaths) {
|
|
|
11189
11371
|
provenanceIndex.delete(fallbackKey);
|
|
11190
11372
|
}
|
|
11191
11373
|
}
|
|
11374
|
+
const priorWatermark = processed.get(canonicalKey);
|
|
11375
|
+
if (priorWatermark && canSilentRestampLegacyWatermark(priorWatermark, artifact)) {
|
|
11376
|
+
const restamped = {
|
|
11377
|
+
...priorWatermark,
|
|
11378
|
+
semanticHash,
|
|
11379
|
+
hashSchemaVersion: HASH_SCHEMA_VERSION
|
|
11380
|
+
};
|
|
11381
|
+
processed.set(canonicalKey, restamped);
|
|
11382
|
+
restamps.push({
|
|
11383
|
+
canonicalKey,
|
|
11384
|
+
semanticHash,
|
|
11385
|
+
hashSchemaVersion: HASH_SCHEMA_VERSION
|
|
11386
|
+
});
|
|
11387
|
+
}
|
|
11192
11388
|
const watermark = processed.get(canonicalKey);
|
|
11193
11389
|
const knownProvenance = provenanceIndex.get(canonicalKey) || /* @__PURE__ */ new Set();
|
|
11194
11390
|
const siblings = entries.filter((e) => e.artifact.id !== artifact.id || e.projectPath !== projectPath).map((e) => ({ projectPath: e.projectPath, artifact: e.artifact }));
|
|
@@ -11227,6 +11423,9 @@ function scanForNewContent(projectPaths) {
|
|
|
11227
11423
|
const bTime = new Date(b.artifact.createdAt).getTime();
|
|
11228
11424
|
return bTime - aTime;
|
|
11229
11425
|
});
|
|
11426
|
+
if (restamps.length > 0) {
|
|
11427
|
+
restampProcessedBatch(restamps);
|
|
11428
|
+
}
|
|
11230
11429
|
return { toProcess, provenanceOnly };
|
|
11231
11430
|
}
|
|
11232
11431
|
const ARXIV_RATE_LIMIT_MS = 3e3;
|
|
@@ -11347,19 +11546,6 @@ function buildPaperUserContent(artifact, fulltext, existingConceptSlugs) {
|
|
|
11347
11546
|
parts.push(`
|
|
11348
11547
|
Abstract:
|
|
11349
11548
|
${artifact.abstract || "(no abstract)"}`);
|
|
11350
|
-
if (artifact.keyFindings?.length) {
|
|
11351
|
-
parts.push(`
|
|
11352
|
-
Key Findings:
|
|
11353
|
-
${artifact.keyFindings.map((f) => `- ${f}`).join("\n")}`);
|
|
11354
|
-
}
|
|
11355
|
-
if (artifact.relevanceJustification) {
|
|
11356
|
-
parts.push(`
|
|
11357
|
-
Relevance: ${artifact.relevanceJustification}`);
|
|
11358
|
-
}
|
|
11359
|
-
if (artifact.subTopic) {
|
|
11360
|
-
parts.push(`
|
|
11361
|
-
Sub-topic: ${artifact.subTopic}`);
|
|
11362
|
-
}
|
|
11363
11549
|
if (fulltext) {
|
|
11364
11550
|
const truncated = fulltext.length > 3e4 ? fulltext.slice(0, 3e4) + "\n\n[... truncated for length ...]" : fulltext;
|
|
11365
11551
|
parts.push(`
|
|
@@ -11642,6 +11828,7 @@ function createWikiAgent(config) {
|
|
|
11642
11828
|
let errors = 0;
|
|
11643
11829
|
if (!acquireProcessLock()) {
|
|
11644
11830
|
log("process lock held by another instance, skipping");
|
|
11831
|
+
emitStatus(0);
|
|
11645
11832
|
return { processed: 0, errors: 0, pendingRemaining: 0 };
|
|
11646
11833
|
}
|
|
11647
11834
|
try {
|
|
@@ -11650,6 +11837,7 @@ function createWikiAgent(config) {
|
|
|
11650
11837
|
const projectPaths = config.projectPaths();
|
|
11651
11838
|
if (projectPaths.length === 0) {
|
|
11652
11839
|
log("no project paths, nothing to scan");
|
|
11840
|
+
emitStatus(0);
|
|
11653
11841
|
return { processed: 0, errors: 0, pendingRemaining: 0 };
|
|
11654
11842
|
}
|
|
11655
11843
|
const { toProcess, provenanceOnly } = scanForNewContent(projectPaths);
|
|
@@ -11813,6 +12001,7 @@ function createWikiAgent(config) {
|
|
|
11813
12001
|
semanticHash,
|
|
11814
12002
|
fulltextStatus: result.fulltextStatus,
|
|
11815
12003
|
generatorVersion: GENERATOR_VERSION,
|
|
12004
|
+
hashSchemaVersion: HASH_SCHEMA_VERSION,
|
|
11816
12005
|
processedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
11817
12006
|
};
|
|
11818
12007
|
markPaperProcessed(entry);
|
|
@@ -11865,6 +12054,7 @@ function createWikiAgent(config) {
|
|
|
11865
12054
|
start() {
|
|
11866
12055
|
if (state !== "created") return;
|
|
11867
12056
|
state = "idle";
|
|
12057
|
+
emitStatus(0);
|
|
11868
12058
|
log(`starting (delay ${config.pacing.startupDelayMs / 1e3}s)`);
|
|
11869
12059
|
timer = setTimeout(() => tick(), config.pacing.startupDelayMs);
|
|
11870
12060
|
},
|
package/app/out/preload/index.js
CHANGED
|
@@ -27,11 +27,13 @@ const api = {
|
|
|
27
27
|
// OpenAI Codex (ChatGPT Subscription) OAuth
|
|
28
28
|
getOpenAICodexStatus: () => electron.ipcRenderer.invoke("auth:get-openai-codex-status"),
|
|
29
29
|
openaiCodexLogin: () => electron.ipcRenderer.invoke("auth:openai-codex-login"),
|
|
30
|
+
openaiCodexCancel: () => electron.ipcRenderer.invoke("auth:openai-codex-cancel"),
|
|
30
31
|
openaiCodexLogout: () => electron.ipcRenderer.invoke("auth:openai-codex-logout"),
|
|
31
32
|
// Anthropic Subscription (Claude Pro/Max) OAuth — enabled by default
|
|
32
33
|
isClaudeSubEnabled: () => true,
|
|
33
34
|
getAnthropicSubStatus: () => electron.ipcRenderer.invoke("auth:get-anthropic-sub-status"),
|
|
34
35
|
anthropicSubLogin: () => electron.ipcRenderer.invoke("auth:anthropic-sub-login"),
|
|
36
|
+
anthropicSubCancel: () => electron.ipcRenderer.invoke("auth:anthropic-sub-cancel"),
|
|
35
37
|
anthropicSubLogout: () => electron.ipcRenderer.invoke("auth:anthropic-sub-logout"),
|
|
36
38
|
pickPreferredModel: () => electron.ipcRenderer.invoke("config:pick-preferred-model"),
|
|
37
39
|
getApiKeyStatus: () => electron.ipcRenderer.invoke("config:get-api-key-status"),
|