research-copilot 0.1.3 → 0.2.1
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 +1281 -282
- package/app/out/preload/index.js +5 -0
- package/app/out/renderer/assets/{MilkdownMarkdownEditor-CCiFOpuq.js → MilkdownMarkdownEditor-D7GYpVZn.js} +50 -50
- package/app/out/renderer/assets/{arc-BR5G9xaE.js → arc-Kp4J_Jd7.js} +1 -1
- package/app/out/renderer/assets/{blockDiagram-c4efeb88-JmvDTsGU.js → blockDiagram-c4efeb88-DkMSdn8j.js} +8 -8
- package/app/out/renderer/assets/{c4Diagram-c83219d4-Daf_3gE1.js → c4Diagram-c83219d4-DqAGxrYw.js} +3 -3
- package/app/out/renderer/assets/{channel-xtutyETs.js → channel-S4GQrISQ.js} +1 -1
- package/app/out/renderer/assets/{classDiagram-beda092f-BFWEqrCW.js → classDiagram-beda092f-B7AsTCEg.js} +6 -6
- package/app/out/renderer/assets/{classDiagram-v2-2358418a-BQw7RI0A.js → classDiagram-v2-2358418a-B4oFy-In.js} +10 -10
- package/app/out/renderer/assets/{clone-uoV60hcB.js → clone-Dv1e6zYr.js} +1 -1
- package/app/out/renderer/assets/{createText-1719965b-BaRII2sm.js → createText-1719965b-HBXHvWlI.js} +2 -2
- package/app/out/renderer/assets/{edges-96097737-CL7Yc4hz.js → edges-96097737-B6X5lcC0.js} +3 -3
- package/app/out/renderer/assets/{erDiagram-0228fc6a-B9hgyxu6.js → erDiagram-0228fc6a-BmBmTBlH.js} +5 -5
- package/app/out/renderer/assets/{flowDb-c6c81e3f-b_RS-jIJ.js → flowDb-c6c81e3f-CObz36ob.js} +1 -1
- package/app/out/renderer/assets/{flowDiagram-50d868cf-CPB3IueC.js → flowDiagram-50d868cf-C2hFHxwF.js} +12 -12
- package/app/out/renderer/assets/{flowDiagram-v2-4f6560a1-DM8cFvdZ.js → flowDiagram-v2-4f6560a1-DEe8EygW.js} +12 -12
- package/app/out/renderer/assets/{flowchart-elk-definition-6af322e1-BsxABHy9.js → flowchart-elk-definition-6af322e1-CgTtfYKk.js} +6 -6
- package/app/out/renderer/assets/{ganttDiagram-a2739b55-DpMib95K.js → ganttDiagram-a2739b55-C5Pq4zEy.js} +3 -3
- package/app/out/renderer/assets/{gitGraphDiagram-82fe8481-C0OtwErh.js → gitGraphDiagram-82fe8481-oLp0f8Ll.js} +2 -2
- package/app/out/renderer/assets/{graph-CXef_RHM.js → graph-51iZ6wgR.js} +1 -1
- package/app/out/renderer/assets/{index-Bg4LHaeu.js → index-32eUzqVW.js} +3 -3
- package/app/out/renderer/assets/{index-5325376f-0FtzFTBH.js → index-5325376f-yLvOW-Os.js} +6 -6
- package/app/out/renderer/assets/{index-tz7ZKjP9.js → index-AuZa-hTj.js} +3 -3
- package/app/out/renderer/assets/{index-uZnv8lTU.js → index-B9a4DKM-.js} +3 -3
- package/app/out/renderer/assets/{index-CabfPYgf.js → index-BMsuFGn6.js} +3 -3
- package/app/out/renderer/assets/{index-CFaiDIr7.js → index-BQA_Kvr6.js} +3 -3
- package/app/out/renderer/assets/{index-D9-3cc7l.js → index-BSd80-j9.js} +4 -4
- package/app/out/renderer/assets/{index-C1Hf3CJw.js → index-BfWWn8B_.js} +6 -6
- package/app/out/renderer/assets/{index-CTF1A-5m.js → index-Bscx_5dF.js} +3 -3
- package/app/out/renderer/assets/{index-BBH0Chbw.js → index-CAOQIqEc.js} +6 -6
- package/app/out/renderer/assets/{index-Cx3Vwh3q.js → index-CTmGCKqa.js} +4 -4
- package/app/out/renderer/assets/{index-DRyElXV-.js → index-CmpSV9Ld.js} +5 -5
- package/app/out/renderer/assets/{index-BohTbJeP.js → index-Cn2e13ja.js} +6 -6
- package/app/out/renderer/assets/{index-BE4XBnng.js → index-D_Y7v6pE.js} +3 -3
- package/app/out/renderer/assets/{index-vGIhunyU.js → index-DjqJjt6u.js} +6 -6
- package/app/out/renderer/assets/{index-Crf9Pipm.js → index-DppxBL77.js} +3 -3
- package/app/out/renderer/assets/{index-C_cgOzmt.js → index-Du-Z3sl4.js} +952 -90
- package/app/out/renderer/assets/{index-CMDsy41q.js → index-FGsCVYSr.js} +1 -1
- package/app/out/renderer/assets/{index-C-_uCjZJ.css → index-L4DJn7cw.css} +14 -18
- package/app/out/renderer/assets/{index-DuAPj57k.js → index-UajPJYNV.js} +3 -3
- package/app/out/renderer/assets/{index-DeVfJmHc.js → index-_Z53hJps.js} +3 -3
- package/app/out/renderer/assets/{index-BHo8axTp.js → index-_iFRQTkA.js} +6 -6
- package/app/out/renderer/assets/{index-CKjCQ1EB.js → index-ohN9yRWw.js} +6 -6
- package/app/out/renderer/assets/{index-Dy2bySYF.js → index-shoMWskw.js} +3 -3
- package/app/out/renderer/assets/{index-OqY0JVi2.js → index-y1Od1ed6.js} +3 -3
- package/app/out/renderer/assets/{infoDiagram-8eee0895-CPFVhSvg.js → infoDiagram-8eee0895-Cm0Hm5ZX.js} +2 -2
- package/app/out/renderer/assets/{journeyDiagram-c64418c1-PKaxJ2mn.js → journeyDiagram-c64418c1-A2Gw9bVu.js} +4 -4
- package/app/out/renderer/assets/{layout-CawlN23W.js → layout-C5N2nTfF.js} +2 -2
- package/app/out/renderer/assets/{line-C_cMMDTP.js → line-Dn6BEQAK.js} +1 -1
- package/app/out/renderer/assets/{linear-CnzgpVoT.js → linear-8wk0rPUX.js} +1 -1
- package/app/out/renderer/assets/{mindmap-definition-8da855dc-2dVBAm3g.js → mindmap-definition-8da855dc-BVy6ISnb.js} +3 -3
- package/app/out/renderer/assets/{pieDiagram-a8764435-p6hNN8aY.js → pieDiagram-a8764435-B9_axIHE.js} +3 -3
- package/app/out/renderer/assets/{quadrantDiagram-1e28029f-JJT_eOsi.js → quadrantDiagram-1e28029f-B1kmkDFg.js} +3 -3
- package/app/out/renderer/assets/{requirementDiagram-08caed73-Ck4auzva.js → requirementDiagram-08caed73-C_bNWUtT.js} +5 -5
- package/app/out/renderer/assets/{sankeyDiagram-a04cb91d-DICtg7Jw.js → sankeyDiagram-a04cb91d-CD2h1LiI.js} +2 -2
- package/app/out/renderer/assets/{sequenceDiagram-c5b8d532-Bv7njQoz.js → sequenceDiagram-c5b8d532-B6d6cuqi.js} +3 -3
- package/app/out/renderer/assets/{stateDiagram-1ecb1508-CmuiBQ0q.js → stateDiagram-1ecb1508-CkuNj_3H.js} +6 -6
- package/app/out/renderer/assets/{stateDiagram-v2-c2b004d7-DErEMYcv.js → stateDiagram-v2-c2b004d7-CevZ3tno.js} +10 -10
- package/app/out/renderer/assets/{styles-b4e223ce-CEjXkOYY.js → styles-b4e223ce-DAe5WQrg.js} +1 -1
- package/app/out/renderer/assets/{styles-ca3715f6-BJWKCKia.js → styles-ca3715f6-BDSX88bY.js} +1 -1
- package/app/out/renderer/assets/{styles-d45a18b0-BrhRky7i.js → styles-d45a18b0-SE9h7les.js} +4 -4
- package/app/out/renderer/assets/{svgDrawCommon-b86b1483-RWkoQoOd.js → svgDrawCommon-b86b1483-D1mpNbDQ.js} +1 -1
- package/app/out/renderer/assets/{timeline-definition-faaaa080-25xmyyis.js → timeline-definition-faaaa080-7Ha-nm4M.js} +3 -3
- package/app/out/renderer/assets/{xychartDiagram-f5964ef8-DwBkod9W.js → xychartDiagram-f5964ef8-DLy7iyZW.js} +5 -5
- package/app/out/renderer/index.html +2 -2
- package/app/package.json +1 -1
- package/package.json +1 -1
package/app/out/main/index.mjs
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
import { app, shell, ipcMain, BrowserWindow, dialog, Menu } from "electron";
|
|
2
|
+
import { setMaxListeners } from "node:events";
|
|
2
3
|
import fs, { existsSync as existsSync$1 } from "node:fs";
|
|
3
4
|
import { execFile, execSync } from "node:child_process";
|
|
4
|
-
import { resolve, join, sep, isAbsolute, extname, basename, dirname, relative } from "path";
|
|
5
|
-
import { existsSync, statSync, readdirSync, readFileSync, writeFileSync, mkdirSync, appendFileSync, rmSync,
|
|
5
|
+
import path, { resolve, join, sep, isAbsolute, extname, basename, dirname, relative } from "path";
|
|
6
|
+
import { existsSync, statSync, readdirSync, readFileSync, writeFileSync, mkdirSync, appendFileSync, rmSync, unlinkSync, renameSync } from "fs";
|
|
6
7
|
import os$1, { homedir } from "os";
|
|
8
|
+
import { createHash, randomUUID } from "crypto";
|
|
7
9
|
import { Agent } from "@mariozechner/pi-agent-core";
|
|
8
|
-
import {
|
|
10
|
+
import { completeSimple, getModel } from "@mariozechner/pi-ai";
|
|
9
11
|
import { createCodingTools, createGrepTool, createFindTool, createLsTool, DEFAULT_COMPACTION_SETTINGS, estimateTokens, shouldCompact, generateSummary } from "@mariozechner/pi-coding-agent";
|
|
10
12
|
import { Type } from "@sinclair/typebox";
|
|
11
|
-
import
|
|
13
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
14
|
+
import path$1 from "node:path";
|
|
12
15
|
import fsp from "node:fs/promises";
|
|
13
|
-
import { createHash } from "node:crypto";
|
|
16
|
+
import { createHash as createHash$1 } from "node:crypto";
|
|
14
17
|
import { promisify } from "node:util";
|
|
15
18
|
import os from "node:os";
|
|
16
19
|
import { fileURLToPath } from "node:url";
|
|
17
|
-
import {
|
|
20
|
+
import { execFile as execFile$1 } from "child_process";
|
|
18
21
|
import __cjs_mod__ from "node:module";
|
|
19
22
|
const __filename = import.meta.filename;
|
|
20
23
|
const __dirname = import.meta.dirname;
|
|
@@ -232,10 +235,6 @@ function registerConfigHandlers(handleRaw) {
|
|
|
232
235
|
return { success: true };
|
|
233
236
|
});
|
|
234
237
|
}
|
|
235
|
-
function getFileName(path2) {
|
|
236
|
-
if (!path2) return "";
|
|
237
|
-
return path2.split("/").pop() || path2;
|
|
238
|
-
}
|
|
239
238
|
function inferMimeType(path2) {
|
|
240
239
|
const ext = extname(path2).toLowerCase();
|
|
241
240
|
if (ext === ".md" || ext === ".txt") return "text/plain";
|
|
@@ -575,13 +574,39 @@ function truncateHeadTail(text, maxChars, headRatio = 0.7) {
|
|
|
575
574
|
...[truncated ${truncatedChars} chars]
|
|
576
575
|
${text.slice(-tailChars)}`;
|
|
577
576
|
}
|
|
577
|
+
function truncateStructuredData(data, maxChars) {
|
|
578
|
+
const json = JSON.stringify(data, null, 2);
|
|
579
|
+
if (json.length <= maxChars) return data;
|
|
580
|
+
const obj = { ...data };
|
|
581
|
+
let largestKey = "";
|
|
582
|
+
let largestSize = 0;
|
|
583
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
584
|
+
if (typeof v === "string" && v.length > largestSize) {
|
|
585
|
+
largestKey = k;
|
|
586
|
+
largestSize = v.length;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
if (largestKey) {
|
|
590
|
+
const overhead = json.length - largestSize;
|
|
591
|
+
const fieldBudget = Math.max(1e3, maxChars - overhead);
|
|
592
|
+
obj[largestKey] = truncateHeadTail(obj[largestKey], fieldBudget);
|
|
593
|
+
}
|
|
594
|
+
return obj;
|
|
595
|
+
}
|
|
578
596
|
function toAgentResult(toolName, result) {
|
|
579
597
|
let text;
|
|
598
|
+
const MAX_RESULT_CHARS = 1e5;
|
|
580
599
|
if (result.success) {
|
|
581
600
|
if (result.data === void 0 || result.data === null) {
|
|
582
601
|
text = `[${toolName}] OK`;
|
|
583
602
|
} else if (typeof result.data === "string") {
|
|
584
|
-
text = result.data;
|
|
603
|
+
text = truncateHeadTail(result.data, MAX_RESULT_CHARS);
|
|
604
|
+
} else if (typeof result.data === "object" && result.data !== null && !Array.isArray(result.data)) {
|
|
605
|
+
const bounded = truncateStructuredData(
|
|
606
|
+
result.data,
|
|
607
|
+
MAX_RESULT_CHARS
|
|
608
|
+
);
|
|
609
|
+
text = JSON.stringify(bounded, null, 2);
|
|
585
610
|
} else {
|
|
586
611
|
text = JSON.stringify(result.data, null, 2);
|
|
587
612
|
}
|
|
@@ -606,10 +631,11 @@ ${result.suggestions.map((s) => `- ${s}`).join("\n")}`);
|
|
|
606
631
|
}
|
|
607
632
|
text = parts.join("\n");
|
|
608
633
|
}
|
|
609
|
-
|
|
610
|
-
|
|
634
|
+
if (text.length > MAX_RESULT_CHARS) {
|
|
635
|
+
text = truncateHeadTail(text, MAX_RESULT_CHARS);
|
|
636
|
+
}
|
|
611
637
|
return {
|
|
612
|
-
content: [{ type: "text", text
|
|
638
|
+
content: [{ type: "text", text }],
|
|
613
639
|
details: { success: result.success, tool_name: toolName }
|
|
614
640
|
};
|
|
615
641
|
}
|
|
@@ -634,6 +660,8 @@ const PATHS = {
|
|
|
634
660
|
memoryRoot: ".research-pilot/memory-v2",
|
|
635
661
|
explainDir: ".research-pilot/memory-v2/explain",
|
|
636
662
|
sessionSummaries: ".research-pilot/memory-v2/session-summaries",
|
|
663
|
+
// Structured long-term memory (auto-memory)
|
|
664
|
+
memory: ".research-pilot/memory",
|
|
637
665
|
// Skills
|
|
638
666
|
skills: ".research-pilot/skills",
|
|
639
667
|
skillsConfig: ".research-pilot/skills-config.json"
|
|
@@ -1244,56 +1272,334 @@ function createArtifactSearchTool(projectPath) {
|
|
|
1244
1272
|
}
|
|
1245
1273
|
};
|
|
1246
1274
|
}
|
|
1247
|
-
function
|
|
1275
|
+
function createResearchMemoryTools(params) {
|
|
1276
|
+
return [
|
|
1277
|
+
createArtifactCreateTool(params.sessionId, params.projectPath),
|
|
1278
|
+
createArtifactUpdateTool(params.projectPath),
|
|
1279
|
+
createArtifactSearchTool(params.projectPath)
|
|
1280
|
+
];
|
|
1281
|
+
}
|
|
1282
|
+
function slugify(text) {
|
|
1283
|
+
const slug = text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
|
|
1284
|
+
if (slug.length < 3) {
|
|
1285
|
+
const hash = createHash("sha256").update(text.toLowerCase().trim()).digest("hex").slice(0, 12);
|
|
1286
|
+
return slug ? `${slug}-${hash}` : hash;
|
|
1287
|
+
}
|
|
1288
|
+
return slug;
|
|
1289
|
+
}
|
|
1290
|
+
function memoryFilename(type, name) {
|
|
1291
|
+
return `${type}_${slugify(name)}.md`;
|
|
1292
|
+
}
|
|
1293
|
+
function memoryDir(projectPath) {
|
|
1294
|
+
return join(projectPath, PATHS.memory);
|
|
1295
|
+
}
|
|
1296
|
+
function ensureMemoryDir(projectPath) {
|
|
1297
|
+
const dir = memoryDir(projectPath);
|
|
1298
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
1299
|
+
}
|
|
1300
|
+
function yamlSafe(value) {
|
|
1301
|
+
if (!value) return '""';
|
|
1302
|
+
if (/[:\#{}\[\]"'`\n\r|>]/.test(value) || value !== value.trim()) {
|
|
1303
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r");
|
|
1304
|
+
return `"${escaped}"`;
|
|
1305
|
+
}
|
|
1306
|
+
return value;
|
|
1307
|
+
}
|
|
1308
|
+
function formatFrontmatter(fm) {
|
|
1309
|
+
return [
|
|
1310
|
+
"---",
|
|
1311
|
+
`name: ${yamlSafe(fm.name)}`,
|
|
1312
|
+
`description: ${yamlSafe(fm.description)}`,
|
|
1313
|
+
`type: ${fm.type}`,
|
|
1314
|
+
"---"
|
|
1315
|
+
].join("\n");
|
|
1316
|
+
}
|
|
1317
|
+
function yamlUnescape(raw) {
|
|
1318
|
+
const trimmed = raw.trim();
|
|
1319
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
1320
|
+
return trimmed.slice(1, -1).replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
1321
|
+
}
|
|
1322
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
1323
|
+
return trimmed.slice(1, -1).replace(/''/g, "'");
|
|
1324
|
+
}
|
|
1325
|
+
return trimmed;
|
|
1326
|
+
}
|
|
1327
|
+
function parseFrontmatter(text) {
|
|
1328
|
+
const match = text.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
1329
|
+
if (!match) return null;
|
|
1330
|
+
const fm = {};
|
|
1331
|
+
for (const line of match[1].split("\n")) {
|
|
1332
|
+
const kv = line.match(/^(\w+):\s*(.+)$/);
|
|
1333
|
+
if (kv) fm[kv[1]] = yamlUnescape(kv[2]);
|
|
1334
|
+
}
|
|
1335
|
+
if (!fm.name || !fm.type) return null;
|
|
1336
|
+
const validTypes = ["user", "feedback", "project", "reference"];
|
|
1337
|
+
if (!validTypes.includes(fm.type)) return null;
|
|
1248
1338
|
return {
|
|
1249
|
-
name: "
|
|
1250
|
-
|
|
1339
|
+
frontmatter: { name: fm.name, description: fm.description || "", type: fm.type },
|
|
1340
|
+
body: match[2].trim()
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
function writeMemoryFile(projectPath, entry) {
|
|
1344
|
+
ensureMemoryDir(projectPath);
|
|
1345
|
+
const filePath = join(memoryDir(projectPath), entry.filename);
|
|
1346
|
+
const content = `${formatFrontmatter(entry.frontmatter)}
|
|
1347
|
+
|
|
1348
|
+
${entry.content}
|
|
1349
|
+
`;
|
|
1350
|
+
writeFileSync(filePath, content, "utf-8");
|
|
1351
|
+
return filePath;
|
|
1352
|
+
}
|
|
1353
|
+
function readMemoryFile(projectPath, filename) {
|
|
1354
|
+
const filePath = join(memoryDir(projectPath), filename);
|
|
1355
|
+
if (!existsSync(filePath)) return null;
|
|
1356
|
+
try {
|
|
1357
|
+
const text = readFileSync(filePath, "utf-8");
|
|
1358
|
+
const parsed = parseFrontmatter(text);
|
|
1359
|
+
if (!parsed) return null;
|
|
1360
|
+
return { frontmatter: parsed.frontmatter, content: parsed.body, filename };
|
|
1361
|
+
} catch {
|
|
1362
|
+
return null;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
function deleteMemoryFile(projectPath, filename) {
|
|
1366
|
+
const filePath = join(memoryDir(projectPath), filename);
|
|
1367
|
+
if (!existsSync(filePath)) return false;
|
|
1368
|
+
try {
|
|
1369
|
+
unlinkSync(filePath);
|
|
1370
|
+
return true;
|
|
1371
|
+
} catch {
|
|
1372
|
+
return false;
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
function listMemoryFiles(projectPath) {
|
|
1376
|
+
const dir = memoryDir(projectPath);
|
|
1377
|
+
if (!existsSync(dir)) return [];
|
|
1378
|
+
try {
|
|
1379
|
+
const files = readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
|
|
1380
|
+
const entries = [];
|
|
1381
|
+
for (const filename of files) {
|
|
1382
|
+
const entry = readMemoryFile(projectPath, filename);
|
|
1383
|
+
if (entry) entries.push(entry);
|
|
1384
|
+
}
|
|
1385
|
+
return entries;
|
|
1386
|
+
} catch {
|
|
1387
|
+
return [];
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
function findMemoryByName(projectPath, name, type) {
|
|
1391
|
+
const lower = name.toLowerCase();
|
|
1392
|
+
const entries = listMemoryFiles(projectPath);
|
|
1393
|
+
return entries.find(
|
|
1394
|
+
(e) => e.frontmatter.name.toLowerCase() === lower && (!type || e.frontmatter.type === type)
|
|
1395
|
+
) ?? null;
|
|
1396
|
+
}
|
|
1397
|
+
function findAllMemoriesByName(projectPath, name) {
|
|
1398
|
+
const lower = name.toLowerCase();
|
|
1399
|
+
return listMemoryFiles(projectPath).filter((e) => e.frontmatter.name.toLowerCase() === lower);
|
|
1400
|
+
}
|
|
1401
|
+
let _indexWriteLock = Promise.resolve();
|
|
1402
|
+
function withIndexLock(fn) {
|
|
1403
|
+
const next = _indexWriteLock.then(fn, fn);
|
|
1404
|
+
_indexWriteLock = next.then(() => {
|
|
1405
|
+
}, () => {
|
|
1406
|
+
});
|
|
1407
|
+
return next;
|
|
1408
|
+
}
|
|
1409
|
+
function buildMemoryIndex(entries) {
|
|
1410
|
+
if (entries.length === 0) return "";
|
|
1411
|
+
return entries.map((e) => {
|
|
1412
|
+
const desc = e.frontmatter.description.slice(0, 100).replace(/\n/g, " ");
|
|
1413
|
+
return `- [${e.frontmatter.name}](memory/${e.filename}) — ${desc}`;
|
|
1414
|
+
}).join("\n");
|
|
1415
|
+
}
|
|
1416
|
+
function updateAgentMdIndex(projectPath, entries) {
|
|
1417
|
+
const record = findArtifactById(projectPath, AGENT_MD_ID);
|
|
1418
|
+
const currentContent = record?.artifact?.type === "note" ? record.artifact.content || "" : "";
|
|
1419
|
+
const marker = "## Agent Memory";
|
|
1420
|
+
const markerIdx = currentContent.indexOf(marker);
|
|
1421
|
+
const userInstructions = markerIdx >= 0 ? currentContent.slice(0, markerIdx).trimEnd() : currentContent.trimEnd();
|
|
1422
|
+
const indexContent = buildMemoryIndex(entries);
|
|
1423
|
+
const newContent = indexContent ? `${userInstructions}
|
|
1424
|
+
|
|
1425
|
+
${marker}
|
|
1426
|
+
|
|
1427
|
+
${indexContent}
|
|
1428
|
+
` : `${userInstructions}
|
|
1429
|
+
|
|
1430
|
+
${marker}
|
|
1431
|
+
`;
|
|
1432
|
+
if (newContent.length > AGENT_MD_MAX_CHARS) {
|
|
1433
|
+
return { success: false, charCount: newContent.length };
|
|
1434
|
+
}
|
|
1435
|
+
updateArtifact(projectPath, AGENT_MD_ID, { content: newContent });
|
|
1436
|
+
return { success: true, charCount: newContent.length };
|
|
1437
|
+
}
|
|
1438
|
+
function migrateAgentMemoryToFile(projectPath) {
|
|
1439
|
+
const record = findArtifactById(projectPath, AGENT_MD_ID);
|
|
1440
|
+
if (!record) return false;
|
|
1441
|
+
const content = record.artifact?.type === "note" ? record.artifact.content || "" : "";
|
|
1442
|
+
const marker = "## Agent Memory";
|
|
1443
|
+
const markerIdx = content.indexOf(marker);
|
|
1444
|
+
if (markerIdx < 0) return false;
|
|
1445
|
+
const agentMemory = content.slice(markerIdx + marker.length).trim();
|
|
1446
|
+
if (!agentMemory || /\[.*\]\(memory\/.*\)/.test(agentMemory)) return false;
|
|
1447
|
+
const legacyFilename = memoryFilename("project", "legacy-notes");
|
|
1448
|
+
const legacyPath = join(memoryDir(projectPath), legacyFilename);
|
|
1449
|
+
if (existsSync(legacyPath)) return false;
|
|
1450
|
+
ensureMemoryDir(projectPath);
|
|
1451
|
+
const entry = {
|
|
1452
|
+
frontmatter: {
|
|
1453
|
+
name: "Legacy notes",
|
|
1454
|
+
description: "Migrated from agent.md Agent Memory section",
|
|
1455
|
+
type: "project"
|
|
1456
|
+
},
|
|
1457
|
+
content: agentMemory,
|
|
1458
|
+
filename: memoryFilename("project", "legacy-notes")
|
|
1459
|
+
};
|
|
1460
|
+
writeMemoryFile(projectPath, entry);
|
|
1461
|
+
const allEntries = listMemoryFiles(projectPath);
|
|
1462
|
+
updateAgentMdIndex(projectPath, allEntries);
|
|
1463
|
+
return true;
|
|
1464
|
+
}
|
|
1465
|
+
const VALID_TYPES$1 = ["user", "feedback", "project", "reference"];
|
|
1466
|
+
function createSaveMemoryTool(projectPath) {
|
|
1467
|
+
return {
|
|
1468
|
+
name: "save-memory",
|
|
1469
|
+
description: 'Save a memory to long-term storage. Each memory becomes a file in .research-pilot/memory/ and an index entry in agent.md (visible every turn). Types: "user" (preferences/background), "feedback" (corrections to behavior), "project" (decisions/deadlines/context), "reference" (external pointers/reusable info). If a memory with the same name+type exists, it is updated.',
|
|
1251
1470
|
parameters: {
|
|
1252
1471
|
type: "object",
|
|
1253
1472
|
properties: {
|
|
1254
|
-
|
|
1473
|
+
type: {
|
|
1474
|
+
type: "string",
|
|
1475
|
+
enum: VALID_TYPES$1,
|
|
1476
|
+
description: "Memory category"
|
|
1477
|
+
},
|
|
1478
|
+
name: {
|
|
1255
1479
|
type: "string",
|
|
1256
|
-
description:
|
|
1480
|
+
description: "Short identifier (used as title and filename slug)"
|
|
1481
|
+
},
|
|
1482
|
+
content: {
|
|
1483
|
+
type: "string",
|
|
1484
|
+
description: "The memory content (markdown). Keep it concise and focused."
|
|
1257
1485
|
}
|
|
1258
1486
|
},
|
|
1259
|
-
required: ["
|
|
1487
|
+
required: ["type", "name", "content"]
|
|
1260
1488
|
},
|
|
1261
1489
|
execute: async (input) => {
|
|
1262
|
-
const
|
|
1263
|
-
const
|
|
1264
|
-
|
|
1265
|
-
|
|
1490
|
+
const type = String(input.type || "");
|
|
1491
|
+
const name = String(input.name || "").trim();
|
|
1492
|
+
const content = String(input.content || "").trim();
|
|
1493
|
+
if (!VALID_TYPES$1.includes(type)) {
|
|
1494
|
+
return toolError("INVALID_PARAMETER", `Invalid memory type: ${type}. Must be one of: ${VALID_TYPES$1.join(", ")}`, {
|
|
1495
|
+
suggestions: ['Use "user", "feedback", "project", or "reference".']
|
|
1496
|
+
});
|
|
1497
|
+
}
|
|
1498
|
+
if (!name) return toolError("MISSING_PARAMETER", "name is required.", {
|
|
1499
|
+
suggestions: ["Provide a short descriptive name for this memory."]
|
|
1266
1500
|
});
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
const
|
|
1271
|
-
const
|
|
1272
|
-
const
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1501
|
+
if (!content) return toolError("MISSING_PARAMETER", "content is required.", {
|
|
1502
|
+
suggestions: ["Provide the memory content to save."]
|
|
1503
|
+
});
|
|
1504
|
+
const lines = content.split("\n");
|
|
1505
|
+
const firstNonEmpty = lines.find((l) => l.trim().length > 0) || "";
|
|
1506
|
+
const description = firstNonEmpty.replace(/^#+\s*/, "").trim().slice(0, 120) || name;
|
|
1507
|
+
return withIndexLock(() => {
|
|
1508
|
+
ensureMemoryDir(projectPath);
|
|
1509
|
+
const filename = memoryFilename(type, name);
|
|
1510
|
+
const entry = {
|
|
1511
|
+
frontmatter: { name, description, type },
|
|
1512
|
+
content,
|
|
1513
|
+
filename
|
|
1514
|
+
};
|
|
1515
|
+
writeMemoryFile(projectPath, entry);
|
|
1516
|
+
const allEntries = listMemoryFiles(projectPath);
|
|
1517
|
+
const indexResult = updateAgentMdIndex(projectPath, allEntries);
|
|
1518
|
+
if (!indexResult.success) {
|
|
1519
|
+
deleteMemoryFile(projectPath, filename);
|
|
1520
|
+
return toolError(
|
|
1521
|
+
"OUTPUT_TOO_LARGE",
|
|
1522
|
+
"agent.md index exceeded size limit. Remove some memories first.",
|
|
1523
|
+
{
|
|
1524
|
+
suggestions: ["Use delete-memory to remove outdated entries before saving new ones."]
|
|
1525
|
+
}
|
|
1526
|
+
);
|
|
1527
|
+
}
|
|
1528
|
+
return {
|
|
1529
|
+
success: true,
|
|
1530
|
+
data: {
|
|
1531
|
+
message: `Memory saved: ${name} (${type})`,
|
|
1532
|
+
filename,
|
|
1533
|
+
totalMemories: allEntries.length,
|
|
1534
|
+
agentMdChars: indexResult.charCount
|
|
1535
|
+
}
|
|
1536
|
+
};
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
function createDeleteMemoryTool(projectPath) {
|
|
1542
|
+
return {
|
|
1543
|
+
name: "delete-memory",
|
|
1544
|
+
description: "Delete a memory by name. Removes the file and its index entry in agent.md. If multiple memories share the same name (different types), specify type to disambiguate.",
|
|
1545
|
+
parameters: {
|
|
1546
|
+
type: "object",
|
|
1547
|
+
properties: {
|
|
1548
|
+
name: {
|
|
1549
|
+
type: "string",
|
|
1550
|
+
description: "Name of the memory to delete (case-insensitive match)"
|
|
1551
|
+
},
|
|
1552
|
+
type: {
|
|
1553
|
+
type: "string",
|
|
1554
|
+
enum: VALID_TYPES$1,
|
|
1555
|
+
description: "Optional: memory type to disambiguate when multiple memories share the same name"
|
|
1556
|
+
}
|
|
1557
|
+
},
|
|
1558
|
+
required: ["name"]
|
|
1559
|
+
},
|
|
1560
|
+
execute: async (input) => {
|
|
1561
|
+
const name = String(input.name || "").trim();
|
|
1562
|
+
const type = input.type ? String(input.type) : void 0;
|
|
1563
|
+
if (!name) return toolError("MISSING_PARAMETER", "name is required.", {
|
|
1564
|
+
suggestions: ["Provide the name of the memory to delete."]
|
|
1565
|
+
});
|
|
1566
|
+
const allMatches = findAllMemoriesByName(projectPath, name);
|
|
1567
|
+
if (allMatches.length === 0) {
|
|
1568
|
+
return toolError("NOT_FOUND", `Memory not found: "${name}"`, {
|
|
1569
|
+
suggestions: ["Check the memory name — it is case-insensitive. Current memories are listed in agent.md."]
|
|
1282
1570
|
});
|
|
1283
1571
|
}
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1572
|
+
if (allMatches.length > 1 && !type) {
|
|
1573
|
+
const types = allMatches.map((m) => m.frontmatter.type).join(", ");
|
|
1574
|
+
return toolError("AMBIGUOUS", `Multiple memories named "${name}" (types: ${types}). Specify type to disambiguate.`, {
|
|
1575
|
+
suggestions: [`Add type parameter: one of ${types}`]
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
const existing = type ? findMemoryByName(projectPath, name, type) : allMatches[0];
|
|
1579
|
+
if (!existing) {
|
|
1580
|
+
return toolError("NOT_FOUND", `Memory not found: "${name}" with type "${type}"`, {
|
|
1581
|
+
suggestions: ["Check the memory name and type."]
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1584
|
+
return withIndexLock(() => {
|
|
1585
|
+
deleteMemoryFile(projectPath, existing.filename);
|
|
1586
|
+
const allEntries = listMemoryFiles(projectPath);
|
|
1587
|
+
updateAgentMdIndex(projectPath, allEntries);
|
|
1588
|
+
return {
|
|
1589
|
+
success: true,
|
|
1590
|
+
data: {
|
|
1591
|
+
message: `Memory deleted: ${name}`,
|
|
1592
|
+
totalMemories: allEntries.length
|
|
1593
|
+
}
|
|
1594
|
+
};
|
|
1595
|
+
});
|
|
1288
1596
|
}
|
|
1289
1597
|
};
|
|
1290
1598
|
}
|
|
1291
|
-
function
|
|
1599
|
+
function createMemoryTools(projectPath) {
|
|
1292
1600
|
return [
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
createArtifactSearchTool(params.projectPath),
|
|
1296
|
-
createUpdateMemoryTool(params.projectPath)
|
|
1601
|
+
createSaveMemoryTool(projectPath),
|
|
1602
|
+
createDeleteMemoryTool(projectPath)
|
|
1297
1603
|
];
|
|
1298
1604
|
}
|
|
1299
1605
|
const WEB_DEFAULTS = {
|
|
@@ -1311,8 +1617,11 @@ const WEB_DEFAULTS = {
|
|
|
1311
1617
|
maxFetchMaxChars: 2e5,
|
|
1312
1618
|
defaultFetchTimeoutMs: 3e4,
|
|
1313
1619
|
maxArxivCacheEntries: 100,
|
|
1314
|
-
arxivSearchCacheTtlMs: 10 * 60 * 1e3
|
|
1620
|
+
arxivSearchCacheTtlMs: 10 * 60 * 1e3,
|
|
1315
1621
|
// 10 min
|
|
1622
|
+
/** Content above this size is saved to disk; agent gets preview + file path */
|
|
1623
|
+
fetchPersistThresholdChars: 3e4,
|
|
1624
|
+
fetchPreviewChars: 2e3
|
|
1316
1625
|
};
|
|
1317
1626
|
class ProviderRateGate {
|
|
1318
1627
|
constructor(minIntervalMs) {
|
|
@@ -1559,7 +1868,6 @@ function createWebSearchTool(ctx) {
|
|
|
1559
1868
|
const providerRequested = normalizeSearchProvider(params.provider);
|
|
1560
1869
|
const braveApiKey = process.env.BRAVE_API_KEY?.trim();
|
|
1561
1870
|
let effectiveProvider = providerRequested === "auto" ? braveApiKey ? "brave" : "arxiv" : providerRequested;
|
|
1562
|
-
ctx.onToolCall?.("web_search", { query, count, provider: effectiveProvider });
|
|
1563
1871
|
let results = [];
|
|
1564
1872
|
try {
|
|
1565
1873
|
if (effectiveProvider === "brave") {
|
|
@@ -1603,7 +1911,6 @@ function createWebSearchTool(ctx) {
|
|
|
1603
1911
|
count: results.length,
|
|
1604
1912
|
results
|
|
1605
1913
|
};
|
|
1606
|
-
ctx.onToolResult?.("web_search", payload);
|
|
1607
1914
|
return toAgentResult("web_search", {
|
|
1608
1915
|
success: true,
|
|
1609
1916
|
data: payload
|
|
@@ -1615,7 +1922,7 @@ function createWebFetchTool(ctx) {
|
|
|
1615
1922
|
return {
|
|
1616
1923
|
name: "web_fetch",
|
|
1617
1924
|
label: "Web Fetch",
|
|
1618
|
-
description: "Fetch a URL and extract readable text or markdown
|
|
1925
|
+
description: "Fetch a URL and extract readable text or markdown. Content over 30K chars is saved to disk — use the read tool on the returned content_path to access full content.",
|
|
1619
1926
|
parameters: WebFetchSchema,
|
|
1620
1927
|
execute: async (_toolCallId, rawParams) => {
|
|
1621
1928
|
const params = rawParams;
|
|
@@ -1644,7 +1951,6 @@ function createWebFetchTool(ctx) {
|
|
|
1644
1951
|
const maxChars = typeof maxCharsRaw === "number" ? Math.max(100, Math.min(WEB_DEFAULTS.maxFetchMaxChars, Math.floor(maxCharsRaw))) : WEB_DEFAULTS.defaultFetchMaxChars;
|
|
1645
1952
|
const timeoutSecRaw = (typeof params.timeout_sec === "number" && Number.isFinite(params.timeout_sec) ? params.timeout_sec : void 0) ?? (typeof params.timeoutSec === "number" && Number.isFinite(params.timeoutSec) ? params.timeoutSec : void 0);
|
|
1646
1953
|
const timeoutMs = typeof timeoutSecRaw === "number" ? Math.max(1e3, Math.floor(timeoutSecRaw * 1e3)) : WEB_DEFAULTS.defaultFetchTimeoutMs;
|
|
1647
|
-
ctx.onToolCall?.("web_fetch", { url: url.toString(), extractMode, maxChars });
|
|
1648
1954
|
let response;
|
|
1649
1955
|
const controller = new AbortController();
|
|
1650
1956
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -1682,16 +1988,37 @@ Source: ${url.toString()}
|
|
|
1682
1988
|
---
|
|
1683
1989
|
|
|
1684
1990
|
${sliced}` : sliced;
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1991
|
+
let payload;
|
|
1992
|
+
if (output.length > WEB_DEFAULTS.fetchPersistThresholdChars) {
|
|
1993
|
+
const hash = createHash("md5").update(url.toString() + Date.now()).digest("hex").slice(0, 12);
|
|
1994
|
+
const ext = extractMode === "markdown" ? "md" : "txt";
|
|
1995
|
+
const contentDir = path.join(ctx.projectPath, "web-content");
|
|
1996
|
+
await mkdir(contentDir, { recursive: true });
|
|
1997
|
+
const filePath = path.join(contentDir, `${hash}.${ext}`);
|
|
1998
|
+
await writeFile(filePath, output, "utf-8");
|
|
1999
|
+
const previewRaw = output.slice(0, WEB_DEFAULTS.fetchPreviewChars);
|
|
2000
|
+
const lastNl = previewRaw.lastIndexOf("\n");
|
|
2001
|
+
const preview = (lastNl > WEB_DEFAULTS.fetchPreviewChars * 0.5 ? previewRaw.slice(0, lastNl) : previewRaw) + "\n...";
|
|
2002
|
+
payload = {
|
|
2003
|
+
url: url.toString(),
|
|
2004
|
+
status_code: response.status,
|
|
2005
|
+
content_type: contentType,
|
|
2006
|
+
extract_mode: extractMode,
|
|
2007
|
+
chars: normalized.length,
|
|
2008
|
+
content_path: path.relative(ctx.workspacePath, filePath),
|
|
2009
|
+
preview
|
|
2010
|
+
};
|
|
2011
|
+
} else {
|
|
2012
|
+
payload = {
|
|
2013
|
+
url: url.toString(),
|
|
2014
|
+
status_code: response.status,
|
|
2015
|
+
content_type: contentType,
|
|
2016
|
+
extract_mode: extractMode,
|
|
2017
|
+
chars: normalized.length,
|
|
2018
|
+
truncated,
|
|
2019
|
+
content: output || "(empty response)"
|
|
2020
|
+
};
|
|
2021
|
+
}
|
|
1695
2022
|
return toAgentResult("web_fetch", {
|
|
1696
2023
|
success: response.ok,
|
|
1697
2024
|
data: payload,
|
|
@@ -1741,11 +2068,20 @@ Memory model:
|
|
|
1741
2068
|
- Session context is maintained automatically via periodic summaries.
|
|
1742
2069
|
- For quick-reference info, create a note via artifact-create({ type: "note", ... }).
|
|
1743
2070
|
|
|
1744
|
-
Long-term memory:
|
|
1745
|
-
-
|
|
1746
|
-
-
|
|
1747
|
-
|
|
1748
|
-
|
|
2071
|
+
Long-term memory (auto-memory):
|
|
2072
|
+
- Your agent.md "## Agent Memory" section shows an index of saved memories. It is injected into your context every turn, so you always see what is remembered.
|
|
2073
|
+
- Use save-memory to persist information across sessions. Each memory becomes a file in .research-pilot/memory/ with one of four types:
|
|
2074
|
+
* user — who the user is: role, expertise, preferences, communication style
|
|
2075
|
+
* feedback — corrections to your behavior: "don't do X", "when I say Y I mean Z"
|
|
2076
|
+
* project — key decisions, deadlines, collaborators, research directions
|
|
2077
|
+
* reference — pointers to external resources, reusable facts, definitions
|
|
2078
|
+
- Use delete-memory to remove outdated entries by name.
|
|
2079
|
+
- WHEN to save: user explicitly states a preference, corrects your behavior, a non-obvious project decision is made, or user points to an external resource. Most turns do NOT warrant saving a memory — only save when you learn something genuinely new that a future session would need.
|
|
2080
|
+
- WHEN NOT to save: routine task results, things already in workspace files or git, ephemeral conversation details, information derivable from the codebase, anything already captured in an existing memory.
|
|
2081
|
+
- Before saving, check agent.md index — if a similar memory exists, update it instead of creating a duplicate.
|
|
2082
|
+
- Keep each memory atomic (one concept) and concise.
|
|
2083
|
+
- Note: save-memory is for cross-session meta-information (preferences, context). Use artifact-create for work products (notes, analysis results, review memos).
|
|
2084
|
+
- You can read full memory files with the read tool at .research-pilot/memory/<filename>.
|
|
1749
2085
|
|
|
1750
2086
|
Coding tasks:
|
|
1751
2087
|
- For code implementation, follow test-first workflow: write/update test → confirm it fails → implement → confirm it passes.
|
|
@@ -2550,7 +2886,6 @@ function createLiteratureSearchTool(ctx) {
|
|
|
2550
2886
|
suggestions: ["Ensure the agent runtime has an LLM provider configured (callLlm in ResearchToolContext)."]
|
|
2551
2887
|
}));
|
|
2552
2888
|
}
|
|
2553
|
-
ctx.onToolCall?.("literature-search", { query, context: extraContext });
|
|
2554
2889
|
const planUserPrompt = extraContext ? `Research request: ${query}
|
|
2555
2890
|
|
|
2556
2891
|
Additional context: ${extraContext}` : `Research request: ${query}`;
|
|
@@ -2727,9 +3062,9 @@ Additional context: ${extraContext}` : `Research request: ${query}`;
|
|
|
2727
3062
|
}
|
|
2728
3063
|
}
|
|
2729
3064
|
}
|
|
2730
|
-
const reviewDir = path.join(ctx.projectPath, ".research-pilot", "literature-runs", runId);
|
|
3065
|
+
const reviewDir = path$1.join(ctx.projectPath, ".research-pilot", "literature-runs", runId);
|
|
2731
3066
|
fs.mkdirSync(reviewDir, { recursive: true });
|
|
2732
|
-
const fullReviewPath = path.join(reviewDir, "review.json");
|
|
3067
|
+
const fullReviewPath = path$1.join(reviewDir, "review.json");
|
|
2733
3068
|
fs.writeFileSync(fullReviewPath, JSON.stringify({
|
|
2734
3069
|
plan,
|
|
2735
3070
|
allPapersCount: deduplicated.length,
|
|
@@ -2738,7 +3073,7 @@ Additional context: ${extraContext}` : `Research request: ${query}`;
|
|
|
2738
3073
|
queriesUsed,
|
|
2739
3074
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2740
3075
|
}, null, 2), "utf-8");
|
|
2741
|
-
const relReviewPath = path.relative(ctx.projectPath, fullReviewPath);
|
|
3076
|
+
const relReviewPath = path$1.relative(ctx.projectPath, fullReviewPath);
|
|
2742
3077
|
const payload = {
|
|
2743
3078
|
totalFound: deduplicated.length,
|
|
2744
3079
|
reviewedCount: review.relevantPapers.length,
|
|
@@ -2761,7 +3096,6 @@ Additional context: ${extraContext}` : `Research request: ${query}`;
|
|
|
2761
3096
|
runId,
|
|
2762
3097
|
queriesUsed: queriesUsed.slice(0, 10)
|
|
2763
3098
|
};
|
|
2764
|
-
ctx.onToolResult?.("literature-search", payload);
|
|
2765
3099
|
return toAgentResult("literature-search", toolSuccess(payload, pipelineWarnings.length > 0 ? pipelineWarnings : void 0));
|
|
2766
3100
|
}
|
|
2767
3101
|
};
|
|
@@ -2804,16 +3138,16 @@ const FORMAT_EXTENSIONS = {
|
|
|
2804
3138
|
zip: "zip"
|
|
2805
3139
|
};
|
|
2806
3140
|
function resolveWithinProject(projectPath, targetPath) {
|
|
2807
|
-
const root = path.resolve(projectPath);
|
|
2808
|
-
const resolved = targetPath.startsWith("/") ? path.resolve(targetPath) : path.resolve(root, targetPath);
|
|
2809
|
-
const rel = path.relative(root, resolved);
|
|
2810
|
-
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
3141
|
+
const root = path$1.resolve(projectPath);
|
|
3142
|
+
const resolved = targetPath.startsWith("/") ? path$1.resolve(targetPath) : path$1.resolve(root, targetPath);
|
|
3143
|
+
const rel = path$1.relative(root, resolved);
|
|
3144
|
+
if (rel.startsWith("..") || path$1.isAbsolute(rel)) {
|
|
2811
3145
|
throw new Error(`Path escapes project directory: ${targetPath}`);
|
|
2812
3146
|
}
|
|
2813
3147
|
return resolved;
|
|
2814
3148
|
}
|
|
2815
3149
|
function toProjectRelative(projectPath, absolutePath) {
|
|
2816
|
-
return path.relative(path.resolve(projectPath), absolutePath);
|
|
3150
|
+
return path$1.relative(path$1.resolve(projectPath), absolutePath);
|
|
2817
3151
|
}
|
|
2818
3152
|
function isHttpUrl(value) {
|
|
2819
3153
|
try {
|
|
@@ -2833,7 +3167,7 @@ function sanitizeBaseName(value) {
|
|
|
2833
3167
|
function inferUrlBaseName(sourceUrl) {
|
|
2834
3168
|
try {
|
|
2835
3169
|
const parsed = new URL(sourceUrl);
|
|
2836
|
-
const candidate = path.parse(parsed.pathname).name || parsed.hostname;
|
|
3170
|
+
const candidate = path$1.parse(parsed.pathname).name || parsed.hostname;
|
|
2837
3171
|
return sanitizeBaseName(candidate);
|
|
2838
3172
|
} catch {
|
|
2839
3173
|
return sanitizeBaseName(sourceUrl);
|
|
@@ -2843,7 +3177,7 @@ function outputExtensionForMode(mode) {
|
|
|
2843
3177
|
return mode === "text" ? ".txt" : ".md";
|
|
2844
3178
|
}
|
|
2845
3179
|
function extensionFromPath(filePath) {
|
|
2846
|
-
const ext = path.extname(filePath).replace(".", "").toLowerCase();
|
|
3180
|
+
const ext = path$1.extname(filePath).replace(".", "").toLowerCase();
|
|
2847
3181
|
return ext || void 0;
|
|
2848
3182
|
}
|
|
2849
3183
|
function normalizeMode(value) {
|
|
@@ -3077,15 +3411,15 @@ async function downloadToProject(projectPath, sourceUrl) {
|
|
|
3077
3411
|
if (bytes.length > DEFAULT_MAX_DOWNLOAD_BYTES) {
|
|
3078
3412
|
return { ok: false, error: `Downloaded file too large (${bytes.length} bytes > ${DEFAULT_MAX_DOWNLOAD_BYTES} bytes)` };
|
|
3079
3413
|
}
|
|
3080
|
-
const downloadDir = path.join(path.resolve(projectPath), ".research-pilot", "cache", "downloads");
|
|
3414
|
+
const downloadDir = path$1.join(path$1.resolve(projectPath), ".research-pilot", "cache", "downloads");
|
|
3081
3415
|
await fsp.mkdir(downloadDir, { recursive: true });
|
|
3082
3416
|
const urlObj = new URL(sourceUrl);
|
|
3083
3417
|
const fromExt = extensionFromPath(urlObj.pathname);
|
|
3084
3418
|
const fromContentType = detectFormatFromContentType(response.headers.get("content-type"));
|
|
3085
3419
|
const chosenExt = FORMAT_EXTENSIONS[fromContentType || ""] || FORMAT_EXTENSIONS[fromExt || ""] || "bin";
|
|
3086
|
-
const hash = createHash("sha256").update(sourceUrl).digest("hex").slice(0, 12);
|
|
3420
|
+
const hash = createHash$1("sha256").update(sourceUrl).digest("hex").slice(0, 12);
|
|
3087
3421
|
const fileName = `${isoStamp()}-${hash}.${chosenExt}`;
|
|
3088
|
-
const inputPath = path.join(downloadDir, fileName);
|
|
3422
|
+
const inputPath = path$1.join(downloadDir, fileName);
|
|
3089
3423
|
await fsp.writeFile(inputPath, bytes);
|
|
3090
3424
|
return {
|
|
3091
3425
|
ok: true,
|
|
@@ -3165,9 +3499,9 @@ ${sections}
|
|
|
3165
3499
|
`;
|
|
3166
3500
|
}
|
|
3167
3501
|
function buildPerRangeOutputPath(baseAbsolutePath, range, mode) {
|
|
3168
|
-
const parsed = path.parse(baseAbsolutePath);
|
|
3502
|
+
const parsed = path$1.parse(baseAbsolutePath);
|
|
3169
3503
|
const ext = parsed.ext || outputExtensionForMode(mode);
|
|
3170
|
-
return path.join(parsed.dir, `${parsed.name}-${rangeFileToken(range)}${ext}`);
|
|
3504
|
+
return path$1.join(parsed.dir, `${parsed.name}-${rangeFileToken(range)}${ext}`);
|
|
3171
3505
|
}
|
|
3172
3506
|
function buildPreview(text, maxPreviewChars = DEFAULT_PREVIEW_CHARS) {
|
|
3173
3507
|
const content = text.slice(0, maxPreviewChars);
|
|
@@ -3179,14 +3513,14 @@ function buildPreview(text, maxPreviewChars = DEFAULT_PREVIEW_CHARS) {
|
|
|
3179
3513
|
};
|
|
3180
3514
|
}
|
|
3181
3515
|
function defaultOutputPath(args) {
|
|
3182
|
-
const outputDir = path.join(
|
|
3183
|
-
path.resolve(args.projectPath),
|
|
3516
|
+
const outputDir = path$1.join(
|
|
3517
|
+
path$1.resolve(args.projectPath),
|
|
3184
3518
|
".research-pilot",
|
|
3185
3519
|
"cache",
|
|
3186
3520
|
"converted"
|
|
3187
3521
|
);
|
|
3188
|
-
const baseName = args.isUrl ? inferUrlBaseName(args.sourceRaw) : sanitizeBaseName(path.parse(args.inputPath || "document").name || "document");
|
|
3189
|
-
return path.join(outputDir, `${baseName}-${isoStamp()}${outputExtensionForMode(args.mode)}`);
|
|
3522
|
+
const baseName = args.isUrl ? inferUrlBaseName(args.sourceRaw) : sanitizeBaseName(path$1.parse(args.inputPath || "document").name || "document");
|
|
3523
|
+
return path$1.join(outputDir, `${baseName}-${isoStamp()}${outputExtensionForMode(args.mode)}`);
|
|
3190
3524
|
}
|
|
3191
3525
|
function failure(payload) {
|
|
3192
3526
|
return toAgentResult("convert_document", { success: false, error: payload.error, data: payload });
|
|
@@ -3369,7 +3703,7 @@ function createConvertDocumentTool(ctx) {
|
|
|
3369
3703
|
mode
|
|
3370
3704
|
});
|
|
3371
3705
|
}
|
|
3372
|
-
await fsp.mkdir(path.dirname(outputAbsolutePath), { recursive: true });
|
|
3706
|
+
await fsp.mkdir(path$1.dirname(outputAbsolutePath), { recursive: true });
|
|
3373
3707
|
let converter;
|
|
3374
3708
|
let producedText = "";
|
|
3375
3709
|
let pageCount;
|
|
@@ -3402,7 +3736,7 @@ function createConvertDocumentTool(ctx) {
|
|
|
3402
3736
|
anyTruncated = true;
|
|
3403
3737
|
}
|
|
3404
3738
|
const segmentAbsPath = buildPerRangeOutputPath(outputAbsolutePath, range, mode);
|
|
3405
|
-
await fsp.mkdir(path.dirname(segmentAbsPath), { recursive: true });
|
|
3739
|
+
await fsp.mkdir(path$1.dirname(segmentAbsPath), { recursive: true });
|
|
3406
3740
|
await fsp.writeFile(segmentAbsPath, segmentText, "utf8");
|
|
3407
3741
|
const segmentRelPath = toProjectRelative(projectPath, segmentAbsPath);
|
|
3408
3742
|
const segmentPreview = buildPreview(segmentText);
|
|
@@ -3541,7 +3875,7 @@ function createDataAnalyzeTool(ctx) {
|
|
|
3541
3875
|
suggestions: ["Valid task types: analyze, visualize, transform, model."]
|
|
3542
3876
|
}));
|
|
3543
3877
|
}
|
|
3544
|
-
const absDataFile = path.resolve(ctx.workspacePath, filePath);
|
|
3878
|
+
const absDataFile = path$1.resolve(ctx.workspacePath, filePath);
|
|
3545
3879
|
if (!fs.existsSync(absDataFile)) {
|
|
3546
3880
|
return toAgentResult("data_analyze", toolError("FILE_NOT_FOUND", `File not found: ${filePath}`, {
|
|
3547
3881
|
suggestions: [
|
|
@@ -3551,18 +3885,17 @@ function createDataAnalyzeTool(ctx) {
|
|
|
3551
3885
|
context: { workspacePath: ctx.workspacePath, resolvedPath: absDataFile }
|
|
3552
3886
|
}));
|
|
3553
3887
|
}
|
|
3554
|
-
ctx.onToolCall?.("data_analyze", { file_path: filePath, instructions, task_type: taskType });
|
|
3555
3888
|
const runId = Date.now().toString(36);
|
|
3556
|
-
const outputBase = path.join(ctx.workspacePath, ".research-pilot", "data-runs", runId);
|
|
3557
|
-
const figuresDir = path.join(outputBase, "figures");
|
|
3558
|
-
const tablesDir = path.join(outputBase, "tables");
|
|
3559
|
-
const dataDir = path.join(outputBase, "data");
|
|
3889
|
+
const outputBase = path$1.join(ctx.workspacePath, ".research-pilot", "data-runs", runId);
|
|
3890
|
+
const figuresDir = path$1.join(outputBase, "figures");
|
|
3891
|
+
const tablesDir = path$1.join(outputBase, "tables");
|
|
3892
|
+
const dataDir = path$1.join(outputBase, "data");
|
|
3560
3893
|
fs.mkdirSync(figuresDir, { recursive: true });
|
|
3561
3894
|
fs.mkdirSync(tablesDir, { recursive: true });
|
|
3562
3895
|
fs.mkdirSync(dataDir, { recursive: true });
|
|
3563
|
-
const resultsFile = path.join(outputBase, "results.json");
|
|
3896
|
+
const resultsFile = path$1.join(outputBase, "results.json");
|
|
3564
3897
|
const rawPreview = fs.readFileSync(absDataFile, "utf-8").slice(0, 2e3);
|
|
3565
|
-
const ext = path.extname(absDataFile).toLowerCase();
|
|
3898
|
+
const ext = path$1.extname(absDataFile).toLowerCase();
|
|
3566
3899
|
const formatHint = ext === ".csv" ? "CSV" : ext === ".tsv" ? "TSV" : ext === ".json" ? "JSON" : ext === ".xlsx" ? "XLSX" : "unknown";
|
|
3567
3900
|
if (!ctx.callLlm) {
|
|
3568
3901
|
return toAgentResult("data_analyze", toolError("LLM_UNAVAILABLE", "LLM not available for code generation.", {
|
|
@@ -3610,7 +3943,7 @@ function createDataAnalyzeTool(ctx) {
|
|
|
3610
3943
|
""
|
|
3611
3944
|
].join("\n");
|
|
3612
3945
|
const fullScript = DATA_CODE_TEMPLATE + pathDefinitions + "\n" + generatedCode;
|
|
3613
|
-
const scriptPath = path.join(outputBase, "script.py");
|
|
3946
|
+
const scriptPath = path$1.join(outputBase, "script.py");
|
|
3614
3947
|
fs.writeFileSync(scriptPath, fullScript, "utf-8");
|
|
3615
3948
|
try {
|
|
3616
3949
|
const { stdout, stderr } = await execFileAsync("python3", [scriptPath], {
|
|
@@ -3637,7 +3970,7 @@ function createDataAnalyzeTool(ctx) {
|
|
|
3637
3970
|
outputs.push({
|
|
3638
3971
|
name: f,
|
|
3639
3972
|
type,
|
|
3640
|
-
path: path.relative(ctx.workspacePath, path.join(dir, f))
|
|
3973
|
+
path: path$1.relative(ctx.workspacePath, path$1.join(dir, f))
|
|
3641
3974
|
});
|
|
3642
3975
|
}
|
|
3643
3976
|
}
|
|
@@ -3650,26 +3983,25 @@ function createDataAnalyzeTool(ctx) {
|
|
|
3650
3983
|
summary: manifest.summary,
|
|
3651
3984
|
warnings: manifest.warnings
|
|
3652
3985
|
} : void 0,
|
|
3653
|
-
scriptPath: path.relative(ctx.workspacePath, scriptPath),
|
|
3986
|
+
scriptPath: path$1.relative(ctx.workspacePath, scriptPath),
|
|
3654
3987
|
runId
|
|
3655
3988
|
};
|
|
3656
|
-
ctx.onToolResult?.("data_analyze", payload);
|
|
3657
3989
|
return toAgentResult("data_analyze", { success: true, data: payload });
|
|
3658
3990
|
} catch (err) {
|
|
3659
3991
|
const errorDetail = err.stderr?.slice(0, 2e3) || err.message;
|
|
3660
3992
|
return toAgentResult("data_analyze", toolError("EXECUTION_FAILED", `Python execution failed: ${errorDetail}`, {
|
|
3661
3993
|
retryable: true,
|
|
3662
3994
|
suggestions: [
|
|
3663
|
-
`Review the generated script at ${path.relative(ctx.workspacePath, scriptPath)} for errors.`,
|
|
3995
|
+
`Review the generated script at ${path$1.relative(ctx.workspacePath, scriptPath)} for errors.`,
|
|
3664
3996
|
"Check that required Python packages (pandas, matplotlib, seaborn, etc.) are installed.",
|
|
3665
3997
|
"Try simplifying the analysis instructions."
|
|
3666
3998
|
],
|
|
3667
3999
|
context: {
|
|
3668
|
-
scriptPath: path.relative(ctx.workspacePath, scriptPath),
|
|
4000
|
+
scriptPath: path$1.relative(ctx.workspacePath, scriptPath),
|
|
3669
4001
|
runId
|
|
3670
4002
|
},
|
|
3671
4003
|
data: {
|
|
3672
|
-
scriptPath: path.relative(ctx.workspacePath, scriptPath),
|
|
4004
|
+
scriptPath: path$1.relative(ctx.workspacePath, scriptPath),
|
|
3673
4005
|
runId
|
|
3674
4006
|
}
|
|
3675
4007
|
}));
|
|
@@ -3717,20 +4049,168 @@ function wrapResearchTool(tool) {
|
|
|
3717
4049
|
}
|
|
3718
4050
|
function createResearchTools(ctx) {
|
|
3719
4051
|
const tools = [];
|
|
3720
|
-
tools.push(createWebSearchTool(
|
|
4052
|
+
tools.push(createWebSearchTool());
|
|
3721
4053
|
tools.push(createWebFetchTool(ctx));
|
|
3722
4054
|
tools.push(createLiteratureSearchTool(ctx));
|
|
3723
4055
|
tools.push(createConvertDocumentTool(ctx));
|
|
3724
4056
|
tools.push(createDataAnalyzeTool(ctx));
|
|
3725
|
-
const
|
|
4057
|
+
const artifactTools = createResearchMemoryTools({
|
|
3726
4058
|
sessionId: ctx.sessionId,
|
|
3727
4059
|
projectPath: ctx.projectPath
|
|
3728
4060
|
});
|
|
3729
|
-
for (const tool of
|
|
4061
|
+
for (const tool of artifactTools) {
|
|
4062
|
+
tools.push(wrapResearchTool(tool));
|
|
4063
|
+
}
|
|
4064
|
+
const structuredMemoryTools = createMemoryTools(ctx.projectPath);
|
|
4065
|
+
for (const tool of structuredMemoryTools) {
|
|
3730
4066
|
tools.push(wrapResearchTool(tool));
|
|
3731
4067
|
}
|
|
3732
4068
|
return tools;
|
|
3733
4069
|
}
|
|
4070
|
+
const VALID_TYPES = ["user", "feedback", "project", "reference"];
|
|
4071
|
+
const EXTRACTION_PROMPT = `Analyze the recent conversation above and extract information worth remembering across sessions.
|
|
4072
|
+
|
|
4073
|
+
Rules:
|
|
4074
|
+
- Only extract DURABLE, IMPORTANT information — things a future session would need.
|
|
4075
|
+
- Types: "user" (preferences/background), "feedback" (behavior corrections), "project" (decisions/deadlines), "reference" (external pointers).
|
|
4076
|
+
- Ignore text inside "[Previous conversation summary]" or "[Session context]" markers — that is old context, not new information.
|
|
4077
|
+
- Do NOT extract: routine task results, ephemeral details, things already in workspace files.
|
|
4078
|
+
- Each memory should be atomic — one concept per entry.
|
|
4079
|
+
- If nothing is worth saving, return an empty array.
|
|
4080
|
+
|
|
4081
|
+
Return ONLY a JSON array (no markdown fences, no explanation):
|
|
4082
|
+
[{"type":"user|feedback|project|reference","name":"short-name","description":"one line","content":"full text"}]
|
|
4083
|
+
Or: []`;
|
|
4084
|
+
function simplifyMessages(messages, maxMessages) {
|
|
4085
|
+
const recent = messages.slice(-20);
|
|
4086
|
+
const result = [];
|
|
4087
|
+
for (const msg of recent) {
|
|
4088
|
+
if (msg.role !== "user" && msg.role !== "assistant") continue;
|
|
4089
|
+
let content = "";
|
|
4090
|
+
if (typeof msg.content === "string") {
|
|
4091
|
+
content = msg.content;
|
|
4092
|
+
} else if (Array.isArray(msg.content)) {
|
|
4093
|
+
for (const block of msg.content) {
|
|
4094
|
+
if (block && typeof block === "object" && "type" in block) {
|
|
4095
|
+
if (block.type === "text" && "text" in block) {
|
|
4096
|
+
const text = block.text;
|
|
4097
|
+
content += text.length > 500 ? text.slice(0, 500) + "...[truncated]" : text;
|
|
4098
|
+
content += "\n";
|
|
4099
|
+
} else if (block.type === "tool_use" && "name" in block) {
|
|
4100
|
+
content += `[Called ${block.name}]
|
|
4101
|
+
`;
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
4104
|
+
}
|
|
4105
|
+
}
|
|
4106
|
+
content = content.trim();
|
|
4107
|
+
if (content) {
|
|
4108
|
+
result.push({
|
|
4109
|
+
role: msg.role,
|
|
4110
|
+
content: content.slice(0, 2e3),
|
|
4111
|
+
timestamp: Date.now()
|
|
4112
|
+
});
|
|
4113
|
+
}
|
|
4114
|
+
}
|
|
4115
|
+
return result;
|
|
4116
|
+
}
|
|
4117
|
+
function agentCalledSaveMemoryThisTurn(messages) {
|
|
4118
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
4119
|
+
const msg = messages[i];
|
|
4120
|
+
if (msg.role === "user") break;
|
|
4121
|
+
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
4122
|
+
for (const block of msg.content) {
|
|
4123
|
+
if (block && typeof block === "object" && "type" in block && block.type === "tool_use") {
|
|
4124
|
+
if (block.name === "save-memory") return true;
|
|
4125
|
+
}
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
return false;
|
|
4130
|
+
}
|
|
4131
|
+
async function maybeExtractMemories(config, messages, turnCount, extractEveryN = 3) {
|
|
4132
|
+
if (process.env.RESEARCH_COPILOT_AUTO_EXTRACT !== "1") return;
|
|
4133
|
+
if (turnCount % extractEveryN !== 0) return;
|
|
4134
|
+
if (agentCalledSaveMemoryThisTurn(messages)) {
|
|
4135
|
+
if (config.debug) console.log("[Extractor] Skipped — agent called save-memory this turn");
|
|
4136
|
+
return;
|
|
4137
|
+
}
|
|
4138
|
+
try {
|
|
4139
|
+
const simplified = simplifyMessages(messages, 20);
|
|
4140
|
+
if (simplified.length < 2) return;
|
|
4141
|
+
simplified.push({
|
|
4142
|
+
role: "user",
|
|
4143
|
+
content: EXTRACTION_PROMPT,
|
|
4144
|
+
timestamp: Date.now()
|
|
4145
|
+
});
|
|
4146
|
+
const result = await completeSimple(config.model, {
|
|
4147
|
+
systemPrompt: config.systemPrompt,
|
|
4148
|
+
messages: simplified
|
|
4149
|
+
}, {
|
|
4150
|
+
maxTokens: 1024,
|
|
4151
|
+
apiKey: config.apiKey
|
|
4152
|
+
});
|
|
4153
|
+
const textContent = result.content.find((c) => c.type === "text");
|
|
4154
|
+
const text = textContent?.text?.trim() ?? "";
|
|
4155
|
+
if (!text || text === "[]") return;
|
|
4156
|
+
let jsonStr;
|
|
4157
|
+
const fenceMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
4158
|
+
if (fenceMatch) {
|
|
4159
|
+
jsonStr = fenceMatch[1].trim();
|
|
4160
|
+
} else {
|
|
4161
|
+
const firstBracket = text.indexOf("[");
|
|
4162
|
+
const lastBracket = text.lastIndexOf("]");
|
|
4163
|
+
if (firstBracket !== -1 && lastBracket > firstBracket) {
|
|
4164
|
+
jsonStr = text.slice(firstBracket, lastBracket + 1);
|
|
4165
|
+
} else {
|
|
4166
|
+
jsonStr = text;
|
|
4167
|
+
}
|
|
4168
|
+
}
|
|
4169
|
+
let extracted;
|
|
4170
|
+
try {
|
|
4171
|
+
extracted = JSON.parse(jsonStr);
|
|
4172
|
+
} catch (parseErr) {
|
|
4173
|
+
if (config.debug) {
|
|
4174
|
+
console.warn("[Extractor] JSON parse failed:", parseErr, "Raw text:", text.slice(0, 200));
|
|
4175
|
+
}
|
|
4176
|
+
return;
|
|
4177
|
+
}
|
|
4178
|
+
if (!Array.isArray(extracted) || extracted.length === 0) return;
|
|
4179
|
+
ensureMemoryDir(config.projectPath);
|
|
4180
|
+
const validEntries = [];
|
|
4181
|
+
for (const mem of extracted) {
|
|
4182
|
+
if (!mem.type || !mem.name || !mem.content) continue;
|
|
4183
|
+
if (!VALID_TYPES.includes(mem.type)) continue;
|
|
4184
|
+
const desc = (mem.description || "").trim();
|
|
4185
|
+
const contentFirstLine = mem.content.split("\n").find((l) => l.trim().length > 0) || "";
|
|
4186
|
+
const description = (desc || contentFirstLine.replace(/^#+\s*/, "").trim().slice(0, 120) || mem.name).replace(/\n/g, " ");
|
|
4187
|
+
validEntries.push({
|
|
4188
|
+
frontmatter: {
|
|
4189
|
+
name: mem.name,
|
|
4190
|
+
description,
|
|
4191
|
+
type: mem.type
|
|
4192
|
+
},
|
|
4193
|
+
content: mem.content,
|
|
4194
|
+
filename: memoryFilename(mem.type, mem.name)
|
|
4195
|
+
});
|
|
4196
|
+
}
|
|
4197
|
+
if (validEntries.length === 0) return;
|
|
4198
|
+
await withIndexLock(() => {
|
|
4199
|
+
for (const entry of validEntries) {
|
|
4200
|
+
writeMemoryFile(config.projectPath, entry);
|
|
4201
|
+
}
|
|
4202
|
+
const allEntries = listMemoryFiles(config.projectPath);
|
|
4203
|
+
updateAgentMdIndex(config.projectPath, allEntries);
|
|
4204
|
+
});
|
|
4205
|
+
if (config.debug) {
|
|
4206
|
+
console.log(`[Extractor] Saved ${validEntries.length} memories from conversation`);
|
|
4207
|
+
}
|
|
4208
|
+
} catch (err) {
|
|
4209
|
+
if (config.debug) {
|
|
4210
|
+
console.warn("[Extractor] Failed:", err);
|
|
4211
|
+
}
|
|
4212
|
+
}
|
|
4213
|
+
}
|
|
3734
4214
|
const SKILL_FILE_NAME = "SKILL.md";
|
|
3735
4215
|
const MAX_SCAN_DEPTH = 3;
|
|
3736
4216
|
let _builtinSkillsRoot = null;
|
|
@@ -3775,7 +4255,7 @@ function discoverSkillFiles(rootDir) {
|
|
|
3775
4255
|
continue;
|
|
3776
4256
|
}
|
|
3777
4257
|
for (const entry of entries) {
|
|
3778
|
-
const abs = path.join(current.dir, entry.name);
|
|
4258
|
+
const abs = path$1.join(current.dir, entry.name);
|
|
3779
4259
|
if (entry.isFile() && entry.name === SKILL_FILE_NAME) {
|
|
3780
4260
|
files.push(abs);
|
|
3781
4261
|
continue;
|
|
@@ -3806,7 +4286,7 @@ function parseSkillFile(skillFile, displayPath, source) {
|
|
|
3806
4286
|
tags,
|
|
3807
4287
|
triggers,
|
|
3808
4288
|
path: displayPath,
|
|
3809
|
-
dir: path.dirname(skillFile),
|
|
4289
|
+
dir: path$1.dirname(skillFile),
|
|
3810
4290
|
source,
|
|
3811
4291
|
content
|
|
3812
4292
|
};
|
|
@@ -3822,34 +4302,34 @@ function inferCategory(name, description) {
|
|
|
3822
4302
|
return "General";
|
|
3823
4303
|
}
|
|
3824
4304
|
function loadBuiltinSkills() {
|
|
3825
|
-
const skillsRoot = _builtinSkillsRoot ?? path.dirname(fileURLToPath(import.meta.url));
|
|
4305
|
+
const skillsRoot = _builtinSkillsRoot ?? path$1.dirname(fileURLToPath(import.meta.url));
|
|
3826
4306
|
if (!fs.existsSync(skillsRoot) || !fs.statSync(skillsRoot).isDirectory()) {
|
|
3827
4307
|
return [];
|
|
3828
4308
|
}
|
|
3829
4309
|
const files = discoverSkillFiles(skillsRoot);
|
|
3830
4310
|
const byName = /* @__PURE__ */ new Map();
|
|
3831
4311
|
for (const file of files) {
|
|
3832
|
-
const entry = parseSkillFile(file, `[builtin] ${path.relative(skillsRoot, file)}`, "builtin");
|
|
4312
|
+
const entry = parseSkillFile(file, `[builtin] ${path$1.relative(skillsRoot, file)}`, "builtin");
|
|
3833
4313
|
if (entry) byName.set(entry.name, entry);
|
|
3834
4314
|
}
|
|
3835
4315
|
return Array.from(byName.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
3836
4316
|
}
|
|
3837
4317
|
function loadWorkspaceSkills(workspacePath) {
|
|
3838
|
-
const skillRoot = path.resolve(workspacePath, ".research-pilot", "skills");
|
|
4318
|
+
const skillRoot = path$1.resolve(workspacePath, ".research-pilot", "skills");
|
|
3839
4319
|
if (!fs.existsSync(skillRoot) || !fs.statSync(skillRoot).isDirectory()) {
|
|
3840
4320
|
return [];
|
|
3841
4321
|
}
|
|
3842
4322
|
const files = discoverSkillFiles(skillRoot);
|
|
3843
|
-
const entries = files.map((file) => parseSkillFile(file, path.relative(workspacePath, file), "workspace")).filter((entry) => entry !== null);
|
|
4323
|
+
const entries = files.map((file) => parseSkillFile(file, path$1.relative(workspacePath, file), "workspace")).filter((entry) => entry !== null);
|
|
3844
4324
|
return entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
3845
4325
|
}
|
|
3846
4326
|
function loadUserSkills() {
|
|
3847
|
-
const userRoot = path.resolve(os.homedir(), ".research-pilot", "skills");
|
|
4327
|
+
const userRoot = path$1.resolve(os.homedir(), ".research-pilot", "skills");
|
|
3848
4328
|
if (!fs.existsSync(userRoot) || !fs.statSync(userRoot).isDirectory()) {
|
|
3849
4329
|
return [];
|
|
3850
4330
|
}
|
|
3851
4331
|
const files = discoverSkillFiles(userRoot);
|
|
3852
|
-
const entries = files.map((file) => parseSkillFile(file, `[user] ~/.research-pilot/skills/${path.relative(userRoot, file)}`, "user")).filter((entry) => entry !== null);
|
|
4332
|
+
const entries = files.map((file) => parseSkillFile(file, `[user] ~/.research-pilot/skills/${path$1.relative(userRoot, file)}`, "user")).filter((entry) => entry !== null);
|
|
3853
4333
|
return entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
3854
4334
|
}
|
|
3855
4335
|
function loadAllSkills(workspacePath) {
|
|
@@ -3894,7 +4374,7 @@ function resolveSkillDependencies(allSkills, directSelection) {
|
|
|
3894
4374
|
return result;
|
|
3895
4375
|
}
|
|
3896
4376
|
function readEnabledSkills(workspacePath) {
|
|
3897
|
-
const configPath = path.resolve(workspacePath, ".research-pilot", "skills-config.json");
|
|
4377
|
+
const configPath = path$1.resolve(workspacePath, ".research-pilot", "skills-config.json");
|
|
3898
4378
|
try {
|
|
3899
4379
|
const raw = fs.readFileSync(configPath, "utf8");
|
|
3900
4380
|
const config = JSON.parse(raw);
|
|
@@ -3904,9 +4384,9 @@ function readEnabledSkills(workspacePath) {
|
|
|
3904
4384
|
return null;
|
|
3905
4385
|
}
|
|
3906
4386
|
function writeEnabledSkills(workspacePath, enabledSkills) {
|
|
3907
|
-
const configDir = path.resolve(workspacePath, ".research-pilot");
|
|
4387
|
+
const configDir = path$1.resolve(workspacePath, ".research-pilot");
|
|
3908
4388
|
if (!fs.existsSync(configDir)) fs.mkdirSync(configDir, { recursive: true });
|
|
3909
|
-
const configPath = path.join(configDir, "skills-config.json");
|
|
4389
|
+
const configPath = path$1.join(configDir, "skills-config.json");
|
|
3910
4390
|
fs.writeFileSync(configPath, JSON.stringify({ enabledSkills }, null, 2), "utf8");
|
|
3911
4391
|
}
|
|
3912
4392
|
function buildSkillManifests(workspacePath) {
|
|
@@ -3930,15 +4410,15 @@ function buildSkillManifests(workspacePath) {
|
|
|
3930
4410
|
});
|
|
3931
4411
|
}
|
|
3932
4412
|
function installSkillToWorkspace(workspacePath, skillName, skillDir) {
|
|
3933
|
-
const destDir = path.resolve(workspacePath, ".research-pilot", "skills", skillName);
|
|
4413
|
+
const destDir = path$1.resolve(workspacePath, ".research-pilot", "skills", skillName);
|
|
3934
4414
|
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
|
|
3935
4415
|
copyDirSync(skillDir, destDir);
|
|
3936
4416
|
}
|
|
3937
4417
|
function copyDirSync(src, dest) {
|
|
3938
4418
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
3939
4419
|
for (const entry of entries) {
|
|
3940
|
-
const srcPath = path.join(src, entry.name);
|
|
3941
|
-
const destPath = path.join(dest, entry.name);
|
|
4420
|
+
const srcPath = path$1.join(src, entry.name);
|
|
4421
|
+
const destPath = path$1.join(dest, entry.name);
|
|
3942
4422
|
if (entry.isDirectory()) {
|
|
3943
4423
|
if (!fs.existsSync(destPath)) fs.mkdirSync(destPath, { recursive: true });
|
|
3944
4424
|
copyDirSync(srcPath, destPath);
|
|
@@ -4139,6 +4619,7 @@ async function createCoordinator(config) {
|
|
|
4139
4619
|
onStream,
|
|
4140
4620
|
onToolCall,
|
|
4141
4621
|
onToolResult,
|
|
4622
|
+
onToolProgress,
|
|
4142
4623
|
onUsage,
|
|
4143
4624
|
onSkillLoaded
|
|
4144
4625
|
} = config;
|
|
@@ -4210,11 +4691,11 @@ async function createCoordinator(config) {
|
|
|
4210
4691
|
}
|
|
4211
4692
|
}
|
|
4212
4693
|
}
|
|
4213
|
-
const wrappedOnToolResult = (tool, result, args) => {
|
|
4694
|
+
const wrappedOnToolResult = (tool, result, args, toolCallId) => {
|
|
4214
4695
|
if (activeTurnToolCallCount !== null) {
|
|
4215
4696
|
activeTurnToolCallCount++;
|
|
4216
4697
|
}
|
|
4217
|
-
onToolResult?.(tool, result, args);
|
|
4698
|
+
onToolResult?.(tool, result, args, toolCallId);
|
|
4218
4699
|
};
|
|
4219
4700
|
const toolCtx = {
|
|
4220
4701
|
workspacePath: projectPath,
|
|
@@ -4337,14 +4818,14 @@ The conversation continues below.`,
|
|
|
4337
4818
|
}
|
|
4338
4819
|
},
|
|
4339
4820
|
beforeToolCall: async (ctx) => {
|
|
4340
|
-
onToolCall?.(ctx.toolCall.name, ctx.args);
|
|
4821
|
+
onToolCall?.(ctx.toolCall.name, ctx.args, ctx.toolCall.id);
|
|
4341
4822
|
if (debug) {
|
|
4342
4823
|
console.log(` [Tool] ${ctx.toolCall.name}(${JSON.stringify(ctx.args).slice(0, 120)}...)`);
|
|
4343
4824
|
}
|
|
4344
4825
|
return void 0;
|
|
4345
4826
|
},
|
|
4346
4827
|
afterToolCall: async (ctx) => {
|
|
4347
|
-
wrappedOnToolResult(ctx.toolCall.name, ctx.result, ctx.args);
|
|
4828
|
+
wrappedOnToolResult(ctx.toolCall.name, ctx.result, ctx.args, ctx.toolCall.id);
|
|
4348
4829
|
if (ctx.toolCall.name === "load_skill" && onSkillLoaded) {
|
|
4349
4830
|
const args = ctx.args;
|
|
4350
4831
|
const result = ctx.result;
|
|
@@ -4355,7 +4836,7 @@ The conversation continues below.`,
|
|
|
4355
4836
|
return void 0;
|
|
4356
4837
|
}
|
|
4357
4838
|
});
|
|
4358
|
-
if (onStream || onUsage) {
|
|
4839
|
+
if (onStream || onUsage || onToolProgress) {
|
|
4359
4840
|
agent.subscribe((event) => {
|
|
4360
4841
|
if (event.type === "message_update" && onStream) {
|
|
4361
4842
|
if (event.assistantMessageEvent.type === "text_delta") {
|
|
@@ -4369,6 +4850,15 @@ The conversation continues below.`,
|
|
|
4369
4850
|
onUsage(usage, usage.cost);
|
|
4370
4851
|
}
|
|
4371
4852
|
}
|
|
4853
|
+
if (onToolProgress) {
|
|
4854
|
+
if (event.type === "tool_execution_start") {
|
|
4855
|
+
onToolProgress(event.toolName, event.toolCallId, "start", { args: event.args });
|
|
4856
|
+
} else if (event.type === "tool_execution_update") {
|
|
4857
|
+
onToolProgress(event.toolName, event.toolCallId, "update", { partialResult: event.partialResult });
|
|
4858
|
+
} else if (event.type === "tool_execution_end") {
|
|
4859
|
+
onToolProgress(event.toolName, event.toolCallId, "end", { result: event.result, isError: event.isError });
|
|
4860
|
+
}
|
|
4861
|
+
}
|
|
4372
4862
|
});
|
|
4373
4863
|
}
|
|
4374
4864
|
async function clearSessionMemory() {
|
|
@@ -4478,24 +4968,17 @@ ${historyText}`,
|
|
|
4478
4968
|
## User Instructions (agent.md)
|
|
4479
4969
|
|
|
4480
4970
|
${agentMdContent}`;
|
|
4481
|
-
}
|
|
4482
|
-
if (skillSummariesPrompt) {
|
|
4483
|
-
enrichedSystem = `${enrichedSystem}
|
|
4484
|
-
|
|
4485
|
-
${skillSummariesPrompt}`;
|
|
4486
4971
|
}
|
|
4487
4972
|
agent.setSystemPrompt(enrichedSystem);
|
|
4488
|
-
|
|
4489
|
-
if (
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
userMessage = `${contextParts.join("\n\n")}
|
|
4973
|
+
const contextParts = [];
|
|
4974
|
+
if (summaryContext) contextParts.push(summaryContext);
|
|
4975
|
+
if (skillSummariesPrompt) contextParts.push(skillSummariesPrompt);
|
|
4976
|
+
if (mentionContext) contextParts.push(mentionContext);
|
|
4977
|
+
let userMessage = contextParts.length > 0 ? `${contextParts.join("\n\n")}
|
|
4494
4978
|
|
|
4495
4979
|
---
|
|
4496
4980
|
|
|
4497
|
-
${message}
|
|
4498
|
-
}
|
|
4981
|
+
${message}` : message;
|
|
4499
4982
|
let perTurnToolCallCount = 0;
|
|
4500
4983
|
activeTurnToolCallCount = 0;
|
|
4501
4984
|
try {
|
|
@@ -4541,6 +5024,11 @@ ${message}`;
|
|
|
4541
5024
|
});
|
|
4542
5025
|
if (turnHistory.length > 8) turnHistory.shift();
|
|
4543
5026
|
void maybeGenerateSummary();
|
|
5027
|
+
void maybeExtractMemories(
|
|
5028
|
+
{ projectPath, model: piModel, apiKey, systemPrompt: enrichedSystem, debug },
|
|
5029
|
+
agent.state.messages,
|
|
5030
|
+
turnCount
|
|
5031
|
+
);
|
|
4544
5032
|
if (debug) {
|
|
4545
5033
|
console.log(`[Chat] Result: success=true, hasOutput=${!!responseText}, turn=${turnCount}`);
|
|
4546
5034
|
}
|
|
@@ -4596,10 +5084,10 @@ function sessionSummaryGet(projectPath, sessionId) {
|
|
|
4596
5084
|
}
|
|
4597
5085
|
}
|
|
4598
5086
|
class RateLimiter {
|
|
4599
|
-
constructor(
|
|
5087
|
+
constructor(configs2) {
|
|
4600
5088
|
this.timestamps = /* @__PURE__ */ new Map();
|
|
4601
5089
|
this.activeCounts = /* @__PURE__ */ new Map();
|
|
4602
|
-
this.configs =
|
|
5090
|
+
this.configs = configs2;
|
|
4603
5091
|
}
|
|
4604
5092
|
/**
|
|
4605
5093
|
* Wait until a request slot is available for the given source.
|
|
@@ -5200,11 +5688,11 @@ function searchEntities(projectPath, query, types) {
|
|
|
5200
5688
|
score: hit.score
|
|
5201
5689
|
}));
|
|
5202
5690
|
}
|
|
5203
|
-
const MENTION_RE = /@(note|paper|data|file|url):(?:"([^"]
|
|
5691
|
+
const MENTION_RE = /@(note|paper|data|file|url):(?:"((?:[^"\\]|\\.)*)"|(\S+))/g;
|
|
5204
5692
|
function parseMentions(message) {
|
|
5205
5693
|
const mentions = [];
|
|
5206
5694
|
const cleanMessage = message.replace(MENTION_RE, (_match, type, quoted, unquoted) => {
|
|
5207
|
-
const key = quoted || unquoted;
|
|
5695
|
+
const key = (quoted || unquoted).replace(/\\"/g, '"');
|
|
5208
5696
|
const raw = _match;
|
|
5209
5697
|
mentions.push({ type, key, raw });
|
|
5210
5698
|
return `[${type}: ${key}]`;
|
|
@@ -5212,7 +5700,7 @@ function parseMentions(message) {
|
|
|
5212
5700
|
return { cleanMessage, mentions };
|
|
5213
5701
|
}
|
|
5214
5702
|
function getCacheKey(filePath, mtime) {
|
|
5215
|
-
const hash = createHash
|
|
5703
|
+
const hash = createHash("sha256").update(`${filePath}:${mtime}`).digest("hex").slice(0, 16);
|
|
5216
5704
|
const name = basename(filePath).replace(/[^a-zA-Z0-9.-]/g, "_");
|
|
5217
5705
|
return `${name}-${hash}.json`;
|
|
5218
5706
|
}
|
|
@@ -5229,11 +5717,11 @@ function getCachedMarkdown(filePath, projectPath) {
|
|
|
5229
5717
|
const mtime = stat.mtimeMs;
|
|
5230
5718
|
const cacheDir = join(projectPath, PATHS.documentCache);
|
|
5231
5719
|
const cacheKey2 = getCacheKey(filePath, mtime);
|
|
5232
|
-
const
|
|
5233
|
-
if (!existsSync(
|
|
5720
|
+
const cachePath2 = join(cacheDir, cacheKey2);
|
|
5721
|
+
if (!existsSync(cachePath2)) {
|
|
5234
5722
|
return null;
|
|
5235
5723
|
}
|
|
5236
|
-
const entry = JSON.parse(readFileSync(
|
|
5724
|
+
const entry = JSON.parse(readFileSync(cachePath2, "utf-8"));
|
|
5237
5725
|
if (entry.sourcePath !== filePath || entry.sourceMtime !== mtime) {
|
|
5238
5726
|
return null;
|
|
5239
5727
|
}
|
|
@@ -5248,14 +5736,14 @@ function setCachedMarkdown(filePath, markdown, projectPath) {
|
|
|
5248
5736
|
const mtime = stat.mtimeMs;
|
|
5249
5737
|
const cacheDir = ensureCacheDir(projectPath);
|
|
5250
5738
|
const cacheKey2 = getCacheKey(filePath, mtime);
|
|
5251
|
-
const
|
|
5739
|
+
const cachePath2 = join(cacheDir, cacheKey2);
|
|
5252
5740
|
const entry = {
|
|
5253
5741
|
sourcePath: filePath,
|
|
5254
5742
|
sourceMtime: mtime,
|
|
5255
5743
|
markdown,
|
|
5256
5744
|
cachedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5257
5745
|
};
|
|
5258
|
-
writeFileSync(
|
|
5746
|
+
writeFileSync(cachePath2, JSON.stringify(entry, null, 2), "utf-8");
|
|
5259
5747
|
} catch (err) {
|
|
5260
5748
|
console.warn("[document-cache] Failed to cache markdown:", err);
|
|
5261
5749
|
}
|
|
@@ -5315,6 +5803,10 @@ function resolveEntity(ref, dir, entityType, projectPath) {
|
|
|
5315
5803
|
const DOCUMENT_EXTENSIONS = /* @__PURE__ */ new Set([".pdf", ".docx", ".xlsx", ".pptx", ".doc", ".xls", ".ppt", ".epub"]);
|
|
5316
5804
|
function resolveFile(ref, projectPath) {
|
|
5317
5805
|
const filePath = resolve(projectPath, ref.key);
|
|
5806
|
+
const normalizedProject = resolve(projectPath);
|
|
5807
|
+
if (!filePath.startsWith(normalizedProject + "/") && filePath !== normalizedProject) {
|
|
5808
|
+
return { ref, label: `file: ${ref.key}`, content: "", error: `Path outside workspace: ${ref.key}` };
|
|
5809
|
+
}
|
|
5318
5810
|
if (!existsSync(filePath)) {
|
|
5319
5811
|
return { ref, label: `file: ${ref.key}`, content: "", error: `File not found: ${ref.key}` };
|
|
5320
5812
|
}
|
|
@@ -5427,6 +5919,9 @@ NOTE: This is a data entity. The actual data is in the file at the path above. U
|
|
|
5427
5919
|
}
|
|
5428
5920
|
return JSON.stringify(entity, null, 2);
|
|
5429
5921
|
}
|
|
5922
|
+
const REFRESH_THROTTLE_MS = 5e3;
|
|
5923
|
+
const MAX_FILES = 5e3;
|
|
5924
|
+
const MAX_DEPTH = 8;
|
|
5430
5925
|
const SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
5431
5926
|
"node_modules",
|
|
5432
5927
|
"__pycache__",
|
|
@@ -5439,10 +5934,58 @@ const SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
5439
5934
|
".venv",
|
|
5440
5935
|
"venv"
|
|
5441
5936
|
]);
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5937
|
+
let cachedFiles = [];
|
|
5938
|
+
let cachedProjectPath = "";
|
|
5939
|
+
let lastRefreshAt = 0;
|
|
5940
|
+
let refreshPromise = null;
|
|
5941
|
+
async function getFileList(projectPath) {
|
|
5942
|
+
if (projectPath !== cachedProjectPath) {
|
|
5943
|
+
cachedFiles = [];
|
|
5944
|
+
cachedProjectPath = projectPath;
|
|
5945
|
+
lastRefreshAt = 0;
|
|
5946
|
+
}
|
|
5947
|
+
const now = Date.now();
|
|
5948
|
+
if (cachedFiles.length > 0 && now - lastRefreshAt < REFRESH_THROTTLE_MS) {
|
|
5949
|
+
return cachedFiles;
|
|
5950
|
+
}
|
|
5951
|
+
if (refreshPromise) return refreshPromise;
|
|
5952
|
+
refreshPromise = refreshFileList(projectPath).finally(() => {
|
|
5953
|
+
refreshPromise = null;
|
|
5954
|
+
});
|
|
5955
|
+
return refreshPromise;
|
|
5956
|
+
}
|
|
5957
|
+
async function refreshFileList(projectPath) {
|
|
5958
|
+
try {
|
|
5959
|
+
const files = await gitLsFiles(projectPath);
|
|
5960
|
+
cachedFiles = files.slice(0, MAX_FILES);
|
|
5961
|
+
} catch {
|
|
5962
|
+
cachedFiles = walkFilesSync(projectPath);
|
|
5963
|
+
}
|
|
5964
|
+
lastRefreshAt = Date.now();
|
|
5965
|
+
return cachedFiles;
|
|
5966
|
+
}
|
|
5967
|
+
function gitLsFiles(cwd) {
|
|
5968
|
+
return new Promise((resolve2, reject) => {
|
|
5969
|
+
execFile$1(
|
|
5970
|
+
"git",
|
|
5971
|
+
["ls-files", "-c", "-o", "--exclude-standard"],
|
|
5972
|
+
{ cwd, maxBuffer: 20 * 1024 * 1024, timeout: 5e3 },
|
|
5973
|
+
(err, stdout) => {
|
|
5974
|
+
if (err) return reject(err);
|
|
5975
|
+
const files = stdout.split("\n").filter(Boolean);
|
|
5976
|
+
if (files.length === 0) return reject(new Error("empty"));
|
|
5977
|
+
resolve2(files);
|
|
5978
|
+
}
|
|
5979
|
+
);
|
|
5980
|
+
});
|
|
5981
|
+
}
|
|
5982
|
+
function walkFilesSync(root) {
|
|
5983
|
+
const out = [];
|
|
5984
|
+
walk(root, "", 0, out);
|
|
5985
|
+
return out;
|
|
5986
|
+
}
|
|
5987
|
+
function walk(root, rel, depth, out) {
|
|
5988
|
+
if (depth > MAX_DEPTH || out.length >= MAX_FILES) return;
|
|
5446
5989
|
const dir = rel ? join(root, rel) : root;
|
|
5447
5990
|
let entries;
|
|
5448
5991
|
try {
|
|
@@ -5452,7 +5995,7 @@ function walkFiles(root, rel, depth, out) {
|
|
|
5452
5995
|
}
|
|
5453
5996
|
for (const name of entries) {
|
|
5454
5997
|
if (name.startsWith(".")) continue;
|
|
5455
|
-
if (out.length >=
|
|
5998
|
+
if (out.length >= MAX_FILES) return;
|
|
5456
5999
|
const childRel = rel ? `${rel}/${name}` : name;
|
|
5457
6000
|
const full = join(dir, name);
|
|
5458
6001
|
let stat;
|
|
@@ -5463,22 +6006,107 @@ function walkFiles(root, rel, depth, out) {
|
|
|
5463
6006
|
}
|
|
5464
6007
|
if (stat.isDirectory()) {
|
|
5465
6008
|
if (SKIP_DIRS.has(name)) continue;
|
|
5466
|
-
|
|
6009
|
+
walk(root, childRel, depth + 1, out);
|
|
5467
6010
|
} else {
|
|
5468
|
-
out.push(
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
6011
|
+
out.push(childRel);
|
|
6012
|
+
}
|
|
6013
|
+
}
|
|
6014
|
+
}
|
|
6015
|
+
let cache = null;
|
|
6016
|
+
let cachePath = "";
|
|
6017
|
+
function getEntityCache(projectPath) {
|
|
6018
|
+
if (cache && cachePath === projectPath) return cache;
|
|
6019
|
+
cache = {
|
|
6020
|
+
notes: listNotes(projectPath),
|
|
6021
|
+
papers: listLiterature(projectPath),
|
|
6022
|
+
data: listData(projectPath)
|
|
6023
|
+
};
|
|
6024
|
+
cachePath = projectPath;
|
|
6025
|
+
return cache;
|
|
6026
|
+
}
|
|
6027
|
+
function invalidateEntityCache() {
|
|
6028
|
+
cache = null;
|
|
6029
|
+
}
|
|
6030
|
+
const SCORE_MATCH = 16;
|
|
6031
|
+
const BONUS_FIRST_CHAR = 8;
|
|
6032
|
+
const BONUS_BOUNDARY = 8;
|
|
6033
|
+
const BONUS_CONSECUTIVE = 4;
|
|
6034
|
+
const PENALTY_GAP_START = 3;
|
|
6035
|
+
const PENALTY_GAP_EXT = 1;
|
|
6036
|
+
function fuzzyMatch(items, needle, getText, limit = 30) {
|
|
6037
|
+
if (!needle) {
|
|
6038
|
+
return items.slice(0, limit).map((item) => ({ item, score: 0 }));
|
|
6039
|
+
}
|
|
6040
|
+
const needleLower = needle.toLowerCase();
|
|
6041
|
+
const results = [];
|
|
6042
|
+
let worstInTopK = -Infinity;
|
|
6043
|
+
for (const item of items) {
|
|
6044
|
+
const haystack = getText(item);
|
|
6045
|
+
const score = scoreMatch(needleLower, haystack.toLowerCase(), haystack);
|
|
6046
|
+
if (score <= 0) continue;
|
|
6047
|
+
if (results.length >= limit && score <= worstInTopK) continue;
|
|
6048
|
+
insertSorted(results, { item, score }, limit);
|
|
6049
|
+
if (results.length >= limit) {
|
|
6050
|
+
worstInTopK = results[results.length - 1].score;
|
|
6051
|
+
}
|
|
6052
|
+
}
|
|
6053
|
+
return results;
|
|
6054
|
+
}
|
|
6055
|
+
function scoreMatch(needle, haystackLower, haystack) {
|
|
6056
|
+
let score = 0;
|
|
6057
|
+
let ni = 0;
|
|
6058
|
+
let consecutive = 0;
|
|
6059
|
+
let lastMatchIdx = -1;
|
|
6060
|
+
for (let hi = 0; hi < haystackLower.length && ni < needle.length; hi++) {
|
|
6061
|
+
if (haystackLower[hi] === needle[ni]) {
|
|
6062
|
+
score += SCORE_MATCH;
|
|
6063
|
+
if (hi === 0) score += BONUS_FIRST_CHAR;
|
|
6064
|
+
if (isBoundary(haystack, hi)) score += BONUS_BOUNDARY;
|
|
6065
|
+
if (lastMatchIdx === hi - 1) {
|
|
6066
|
+
consecutive++;
|
|
6067
|
+
score += BONUS_CONSECUTIVE * consecutive;
|
|
6068
|
+
} else {
|
|
6069
|
+
const gap = lastMatchIdx >= 0 ? hi - lastMatchIdx - 1 : 0;
|
|
6070
|
+
if (gap > 0) score -= PENALTY_GAP_START + PENALTY_GAP_EXT * (gap - 1);
|
|
6071
|
+
consecutive = 1;
|
|
6072
|
+
}
|
|
6073
|
+
lastMatchIdx = hi;
|
|
6074
|
+
ni++;
|
|
5474
6075
|
}
|
|
5475
6076
|
}
|
|
6077
|
+
return ni === needle.length ? score : 0;
|
|
6078
|
+
}
|
|
6079
|
+
function isBoundary(s, i) {
|
|
6080
|
+
if (i === 0) return true;
|
|
6081
|
+
const prev = s.charCodeAt(i - 1);
|
|
6082
|
+
const cur = s.charCodeAt(i);
|
|
6083
|
+
if (prev === 47 || prev === 92 || prev === 45 || prev === 95 || prev === 46 || prev === 32) {
|
|
6084
|
+
return true;
|
|
6085
|
+
}
|
|
6086
|
+
if (prev >= 97 && prev <= 122 && cur >= 65 && cur <= 90) return true;
|
|
6087
|
+
return false;
|
|
5476
6088
|
}
|
|
5477
|
-
function
|
|
6089
|
+
function insertSorted(arr, entry, limit) {
|
|
6090
|
+
let lo = 0;
|
|
6091
|
+
let hi = arr.length;
|
|
6092
|
+
while (lo < hi) {
|
|
6093
|
+
const mid = lo + hi >>> 1;
|
|
6094
|
+
if (arr[mid].score >= entry.score) {
|
|
6095
|
+
lo = mid + 1;
|
|
6096
|
+
} else {
|
|
6097
|
+
hi = mid;
|
|
6098
|
+
}
|
|
6099
|
+
}
|
|
6100
|
+
arr.splice(lo, 0, entry);
|
|
6101
|
+
if (arr.length > limit) arr.pop();
|
|
6102
|
+
}
|
|
6103
|
+
const MAX_EMPTY_QUERY_FILES = 30;
|
|
6104
|
+
async function getCandidates(projectPath, typeFilter, query) {
|
|
5478
6105
|
const candidates = [];
|
|
5479
6106
|
const q = query?.toLowerCase() ?? "";
|
|
6107
|
+
const entities = getEntityCache(projectPath);
|
|
5480
6108
|
if (!typeFilter || typeFilter === "note") {
|
|
5481
|
-
for (const n of
|
|
6109
|
+
for (const n of entities.notes) {
|
|
5482
6110
|
candidates.push({
|
|
5483
6111
|
type: "note",
|
|
5484
6112
|
value: n.id.slice(0, 8),
|
|
@@ -5488,7 +6116,7 @@ function getCandidates(projectPath, typeFilter, query) {
|
|
|
5488
6116
|
}
|
|
5489
6117
|
}
|
|
5490
6118
|
if (!typeFilter || typeFilter === "paper") {
|
|
5491
|
-
for (const l of
|
|
6119
|
+
for (const l of entities.papers) {
|
|
5492
6120
|
candidates.push({
|
|
5493
6121
|
type: "paper",
|
|
5494
6122
|
value: l.citeKey,
|
|
@@ -5498,7 +6126,7 @@ function getCandidates(projectPath, typeFilter, query) {
|
|
|
5498
6126
|
}
|
|
5499
6127
|
}
|
|
5500
6128
|
if (!typeFilter || typeFilter === "data") {
|
|
5501
|
-
for (const d of
|
|
6129
|
+
for (const d of entities.data) {
|
|
5502
6130
|
candidates.push({
|
|
5503
6131
|
type: "data",
|
|
5504
6132
|
value: d.id.slice(0, 8),
|
|
@@ -5508,16 +6136,25 @@ function getCandidates(projectPath, typeFilter, query) {
|
|
|
5508
6136
|
}
|
|
5509
6137
|
}
|
|
5510
6138
|
if (!typeFilter || typeFilter === "file") {
|
|
5511
|
-
|
|
5512
|
-
|
|
6139
|
+
const files = await getFileList(projectPath);
|
|
6140
|
+
for (const rel of files) {
|
|
6141
|
+
let detail;
|
|
6142
|
+
try {
|
|
6143
|
+
const stat = statSync(join(projectPath, rel));
|
|
6144
|
+
detail = `${(stat.size / 1024).toFixed(1)}KB`;
|
|
6145
|
+
} catch {
|
|
6146
|
+
}
|
|
6147
|
+
candidates.push({ type: "file", value: rel, label: rel, detail });
|
|
5513
6148
|
}
|
|
5514
6149
|
}
|
|
5515
6150
|
if (q) {
|
|
5516
|
-
return
|
|
5517
|
-
|
|
5518
|
-
|
|
6151
|
+
return fuzzyMatch(
|
|
6152
|
+
candidates,
|
|
6153
|
+
q,
|
|
6154
|
+
(c) => `${c.label} ${c.value} ${c.detail ?? ""}`,
|
|
6155
|
+
50
|
|
6156
|
+
).map((r) => r.item);
|
|
5519
6157
|
}
|
|
5520
|
-
const MAX_EMPTY_QUERY_FILES = 30;
|
|
5521
6158
|
const fileCount = candidates.filter((c) => c.type === "file").length;
|
|
5522
6159
|
if (fileCount > MAX_EMPTY_QUERY_FILES) {
|
|
5523
6160
|
const nonFiles = candidates.filter((c) => c.type !== "file");
|
|
@@ -5535,6 +6172,10 @@ class RealtimeBuffer {
|
|
|
5535
6172
|
isStreaming = false;
|
|
5536
6173
|
progressItems = [];
|
|
5537
6174
|
activityEvents = [];
|
|
6175
|
+
/** Tool events for chat-inline rendering (mirrors tool-events-store) */
|
|
6176
|
+
toolEvents = [];
|
|
6177
|
+
/** Track tool-call start times keyed by toolCallId for duration computation */
|
|
6178
|
+
toolCallStartTimes = /* @__PURE__ */ new Map();
|
|
5538
6179
|
/** Append a streaming text chunk (called from onStream callback) */
|
|
5539
6180
|
appendChunk(chunk) {
|
|
5540
6181
|
this.streamingText += chunk;
|
|
@@ -5549,18 +6190,45 @@ class RealtimeBuffer {
|
|
|
5549
6190
|
this.progressItems.push(item);
|
|
5550
6191
|
}
|
|
5551
6192
|
}
|
|
5552
|
-
/** Record an activity event */
|
|
6193
|
+
/** Record an activity event and track tool-call start times */
|
|
5553
6194
|
pushActivity(event) {
|
|
6195
|
+
if (event.type === "tool-call" && event.toolCallId) {
|
|
6196
|
+
this.toolCallStartTimes.set(event.toolCallId, Date.now());
|
|
6197
|
+
}
|
|
5554
6198
|
this.activityEvents.push(event);
|
|
5555
6199
|
}
|
|
6200
|
+
/** Record a tool event for chat-inline rendering */
|
|
6201
|
+
pushToolEvent(event) {
|
|
6202
|
+
this.toolEvents.push(event);
|
|
6203
|
+
}
|
|
6204
|
+
/** Update a tool event by toolCallId (for tool-result merge) */
|
|
6205
|
+
updateToolEvent(toolCallId, patch) {
|
|
6206
|
+
const idx = this.toolEvents.findLastIndex((e) => e.toolCallId === toolCallId);
|
|
6207
|
+
if (idx !== -1) {
|
|
6208
|
+
this.toolEvents[idx] = { ...this.toolEvents[idx], ...patch };
|
|
6209
|
+
}
|
|
6210
|
+
}
|
|
6211
|
+
/** Clear tool events (on new run or finalize) */
|
|
6212
|
+
clearToolEvents() {
|
|
6213
|
+
this.toolEvents = [];
|
|
6214
|
+
}
|
|
6215
|
+
/** Pop and return the start time for a tool-call, or undefined if not found */
|
|
6216
|
+
popToolCallStartTime(toolCallId) {
|
|
6217
|
+
const t = this.toolCallStartTimes.get(toolCallId);
|
|
6218
|
+
if (t !== void 0) this.toolCallStartTimes.delete(toolCallId);
|
|
6219
|
+
return t;
|
|
6220
|
+
}
|
|
5556
6221
|
/** Clear progress and activity (called on project close or explicit reset) */
|
|
5557
6222
|
clearRun() {
|
|
5558
6223
|
this.progressItems = [];
|
|
5559
6224
|
this.activityEvents = [];
|
|
6225
|
+
this.toolEvents = [];
|
|
5560
6226
|
}
|
|
5561
6227
|
/** Clear only activity events (called on new agent run) */
|
|
5562
6228
|
clearActivity() {
|
|
5563
6229
|
this.activityEvents = [];
|
|
6230
|
+
this.toolEvents = [];
|
|
6231
|
+
this.toolCallStartTimes.clear();
|
|
5564
6232
|
}
|
|
5565
6233
|
/** Mark streaming finished (called on agent:done) */
|
|
5566
6234
|
finishStreaming() {
|
|
@@ -5573,6 +6241,8 @@ class RealtimeBuffer {
|
|
|
5573
6241
|
this.isStreaming = false;
|
|
5574
6242
|
this.progressItems = [];
|
|
5575
6243
|
this.activityEvents = [];
|
|
6244
|
+
this.toolEvents = [];
|
|
6245
|
+
this.toolCallStartTimes.clear();
|
|
5576
6246
|
}
|
|
5577
6247
|
/** Return a snapshot the renderer can use to hydrate stores */
|
|
5578
6248
|
getSnapshot() {
|
|
@@ -5580,17 +6250,366 @@ class RealtimeBuffer {
|
|
|
5580
6250
|
streamingText: this.streamingText,
|
|
5581
6251
|
isStreaming: this.isStreaming,
|
|
5582
6252
|
progressItems: [...this.progressItems],
|
|
5583
|
-
activityEvents: [...this.activityEvents]
|
|
6253
|
+
activityEvents: [...this.activityEvents],
|
|
6254
|
+
toolEvents: [...this.toolEvents]
|
|
5584
6255
|
};
|
|
5585
6256
|
}
|
|
5586
6257
|
}
|
|
5587
6258
|
function createRealtimeBuffer() {
|
|
5588
6259
|
return new RealtimeBuffer();
|
|
5589
6260
|
}
|
|
6261
|
+
function getFileName(path2) {
|
|
6262
|
+
if (!path2) return "";
|
|
6263
|
+
const parts = path2.replace(/\\/g, "/").split("/");
|
|
6264
|
+
return parts[parts.length - 1] || path2;
|
|
6265
|
+
}
|
|
6266
|
+
function truncStr(s, max) {
|
|
6267
|
+
if (!s) return "";
|
|
6268
|
+
return s.length > max ? s.slice(0, max - 3) + "..." : s;
|
|
6269
|
+
}
|
|
6270
|
+
function safeRecord(obj) {
|
|
6271
|
+
return obj && typeof obj === "object" ? obj : {};
|
|
6272
|
+
}
|
|
6273
|
+
function extractResultText(result) {
|
|
6274
|
+
const r = safeRecord(result);
|
|
6275
|
+
const content = r.content;
|
|
6276
|
+
return content?.[0]?.text || "";
|
|
6277
|
+
}
|
|
6278
|
+
function lastNLines(text, n) {
|
|
6279
|
+
const lines = text.split("\n").filter(Boolean);
|
|
6280
|
+
return lines.slice(-n).join("\n");
|
|
6281
|
+
}
|
|
6282
|
+
const configs = [
|
|
6283
|
+
// ── File tools ────────────────────────
|
|
6284
|
+
{
|
|
6285
|
+
name: "read",
|
|
6286
|
+
displayName: "Read",
|
|
6287
|
+
icon: "FileText",
|
|
6288
|
+
category: "file",
|
|
6289
|
+
formatCallSummary: (a) => {
|
|
6290
|
+
const path2 = a.path || "";
|
|
6291
|
+
const offset = a.offset;
|
|
6292
|
+
const limit = a.limit;
|
|
6293
|
+
const suffix = offset ? ` · lines ${offset}-${offset + (limit || 2e3)}` : "";
|
|
6294
|
+
return `${getFileName(path2)}${suffix}`;
|
|
6295
|
+
},
|
|
6296
|
+
formatCallDetail: (a) => ({ path: a.path, offset: a.offset, limit: a.limit }),
|
|
6297
|
+
formatResultSummary: (result) => {
|
|
6298
|
+
const text = extractResultText(result);
|
|
6299
|
+
const lineCount = text ? text.split("\n").length : 0;
|
|
6300
|
+
return lineCount ? `${lineCount} lines` : "Read completed";
|
|
6301
|
+
},
|
|
6302
|
+
formatResultDetail: (result) => {
|
|
6303
|
+
const text = extractResultText(result);
|
|
6304
|
+
return { lineCount: text ? text.split("\n").length : 0 };
|
|
6305
|
+
}
|
|
6306
|
+
},
|
|
6307
|
+
{
|
|
6308
|
+
name: "write",
|
|
6309
|
+
displayName: "Write",
|
|
6310
|
+
icon: "FileText",
|
|
6311
|
+
category: "file",
|
|
6312
|
+
formatCallSummary: (a) => getFileName(a.path || ""),
|
|
6313
|
+
formatCallDetail: (a) => ({ path: a.path }),
|
|
6314
|
+
formatResultSummary: (_, a) => `Written: ${getFileName(a?.path || "")}`,
|
|
6315
|
+
formatResultDetail: (_, a) => ({ path: a?.path })
|
|
6316
|
+
},
|
|
6317
|
+
{
|
|
6318
|
+
name: "edit",
|
|
6319
|
+
displayName: "Edit",
|
|
6320
|
+
icon: "FileText",
|
|
6321
|
+
category: "file",
|
|
6322
|
+
formatCallSummary: (a) => getFileName(a.path || ""),
|
|
6323
|
+
formatCallDetail: (a) => ({ path: a.path }),
|
|
6324
|
+
formatResultSummary: (_, a) => `Edited: ${getFileName(a?.path || "")}`,
|
|
6325
|
+
formatResultDetail: (_, a) => ({ path: a?.path })
|
|
6326
|
+
},
|
|
6327
|
+
// ── Code tools ────────────────────────
|
|
6328
|
+
{
|
|
6329
|
+
name: "bash",
|
|
6330
|
+
displayName: "Bash",
|
|
6331
|
+
icon: "Terminal",
|
|
6332
|
+
category: "code",
|
|
6333
|
+
formatCallSummary: (a) => {
|
|
6334
|
+
const cmd = a.command || "";
|
|
6335
|
+
return cmd.length > 60 ? `$ ${cmd.slice(0, 57)}...` : `$ ${cmd}`;
|
|
6336
|
+
},
|
|
6337
|
+
formatCallDetail: (a) => ({ command: truncStr(a.command, 200) }),
|
|
6338
|
+
formatResultSummary: () => "Command completed",
|
|
6339
|
+
formatResultDetail: (result) => {
|
|
6340
|
+
const text = extractResultText(result);
|
|
6341
|
+
const lines = text.split("\n").filter(Boolean);
|
|
6342
|
+
return { outputLines: lines.length, outputPreview: truncStr(lastNLines(text, 3), 200) };
|
|
6343
|
+
},
|
|
6344
|
+
formatProgress: (partial) => {
|
|
6345
|
+
const text = partial?.content?.[0]?.text;
|
|
6346
|
+
if (typeof text === "string" && text.length > 0) {
|
|
6347
|
+
return lastNLines(text, 5);
|
|
6348
|
+
}
|
|
6349
|
+
return void 0;
|
|
6350
|
+
}
|
|
6351
|
+
},
|
|
6352
|
+
// ── Search tools ────────────────────────
|
|
6353
|
+
{
|
|
6354
|
+
name: "grep",
|
|
6355
|
+
displayName: "Search",
|
|
6356
|
+
icon: "Search",
|
|
6357
|
+
category: "search",
|
|
6358
|
+
formatCallSummary: (a) => `"${truncStr(a.pattern, 30)}"${a.path ? ` in ${a.path}` : ""}`,
|
|
6359
|
+
formatCallDetail: (a) => ({ pattern: a.pattern, path: a.path, glob: a.glob }),
|
|
6360
|
+
formatResultSummary: (result) => {
|
|
6361
|
+
const text = extractResultText(result);
|
|
6362
|
+
const count = text.split("\n").filter(Boolean).length;
|
|
6363
|
+
return `${count} results`;
|
|
6364
|
+
},
|
|
6365
|
+
formatResultDetail: (result) => {
|
|
6366
|
+
const text = extractResultText(result);
|
|
6367
|
+
return { matchCount: text.split("\n").filter(Boolean).length };
|
|
6368
|
+
}
|
|
6369
|
+
},
|
|
6370
|
+
{
|
|
6371
|
+
name: "glob",
|
|
6372
|
+
displayName: "Find Files",
|
|
6373
|
+
icon: "Search",
|
|
6374
|
+
category: "search",
|
|
6375
|
+
formatCallSummary: (a) => a.pattern || "",
|
|
6376
|
+
formatCallDetail: (a) => ({ pattern: a.pattern, path: a.path }),
|
|
6377
|
+
formatResultSummary: (result) => {
|
|
6378
|
+
const text = extractResultText(result);
|
|
6379
|
+
const count = text.split("\n").filter(Boolean).length;
|
|
6380
|
+
return `${count} files`;
|
|
6381
|
+
},
|
|
6382
|
+
formatResultDetail: (result) => {
|
|
6383
|
+
const text = extractResultText(result);
|
|
6384
|
+
return { fileCount: text.split("\n").filter(Boolean).length };
|
|
6385
|
+
}
|
|
6386
|
+
},
|
|
6387
|
+
{
|
|
6388
|
+
name: "find",
|
|
6389
|
+
displayName: "Find",
|
|
6390
|
+
icon: "Search",
|
|
6391
|
+
category: "search",
|
|
6392
|
+
formatCallSummary: (a) => truncStr(a.pattern || a.path, 40),
|
|
6393
|
+
formatCallDetail: (a) => ({ pattern: a.pattern, path: a.path }),
|
|
6394
|
+
formatResultSummary: () => "Find completed",
|
|
6395
|
+
formatResultDetail: () => ({})
|
|
6396
|
+
},
|
|
6397
|
+
{
|
|
6398
|
+
name: "ls",
|
|
6399
|
+
displayName: "List",
|
|
6400
|
+
icon: "FileText",
|
|
6401
|
+
category: "file",
|
|
6402
|
+
formatCallSummary: (a) => a.path || ".",
|
|
6403
|
+
formatCallDetail: (a) => ({ path: a.path }),
|
|
6404
|
+
formatResultSummary: () => "Listed",
|
|
6405
|
+
formatResultDetail: () => ({})
|
|
6406
|
+
},
|
|
6407
|
+
// ── Web tools ────────────────────────
|
|
6408
|
+
{
|
|
6409
|
+
name: "fetch",
|
|
6410
|
+
displayName: "Fetch",
|
|
6411
|
+
icon: "Globe",
|
|
6412
|
+
category: "web",
|
|
6413
|
+
formatCallSummary: (a) => truncStr(a.url, 50),
|
|
6414
|
+
formatCallDetail: (a) => ({ url: a.url }),
|
|
6415
|
+
formatResultSummary: (result) => {
|
|
6416
|
+
const text = extractResultText(result);
|
|
6417
|
+
const kb = (text.length / 1024).toFixed(1);
|
|
6418
|
+
return `${kb}KB received`;
|
|
6419
|
+
},
|
|
6420
|
+
formatResultDetail: (result) => {
|
|
6421
|
+
const text = extractResultText(result);
|
|
6422
|
+
return { sizeKB: parseFloat((text.length / 1024).toFixed(1)) };
|
|
6423
|
+
}
|
|
6424
|
+
},
|
|
6425
|
+
{
|
|
6426
|
+
name: "web_fetch",
|
|
6427
|
+
displayName: "Web Fetch",
|
|
6428
|
+
icon: "Globe",
|
|
6429
|
+
category: "web",
|
|
6430
|
+
formatCallSummary: (a) => truncStr(a.url, 50),
|
|
6431
|
+
formatCallDetail: (a) => ({ url: a.url }),
|
|
6432
|
+
formatResultSummary: (result) => {
|
|
6433
|
+
const r = safeRecord(result);
|
|
6434
|
+
const data = safeRecord(r.data);
|
|
6435
|
+
const charCount = data.charCount;
|
|
6436
|
+
if (charCount) return `${(charCount / 1024).toFixed(1)}KB received`;
|
|
6437
|
+
return "Fetch completed";
|
|
6438
|
+
},
|
|
6439
|
+
formatResultDetail: (result) => {
|
|
6440
|
+
const r = safeRecord(result);
|
|
6441
|
+
const data = safeRecord(r.data);
|
|
6442
|
+
return { charCount: data.charCount, url: data.url };
|
|
6443
|
+
}
|
|
6444
|
+
},
|
|
6445
|
+
{
|
|
6446
|
+
name: "web_search",
|
|
6447
|
+
displayName: "Web Search",
|
|
6448
|
+
icon: "Globe",
|
|
6449
|
+
category: "web",
|
|
6450
|
+
formatCallSummary: (a) => truncStr(a.query, 50),
|
|
6451
|
+
formatCallDetail: (a) => ({ query: a.query }),
|
|
6452
|
+
formatResultSummary: () => "Search completed",
|
|
6453
|
+
formatResultDetail: () => ({})
|
|
6454
|
+
},
|
|
6455
|
+
// ── Research tools ────────────────────────
|
|
6456
|
+
{
|
|
6457
|
+
name: "literature-search",
|
|
6458
|
+
displayName: "Literature Search",
|
|
6459
|
+
icon: "BookOpen",
|
|
6460
|
+
category: "research",
|
|
6461
|
+
formatCallSummary: (a) => truncStr(a.query, 40),
|
|
6462
|
+
formatCallDetail: (a) => ({ query: a.query, maxResults: a.max_results }),
|
|
6463
|
+
formatResultSummary: (result) => {
|
|
6464
|
+
const r = safeRecord(result);
|
|
6465
|
+
const data = safeRecord(r.data);
|
|
6466
|
+
const totalFound = data.totalPapersFound ?? 0;
|
|
6467
|
+
const saved = data.papersAutoSaved ?? 0;
|
|
6468
|
+
const coverage = data.coverage;
|
|
6469
|
+
if (totalFound > 0) {
|
|
6470
|
+
let s = `Found ${totalFound} papers`;
|
|
6471
|
+
if (coverage?.score != null) s += ` (${Math.round(coverage.score * 100)}%)`;
|
|
6472
|
+
if (saved > 0) s += `, saved ${saved}`;
|
|
6473
|
+
return s;
|
|
6474
|
+
}
|
|
6475
|
+
const local = data.localPapersUsed ?? 0;
|
|
6476
|
+
const external = data.externalPapersUsed ?? 0;
|
|
6477
|
+
return `Found ${local + external} papers`;
|
|
6478
|
+
},
|
|
6479
|
+
formatResultDetail: (result) => {
|
|
6480
|
+
const r = safeRecord(result);
|
|
6481
|
+
const data = safeRecord(r.data);
|
|
6482
|
+
return {
|
|
6483
|
+
papersFound: data.totalPapersFound ?? 0,
|
|
6484
|
+
papersSaved: data.papersAutoSaved ?? 0,
|
|
6485
|
+
coverage: data.coverage?.score
|
|
6486
|
+
};
|
|
6487
|
+
}
|
|
6488
|
+
},
|
|
6489
|
+
{
|
|
6490
|
+
name: "lit-subtopic",
|
|
6491
|
+
displayName: "Sub-topic Search",
|
|
6492
|
+
icon: "BookOpen",
|
|
6493
|
+
category: "research",
|
|
6494
|
+
formatCallSummary: (a) => a._summary || "Searching sub-topic",
|
|
6495
|
+
formatCallDetail: (a) => ({ summary: a._summary }),
|
|
6496
|
+
formatResultSummary: (result) => safeRecord(result).data || "Search completed",
|
|
6497
|
+
formatResultDetail: () => ({})
|
|
6498
|
+
},
|
|
6499
|
+
{
|
|
6500
|
+
name: "lit-enrich",
|
|
6501
|
+
displayName: "Enrich Papers",
|
|
6502
|
+
icon: "BookOpen",
|
|
6503
|
+
category: "research",
|
|
6504
|
+
formatCallSummary: (a) => a._summary || "Enriching paper metadata",
|
|
6505
|
+
formatCallDetail: (a) => ({ summary: a._summary }),
|
|
6506
|
+
formatResultSummary: (result) => safeRecord(result).data || "Enriched metadata",
|
|
6507
|
+
formatResultDetail: () => ({})
|
|
6508
|
+
},
|
|
6509
|
+
{
|
|
6510
|
+
name: "lit-autosave",
|
|
6511
|
+
displayName: "Save Papers",
|
|
6512
|
+
icon: "BookOpen",
|
|
6513
|
+
category: "research",
|
|
6514
|
+
formatCallSummary: (a) => a._summary || "Saving papers",
|
|
6515
|
+
formatCallDetail: (a) => ({ summary: a._summary }),
|
|
6516
|
+
formatResultSummary: (result) => safeRecord(result).data || "Saved papers",
|
|
6517
|
+
formatResultDetail: () => ({})
|
|
6518
|
+
},
|
|
6519
|
+
{
|
|
6520
|
+
name: "data_analyze",
|
|
6521
|
+
displayName: "Data Analysis",
|
|
6522
|
+
icon: "Database",
|
|
6523
|
+
category: "research",
|
|
6524
|
+
formatCallSummary: (a) => getFileName(a.file_path || "") || "data",
|
|
6525
|
+
formatCallDetail: (a) => ({ file_path: a.file_path }),
|
|
6526
|
+
formatResultSummary: () => "Analysis completed",
|
|
6527
|
+
formatResultDetail: () => ({})
|
|
6528
|
+
},
|
|
6529
|
+
// ── Artifact tools ────────────────────────
|
|
6530
|
+
{
|
|
6531
|
+
name: "artifact-create",
|
|
6532
|
+
displayName: "Create Artifact",
|
|
6533
|
+
icon: "Sparkles",
|
|
6534
|
+
category: "memory",
|
|
6535
|
+
formatCallSummary: (a) => {
|
|
6536
|
+
const type = (a.type || "artifact").toLowerCase();
|
|
6537
|
+
const title = truncStr(a.title, 35);
|
|
6538
|
+
return `${type}: ${title}`;
|
|
6539
|
+
},
|
|
6540
|
+
formatCallDetail: (a) => ({ type: a.type, title: a.title }),
|
|
6541
|
+
formatResultSummary: (result) => {
|
|
6542
|
+
const data = safeRecord(safeRecord(result).data);
|
|
6543
|
+
const type = data.type || "artifact";
|
|
6544
|
+
const title = truncStr(data.title, 30);
|
|
6545
|
+
return title ? `Created ${type}: ${title}` : `Created ${type}`;
|
|
6546
|
+
},
|
|
6547
|
+
formatResultDetail: (result) => {
|
|
6548
|
+
const data = safeRecord(safeRecord(result).data);
|
|
6549
|
+
return { type: data.type, title: data.title };
|
|
6550
|
+
}
|
|
6551
|
+
},
|
|
6552
|
+
{
|
|
6553
|
+
name: "artifact-update",
|
|
6554
|
+
displayName: "Update Artifact",
|
|
6555
|
+
icon: "Sparkles",
|
|
6556
|
+
category: "memory",
|
|
6557
|
+
formatCallSummary: (a) => truncStr(a.id, 30),
|
|
6558
|
+
formatCallDetail: (a) => ({ id: a.id }),
|
|
6559
|
+
formatResultSummary: () => "Updated",
|
|
6560
|
+
formatResultDetail: () => ({})
|
|
6561
|
+
},
|
|
6562
|
+
{
|
|
6563
|
+
name: "artifact-search",
|
|
6564
|
+
displayName: "Search Artifacts",
|
|
6565
|
+
icon: "Search",
|
|
6566
|
+
category: "memory",
|
|
6567
|
+
formatCallSummary: (a) => truncStr(a.query, 40),
|
|
6568
|
+
formatCallDetail: (a) => ({ query: a.query, types: a.types }),
|
|
6569
|
+
formatResultSummary: () => "Search completed",
|
|
6570
|
+
formatResultDetail: () => ({})
|
|
6571
|
+
},
|
|
6572
|
+
// ── System tools ────────────────────────
|
|
6573
|
+
{
|
|
6574
|
+
name: "convert_document",
|
|
6575
|
+
displayName: "Convert Document",
|
|
6576
|
+
icon: "FileText",
|
|
6577
|
+
category: "system",
|
|
6578
|
+
formatCallSummary: (a) => getFileName(a.source || ""),
|
|
6579
|
+
formatCallDetail: (a) => ({ source: a.source }),
|
|
6580
|
+
formatResultSummary: (result, a) => {
|
|
6581
|
+
const data = safeRecord(safeRecord(result).data);
|
|
6582
|
+
const skill = data.converterSkill;
|
|
6583
|
+
const sourceName = getFileName(a?.source || "");
|
|
6584
|
+
return skill ? `Converted ${sourceName} via ${skill}` : `Converted ${sourceName}`;
|
|
6585
|
+
},
|
|
6586
|
+
formatResultDetail: (result) => {
|
|
6587
|
+
const data = safeRecord(safeRecord(result).data);
|
|
6588
|
+
return { converterSkill: data.converterSkill, outputFile: data.outputFile };
|
|
6589
|
+
}
|
|
6590
|
+
},
|
|
6591
|
+
{
|
|
6592
|
+
name: "load_skill",
|
|
6593
|
+
displayName: "Load Skill",
|
|
6594
|
+
icon: "Sparkles",
|
|
6595
|
+
category: "system",
|
|
6596
|
+
formatCallSummary: (a) => a.name || "skill",
|
|
6597
|
+
formatCallDetail: (a) => ({ name: a.name }),
|
|
6598
|
+
formatResultSummary: (_, a) => `Loaded: ${a?.name || "skill"}`,
|
|
6599
|
+
formatResultDetail: () => ({})
|
|
6600
|
+
}
|
|
6601
|
+
];
|
|
6602
|
+
const registry = /* @__PURE__ */ new Map();
|
|
6603
|
+
for (const config of configs) {
|
|
6604
|
+
registry.set(config.name, config);
|
|
6605
|
+
}
|
|
6606
|
+
function getToolRenderConfig(toolName) {
|
|
6607
|
+
return registry.get(toolName);
|
|
6608
|
+
}
|
|
5590
6609
|
const EMPTY = {
|
|
5591
6610
|
version: 1,
|
|
5592
6611
|
updatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
5593
|
-
totals: { tokens: 0, promptTokens: 0, cachedTokens: 0, cost: 0, calls: 0 }
|
|
6612
|
+
totals: { tokens: 0, promptTokens: 0, completionTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, cost: 0, calls: 0 }
|
|
5594
6613
|
};
|
|
5595
6614
|
function usagePath(baseDir) {
|
|
5596
6615
|
return join(baseDir, "usage.json");
|
|
@@ -5600,6 +6619,9 @@ function loadUsageTotals(baseDir) {
|
|
|
5600
6619
|
const raw = readFileSync(usagePath(baseDir), "utf-8");
|
|
5601
6620
|
const parsed = JSON.parse(raw);
|
|
5602
6621
|
if (!parsed?.totals) return { ...EMPTY };
|
|
6622
|
+
const t = parsed.totals;
|
|
6623
|
+
t.completionTokens ??= 0;
|
|
6624
|
+
t.cacheWriteTokens ??= 0;
|
|
5603
6625
|
return parsed;
|
|
5604
6626
|
} catch {
|
|
5605
6627
|
return { ...EMPTY };
|
|
@@ -5628,15 +6650,17 @@ function writeAtomically(filePath, data) {
|
|
|
5628
6650
|
}
|
|
5629
6651
|
}
|
|
5630
6652
|
}
|
|
5631
|
-
function accumulateUsage(baseDir, promptTokens, completionTokens, cachedTokens, cost) {
|
|
6653
|
+
function accumulateUsage(baseDir, promptTokens, completionTokens, cachedTokens, cacheWriteTokens, cost) {
|
|
5632
6654
|
const existing = loadUsageTotals(baseDir);
|
|
5633
6655
|
const next = {
|
|
5634
6656
|
version: 1,
|
|
5635
6657
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5636
6658
|
totals: {
|
|
5637
|
-
tokens: existing.totals.tokens + promptTokens + completionTokens,
|
|
6659
|
+
tokens: existing.totals.tokens + promptTokens + completionTokens + cachedTokens,
|
|
5638
6660
|
promptTokens: existing.totals.promptTokens + promptTokens,
|
|
6661
|
+
completionTokens: existing.totals.completionTokens + completionTokens,
|
|
5639
6662
|
cachedTokens: existing.totals.cachedTokens + cachedTokens,
|
|
6663
|
+
cacheWriteTokens: existing.totals.cacheWriteTokens + cacheWriteTokens,
|
|
5640
6664
|
cost: existing.totals.cost + cost,
|
|
5641
6665
|
calls: existing.totals.calls + 1
|
|
5642
6666
|
}
|
|
@@ -5648,98 +6672,45 @@ function resetUsageTotals(baseDir) {
|
|
|
5648
6672
|
const cleared = {
|
|
5649
6673
|
version: 1,
|
|
5650
6674
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5651
|
-
totals: { tokens: 0, promptTokens: 0, cachedTokens: 0, cost: 0, calls: 0 }
|
|
6675
|
+
totals: { tokens: 0, promptTokens: 0, completionTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, cost: 0, calls: 0 }
|
|
5652
6676
|
};
|
|
5653
6677
|
writeAtomically(usagePath(baseDir), JSON.stringify(cleared, null, 2));
|
|
5654
6678
|
return cleared;
|
|
5655
6679
|
}
|
|
5656
6680
|
function formatToolCall(tool, args) {
|
|
5657
6681
|
const a = args && typeof args === "object" ? args : {};
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
case "lit-autosave":
|
|
5666
|
-
return { label: a._summary || "Saving papers", icon: "file" };
|
|
5667
|
-
case "data-analyze":
|
|
5668
|
-
return { label: `Analyze: ${getFileName(a.filePath || "") || "data"}`, icon: "file" };
|
|
5669
|
-
case "convert_to_markdown": {
|
|
5670
|
-
const sourcePath = a.path || a.uri || "";
|
|
5671
|
-
return { label: `Convert: ${getFileName(sourcePath)}`, icon: "file" };
|
|
5672
|
-
}
|
|
5673
|
-
case "artifact-create": {
|
|
5674
|
-
const type = (a.type || "artifact").toLowerCase();
|
|
5675
|
-
const title = (a.title || type).slice(0, 35);
|
|
5676
|
-
return { label: `Create ${type}: ${title}`, icon: "file" };
|
|
5677
|
-
}
|
|
5678
|
-
case "read":
|
|
5679
|
-
return { label: `Read: ${getFileName(a.path || "")}`, icon: "file" };
|
|
5680
|
-
case "write":
|
|
5681
|
-
return { label: `Write: ${getFileName(a.path || "")}`, icon: "file" };
|
|
5682
|
-
case "edit":
|
|
5683
|
-
return { label: `Edit: ${getFileName(a.path || "")}`, icon: "file" };
|
|
5684
|
-
case "bash":
|
|
5685
|
-
return { label: `Run command`, icon: "terminal" };
|
|
5686
|
-
case "glob":
|
|
5687
|
-
return { label: `Search files: ${a.pattern || ""}`, icon: "search" };
|
|
5688
|
-
case "grep":
|
|
5689
|
-
return { label: `Search content: ${(a.pattern || "").slice(0, 30)}`, icon: "search" };
|
|
5690
|
-
case "fetch":
|
|
5691
|
-
return { label: `Fetch: ${(a.url || "").slice(0, 40)}`, icon: "network" };
|
|
5692
|
-
default:
|
|
5693
|
-
return { label: `${tool}`, icon: "tool" };
|
|
6682
|
+
const config = getToolRenderConfig(tool);
|
|
6683
|
+
if (config) {
|
|
6684
|
+
return {
|
|
6685
|
+
label: `${config.displayName}: ${config.formatCallSummary(a)}`,
|
|
6686
|
+
icon: config.icon,
|
|
6687
|
+
detail: config.formatCallDetail(a)
|
|
6688
|
+
};
|
|
5694
6689
|
}
|
|
6690
|
+
return { label: `${tool}`, icon: "tool" };
|
|
5695
6691
|
}
|
|
5696
6692
|
function formatToolResult(tool, result, args) {
|
|
5697
|
-
const r = result && typeof result === "object" ? result : {};
|
|
5698
6693
|
const a = args && typeof args === "object" ? args : {};
|
|
5699
|
-
const
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
return { label: summary, icon: "search" };
|
|
5718
|
-
}
|
|
5719
|
-
case "lit-subtopic":
|
|
5720
|
-
return { label: r.data || "Search completed", icon: "search" };
|
|
5721
|
-
case "lit-enrich":
|
|
5722
|
-
return { label: r.data || "Enriched metadata", icon: "search" };
|
|
5723
|
-
case "lit-autosave":
|
|
5724
|
-
return { label: r.data || "Saved papers", icon: "file" };
|
|
5725
|
-
case "convert_to_markdown": {
|
|
5726
|
-
const sourcePath = a.path || a.uri || "";
|
|
5727
|
-
const skill = typeof data.converterSkill === "string" ? data.converterSkill : "";
|
|
5728
|
-
const script = typeof data.converterScript === "string" ? data.converterScript : "";
|
|
5729
|
-
if (skill && script) return { label: `Converted ${getFileName(sourcePath)} via ${skill}/${script}`, icon: "file" };
|
|
5730
|
-
if (skill) return { label: `Converted ${getFileName(sourcePath)} via ${skill}`, icon: "file" };
|
|
5731
|
-
return { label: `Converted ${getFileName(sourcePath)}`, icon: "file" };
|
|
5732
|
-
}
|
|
5733
|
-
case "artifact-create": {
|
|
5734
|
-
const type = data.type || "artifact";
|
|
5735
|
-
const title = data.title || "";
|
|
5736
|
-
return { label: title ? `Created ${type}: ${title.slice(0, 30)}` : `Created ${type}`, icon: "file" };
|
|
5737
|
-
}
|
|
5738
|
-
default: {
|
|
5739
|
-
const success2 = r.success !== false;
|
|
5740
|
-
return { label: success2 ? `${tool} completed` : `${tool} failed`, icon: "tool" };
|
|
5741
|
-
}
|
|
6694
|
+
const r = result && typeof result === "object" ? result : {};
|
|
6695
|
+
const success2 = r.success !== false;
|
|
6696
|
+
const config = getToolRenderConfig(tool);
|
|
6697
|
+
if (config && success2) {
|
|
6698
|
+
return {
|
|
6699
|
+
label: config.formatResultSummary(result, a),
|
|
6700
|
+
icon: config.icon,
|
|
6701
|
+
detail: config.formatResultDetail(result, a)
|
|
6702
|
+
};
|
|
6703
|
+
}
|
|
6704
|
+
if (config && !success2) {
|
|
6705
|
+
const errorMsg = r.error || "";
|
|
6706
|
+
const brief = errorMsg.length > 60 ? errorMsg.slice(0, 57) + "..." : errorMsg;
|
|
6707
|
+
return {
|
|
6708
|
+
label: brief ? `${config.displayName} failed: ${brief}` : `${config.displayName} failed`,
|
|
6709
|
+
icon: config.icon,
|
|
6710
|
+
detail: config.formatResultDetail(result, a)
|
|
6711
|
+
};
|
|
5742
6712
|
}
|
|
6713
|
+
return { label: success2 ? `${tool} completed` : `${tool} failed`, icon: "tool", detail: { success: success2 } };
|
|
5743
6714
|
}
|
|
5744
6715
|
const windowStates = /* @__PURE__ */ new Map();
|
|
5745
6716
|
let ipcHandlersRegistered = false;
|
|
@@ -5835,7 +6806,8 @@ function initializeProject(path2) {
|
|
|
5835
6806
|
PATHS.memoryRoot,
|
|
5836
6807
|
PATHS.explainDir,
|
|
5837
6808
|
PATHS.sessionSummaries,
|
|
5838
|
-
PATHS.skills
|
|
6809
|
+
PATHS.skills,
|
|
6810
|
+
PATHS.memory
|
|
5839
6811
|
];
|
|
5840
6812
|
for (const dir of dirs) {
|
|
5841
6813
|
const fullPath = join(path2, dir);
|
|
@@ -5856,6 +6828,7 @@ function initializeProject(path2) {
|
|
|
5856
6828
|
writeFileSync(projectFile, JSON.stringify(defaultConfig, null, 2));
|
|
5857
6829
|
}
|
|
5858
6830
|
ensureAgentMd(path2);
|
|
6831
|
+
migrateAgentMemoryToFile(path2);
|
|
5859
6832
|
const migration = migrateLegacyArtifacts(path2);
|
|
5860
6833
|
if (migration.updatedFiles > 0 && process.env.RESEARCH_COPILOT_DEBUG) {
|
|
5861
6834
|
console.log(`[ResearchPilot] migrated legacy artifacts: files=${migration.updatedFiles}, literature->paper=${migration.convertedLiteratureType}, data.name removed=${migration.removedDataNameField}`);
|
|
@@ -5889,13 +6862,15 @@ async function ensureCoordinator(state, win, model, options) {
|
|
|
5889
6862
|
state.realtimeBuffer.appendChunk(chunk);
|
|
5890
6863
|
safeSend(win, "agent:stream-chunk", chunk);
|
|
5891
6864
|
},
|
|
5892
|
-
onToolCall: (tool, args) => {
|
|
5893
|
-
const
|
|
5894
|
-
const
|
|
6865
|
+
onToolCall: (tool, args, toolCallId) => {
|
|
6866
|
+
const id = toolCallId || randomUUID();
|
|
6867
|
+
const { label, detail } = formatToolCall(tool, args);
|
|
6868
|
+
const event = { type: "tool-call", tool, toolCallId: id, summary: label, detail };
|
|
5895
6869
|
state.realtimeBuffer.pushActivity(event);
|
|
6870
|
+
state.realtimeBuffer.pushToolEvent({ type: "tool-call", tool, toolCallId: id, summary: label, detail });
|
|
5896
6871
|
safeSend(win, "agent:activity", event);
|
|
5897
6872
|
},
|
|
5898
|
-
onToolResult: (tool, result, args) => {
|
|
6873
|
+
onToolResult: (tool, result, args, toolCallId) => {
|
|
5899
6874
|
if (tool.startsWith("todo-") && result && typeof result === "object" && "success" in result) {
|
|
5900
6875
|
const r2 = result;
|
|
5901
6876
|
if (r2.success && r2.item) {
|
|
@@ -5910,16 +6885,16 @@ async function ensureCoordinator(state, win, model, options) {
|
|
|
5910
6885
|
safeSend(win, "agent:file-created", absPath);
|
|
5911
6886
|
}
|
|
5912
6887
|
}
|
|
5913
|
-
if (tool === "
|
|
6888
|
+
if (tool === "convert_document" && result && typeof result === "object" && "success" in result) {
|
|
5914
6889
|
const r2 = result;
|
|
5915
6890
|
if (r2.success && r2.data?.outputFile) {
|
|
5916
6891
|
safeSend(win, "agent:file-created", r2.data.outputFile);
|
|
5917
6892
|
}
|
|
5918
6893
|
}
|
|
5919
|
-
if (tool === "
|
|
6894
|
+
if (tool === "convert_document" && result && typeof result === "object" && "success" in result) {
|
|
5920
6895
|
const r2 = result;
|
|
5921
|
-
if (r2.success && r2.data?.outputFile && args && typeof args === "object" && "
|
|
5922
|
-
const sourcePath = args.
|
|
6896
|
+
if (r2.success && r2.data?.outputFile && args && typeof args === "object" && "source" in args) {
|
|
6897
|
+
const sourcePath = args.source;
|
|
5923
6898
|
const absSourcePath = isAbsolute(sourcePath) ? sourcePath : resolve(runProjectPath, sourcePath);
|
|
5924
6899
|
const absOutputPath = resolve(runProjectPath, r2.data.outputFile);
|
|
5925
6900
|
if (existsSync(absOutputPath)) {
|
|
@@ -5933,28 +6908,46 @@ async function ensureCoordinator(state, win, model, options) {
|
|
|
5933
6908
|
}
|
|
5934
6909
|
}
|
|
5935
6910
|
}
|
|
5936
|
-
if (tool === "artifact-create" && result && typeof result === "object" && "success" in result) {
|
|
6911
|
+
if ((tool === "artifact-create" || tool === "artifact-update") && result && typeof result === "object" && "success" in result) {
|
|
5937
6912
|
const r2 = result;
|
|
5938
6913
|
if (r2.success) {
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
6914
|
+
invalidateEntityCache();
|
|
6915
|
+
if (tool === "artifact-create") {
|
|
6916
|
+
safeSend(win, "agent:entity-created", {
|
|
6917
|
+
type: r2.data?.type || "artifact",
|
|
6918
|
+
id: r2.data?.id,
|
|
6919
|
+
title: r2.data?.title
|
|
6920
|
+
});
|
|
6921
|
+
if (r2.data?.filePath) {
|
|
6922
|
+
const absPath = isAbsolute(r2.data.filePath) ? r2.data.filePath : resolve(runProjectPath, r2.data.filePath);
|
|
6923
|
+
safeSend(win, "agent:file-created", absPath);
|
|
6924
|
+
}
|
|
5947
6925
|
}
|
|
5948
6926
|
}
|
|
5949
6927
|
}
|
|
5950
6928
|
const r = result;
|
|
5951
6929
|
const success2 = r?.success !== false;
|
|
5952
6930
|
const error = !success2 ? r?.error || "Unknown error" : void 0;
|
|
5953
|
-
const
|
|
5954
|
-
const
|
|
6931
|
+
const { label: resultLabel, detail: resultDetail } = formatToolResult(tool, result, args);
|
|
6932
|
+
const startTime = toolCallId ? state.realtimeBuffer.popToolCallStartTime(toolCallId) : void 0;
|
|
6933
|
+
const durationMs = startTime ? Date.now() - startTime : void 0;
|
|
6934
|
+
const actEvent = { type: "tool-result", tool, toolCallId, summary: resultLabel, success: success2, error, resultDetail, durationMs };
|
|
5955
6935
|
state.realtimeBuffer.pushActivity(actEvent);
|
|
6936
|
+
if (toolCallId) {
|
|
6937
|
+
state.realtimeBuffer.updateToolEvent(toolCallId, {
|
|
6938
|
+
type: "tool-result",
|
|
6939
|
+
summary: resultLabel,
|
|
6940
|
+
success: success2,
|
|
6941
|
+
resultDetail,
|
|
6942
|
+
durationMs
|
|
6943
|
+
});
|
|
6944
|
+
}
|
|
5956
6945
|
safeSend(win, "agent:activity", actEvent);
|
|
5957
6946
|
},
|
|
6947
|
+
// Tool execution progress (real-time updates during tool execution)
|
|
6948
|
+
onToolProgress: (tool, toolCallId, phase, data) => {
|
|
6949
|
+
safeSend(win, "agent:tool-progress", { tool, toolCallId, phase, data, timestamp: Date.now() });
|
|
6950
|
+
},
|
|
5958
6951
|
// Skill activation tracking
|
|
5959
6952
|
onSkillLoaded: (skillName) => {
|
|
5960
6953
|
safeSend(win, "agent:skill-loaded", skillName);
|
|
@@ -5966,12 +6959,14 @@ async function ensureCoordinator(state, win, model, options) {
|
|
|
5966
6959
|
const promptTokens = usage.input ?? 0;
|
|
5967
6960
|
const completionTokens = usage.output ?? 0;
|
|
5968
6961
|
const cachedTokens = usage.cacheRead ?? 0;
|
|
6962
|
+
const cacheWriteTokens = usage.cacheWrite ?? 0;
|
|
5969
6963
|
const baseDir = join(runProjectPath, PATHS.root);
|
|
5970
|
-
accumulateUsage(baseDir, promptTokens, completionTokens, cachedTokens, rawCost);
|
|
6964
|
+
accumulateUsage(baseDir, promptTokens, completionTokens, cachedTokens, cacheWriteTokens, rawCost);
|
|
5971
6965
|
const usageEvent = {
|
|
5972
6966
|
promptTokens,
|
|
5973
6967
|
completionTokens,
|
|
5974
6968
|
cachedTokens,
|
|
6969
|
+
cacheWriteTokens,
|
|
5975
6970
|
cost: rawCost,
|
|
5976
6971
|
rawCost,
|
|
5977
6972
|
billableCost: rawCost,
|
|
@@ -6148,10 +7143,10 @@ function registerIpcHandlers() {
|
|
|
6148
7143
|
}
|
|
6149
7144
|
});
|
|
6150
7145
|
});
|
|
6151
|
-
handleWindow("mention:candidates", ({ state }, query, type) => {
|
|
7146
|
+
handleWindow("mention:candidates", async ({ state }, query, type) => {
|
|
6152
7147
|
if (!state.projectPath) return [];
|
|
6153
7148
|
try {
|
|
6154
|
-
return getCandidates(state.projectPath, type, query);
|
|
7149
|
+
return await getCandidates(state.projectPath, type, query);
|
|
6155
7150
|
} catch {
|
|
6156
7151
|
return [];
|
|
6157
7152
|
}
|
|
@@ -6597,7 +7592,11 @@ function destroyAllTerminals() {
|
|
|
6597
7592
|
terminals.delete(id);
|
|
6598
7593
|
}
|
|
6599
7594
|
}
|
|
7595
|
+
setMaxListeners(20);
|
|
6600
7596
|
loadApiKeysFromConfig();
|
|
7597
|
+
if (!process.env.PI_CACHE_RETENTION) {
|
|
7598
|
+
process.env.PI_CACHE_RETENTION = "long";
|
|
7599
|
+
}
|
|
6601
7600
|
if (process.platform === "darwin" && !is.dev) {
|
|
6602
7601
|
try {
|
|
6603
7602
|
const shellPath = process.env.SHELL || "/bin/zsh";
|