research-copilot 0.2.0 → 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 +751 -259
- package/app/out/preload/index.js +5 -0
- package/app/out/renderer/assets/{MilkdownMarkdownEditor-Czh2N6UQ.js → MilkdownMarkdownEditor-D7GYpVZn.js} +50 -50
- package/app/out/renderer/assets/{arc-BWoErJNa.js → arc-Kp4J_Jd7.js} +1 -1
- package/app/out/renderer/assets/{blockDiagram-c4efeb88-Bod-vAlS.js → blockDiagram-c4efeb88-DkMSdn8j.js} +8 -8
- package/app/out/renderer/assets/{c4Diagram-c83219d4-CTVUA_li.js → c4Diagram-c83219d4-DqAGxrYw.js} +3 -3
- package/app/out/renderer/assets/{channel-CxGr5Q5E.js → channel-S4GQrISQ.js} +1 -1
- package/app/out/renderer/assets/{classDiagram-beda092f-DABwUrsU.js → classDiagram-beda092f-B7AsTCEg.js} +6 -6
- package/app/out/renderer/assets/{classDiagram-v2-2358418a-CFt8hqf5.js → classDiagram-v2-2358418a-B4oFy-In.js} +10 -10
- package/app/out/renderer/assets/{clone-BL91dKYn.js → clone-Dv1e6zYr.js} +1 -1
- package/app/out/renderer/assets/{createText-1719965b-DGkv4rEO.js → createText-1719965b-HBXHvWlI.js} +2 -2
- package/app/out/renderer/assets/{edges-96097737-Gf41lQOd.js → edges-96097737-B6X5lcC0.js} +3 -3
- package/app/out/renderer/assets/{erDiagram-0228fc6a-Dj75BiRy.js → erDiagram-0228fc6a-BmBmTBlH.js} +5 -5
- package/app/out/renderer/assets/{flowDb-c6c81e3f-C_xVBMxS.js → flowDb-c6c81e3f-CObz36ob.js} +1 -1
- package/app/out/renderer/assets/{flowDiagram-50d868cf-B-lLn2XC.js → flowDiagram-50d868cf-C2hFHxwF.js} +12 -12
- package/app/out/renderer/assets/{flowDiagram-v2-4f6560a1-BFnLU3PE.js → flowDiagram-v2-4f6560a1-DEe8EygW.js} +12 -12
- package/app/out/renderer/assets/{flowchart-elk-definition-6af322e1-DmjfyXbt.js → flowchart-elk-definition-6af322e1-CgTtfYKk.js} +6 -6
- package/app/out/renderer/assets/{ganttDiagram-a2739b55-BTPRekAy.js → ganttDiagram-a2739b55-C5Pq4zEy.js} +3 -3
- package/app/out/renderer/assets/{gitGraphDiagram-82fe8481-1riYxgGS.js → gitGraphDiagram-82fe8481-oLp0f8Ll.js} +2 -2
- package/app/out/renderer/assets/{graph-CvDtMlX-.js → graph-51iZ6wgR.js} +1 -1
- package/app/out/renderer/assets/{index-0kPJXDfu.js → index-32eUzqVW.js} +3 -3
- package/app/out/renderer/assets/{index-5325376f-BGaoNMNN.js → index-5325376f-yLvOW-Os.js} +6 -6
- package/app/out/renderer/assets/{index-O3gvL3-Z.js → index-AuZa-hTj.js} +3 -3
- package/app/out/renderer/assets/{index-BxOmAXUZ.js → index-B9a4DKM-.js} +3 -3
- package/app/out/renderer/assets/{index-NHbUPOmb.js → index-BMsuFGn6.js} +3 -3
- package/app/out/renderer/assets/{index-BnRwUKpv.js → index-BQA_Kvr6.js} +3 -3
- package/app/out/renderer/assets/{index-y5XZ-0EB.js → index-BSd80-j9.js} +4 -4
- package/app/out/renderer/assets/{index-zr8uxb8p.js → index-BfWWn8B_.js} +6 -6
- package/app/out/renderer/assets/{index-3LdRym1K.js → index-Bscx_5dF.js} +3 -3
- package/app/out/renderer/assets/{index-BgSz3yUy.js → index-CAOQIqEc.js} +6 -6
- package/app/out/renderer/assets/{index-BCOrnr8q.js → index-CTmGCKqa.js} +4 -4
- package/app/out/renderer/assets/{index-BK5rYWMs.js → index-CmpSV9Ld.js} +5 -5
- package/app/out/renderer/assets/{index-cAZJ88Np.js → index-Cn2e13ja.js} +6 -6
- package/app/out/renderer/assets/{index-CnL9yPzK.js → index-D_Y7v6pE.js} +3 -3
- package/app/out/renderer/assets/{index-BVNrdWzl.js → index-DjqJjt6u.js} +6 -6
- package/app/out/renderer/assets/{index-8tvmsRje.js → index-DppxBL77.js} +3 -3
- package/app/out/renderer/assets/{index-CUPy7R5v.js → index-Du-Z3sl4.js} +915 -72
- package/app/out/renderer/assets/{index-B4djqBxS.js → index-FGsCVYSr.js} +1 -1
- package/app/out/renderer/assets/{index-Ctwkk-AW.css → index-L4DJn7cw.css} +12 -8
- package/app/out/renderer/assets/{index-DrvR7Peq.js → index-UajPJYNV.js} +3 -3
- package/app/out/renderer/assets/{index-CXN1f9OT.js → index-_Z53hJps.js} +3 -3
- package/app/out/renderer/assets/{index-D2fFfHUR.js → index-_iFRQTkA.js} +6 -6
- package/app/out/renderer/assets/{index-B9lieynj.js → index-ohN9yRWw.js} +6 -6
- package/app/out/renderer/assets/{index-Bii7x9Rr.js → index-shoMWskw.js} +3 -3
- package/app/out/renderer/assets/{index-qS7qbXvX.js → index-y1Od1ed6.js} +3 -3
- package/app/out/renderer/assets/{infoDiagram-8eee0895-Cq8aXV8u.js → infoDiagram-8eee0895-Cm0Hm5ZX.js} +2 -2
- package/app/out/renderer/assets/{journeyDiagram-c64418c1-D4ewDrYD.js → journeyDiagram-c64418c1-A2Gw9bVu.js} +4 -4
- package/app/out/renderer/assets/{layout-CZmLZO9t.js → layout-C5N2nTfF.js} +2 -2
- package/app/out/renderer/assets/{line-D7kWOiRx.js → line-Dn6BEQAK.js} +1 -1
- package/app/out/renderer/assets/{linear-B055Dz0c.js → linear-8wk0rPUX.js} +1 -1
- package/app/out/renderer/assets/{mindmap-definition-8da855dc-D6EW4QCj.js → mindmap-definition-8da855dc-BVy6ISnb.js} +3 -3
- package/app/out/renderer/assets/{pieDiagram-a8764435-BX_Dz4T9.js → pieDiagram-a8764435-B9_axIHE.js} +3 -3
- package/app/out/renderer/assets/{quadrantDiagram-1e28029f-BsI6xGsm.js → quadrantDiagram-1e28029f-B1kmkDFg.js} +3 -3
- package/app/out/renderer/assets/{requirementDiagram-08caed73-c2d8T0BS.js → requirementDiagram-08caed73-C_bNWUtT.js} +5 -5
- package/app/out/renderer/assets/{sankeyDiagram-a04cb91d-CkDhRKRC.js → sankeyDiagram-a04cb91d-CD2h1LiI.js} +2 -2
- package/app/out/renderer/assets/{sequenceDiagram-c5b8d532-DS0RKYnD.js → sequenceDiagram-c5b8d532-B6d6cuqi.js} +3 -3
- package/app/out/renderer/assets/{stateDiagram-1ecb1508-BjTK27QX.js → stateDiagram-1ecb1508-CkuNj_3H.js} +6 -6
- package/app/out/renderer/assets/{stateDiagram-v2-c2b004d7-D1wWbeR3.js → stateDiagram-v2-c2b004d7-CevZ3tno.js} +10 -10
- package/app/out/renderer/assets/{styles-b4e223ce-DXUfbXTM.js → styles-b4e223ce-DAe5WQrg.js} +1 -1
- package/app/out/renderer/assets/{styles-ca3715f6-CE_JRTmB.js → styles-ca3715f6-BDSX88bY.js} +1 -1
- package/app/out/renderer/assets/{styles-d45a18b0-CdtAXXSE.js → styles-d45a18b0-SE9h7les.js} +4 -4
- package/app/out/renderer/assets/{svgDrawCommon-b86b1483-dCxPWgBl.js → svgDrawCommon-b86b1483-D1mpNbDQ.js} +1 -1
- package/app/out/renderer/assets/{timeline-definition-faaaa080-B7ZP3Dqw.js → timeline-definition-faaaa080-7Ha-nm4M.js} +3 -3
- package/app/out/renderer/assets/{xychartDiagram-f5964ef8-CXagmo1Q.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,22 @@
|
|
|
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 path, { resolve, join, sep, isAbsolute, extname, basename, dirname, relative } from "path";
|
|
5
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
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 { createHash as createHash$1 } from "crypto";
|
|
18
20
|
import { execFile as execFile$1 } from "child_process";
|
|
19
21
|
import __cjs_mod__ from "node:module";
|
|
20
22
|
const __filename = import.meta.filename;
|
|
@@ -233,10 +235,6 @@ function registerConfigHandlers(handleRaw) {
|
|
|
233
235
|
return { success: true };
|
|
234
236
|
});
|
|
235
237
|
}
|
|
236
|
-
function getFileName(path2) {
|
|
237
|
-
if (!path2) return "";
|
|
238
|
-
return path2.split("/").pop() || path2;
|
|
239
|
-
}
|
|
240
238
|
function inferMimeType(path2) {
|
|
241
239
|
const ext = extname(path2).toLowerCase();
|
|
242
240
|
if (ext === ".md" || ext === ".txt") return "text/plain";
|
|
@@ -576,13 +574,39 @@ function truncateHeadTail(text, maxChars, headRatio = 0.7) {
|
|
|
576
574
|
...[truncated ${truncatedChars} chars]
|
|
577
575
|
${text.slice(-tailChars)}`;
|
|
578
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
|
+
}
|
|
579
596
|
function toAgentResult(toolName, result) {
|
|
580
597
|
let text;
|
|
598
|
+
const MAX_RESULT_CHARS = 1e5;
|
|
581
599
|
if (result.success) {
|
|
582
600
|
if (result.data === void 0 || result.data === null) {
|
|
583
601
|
text = `[${toolName}] OK`;
|
|
584
602
|
} else if (typeof result.data === "string") {
|
|
585
|
-
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);
|
|
586
610
|
} else {
|
|
587
611
|
text = JSON.stringify(result.data, null, 2);
|
|
588
612
|
}
|
|
@@ -607,10 +631,11 @@ ${result.suggestions.map((s) => `- ${s}`).join("\n")}`);
|
|
|
607
631
|
}
|
|
608
632
|
text = parts.join("\n");
|
|
609
633
|
}
|
|
610
|
-
|
|
611
|
-
|
|
634
|
+
if (text.length > MAX_RESULT_CHARS) {
|
|
635
|
+
text = truncateHeadTail(text, MAX_RESULT_CHARS);
|
|
636
|
+
}
|
|
612
637
|
return {
|
|
613
|
-
content: [{ type: "text", text
|
|
638
|
+
content: [{ type: "text", text }],
|
|
614
639
|
details: { success: result.success, tool_name: toolName }
|
|
615
640
|
};
|
|
616
641
|
}
|
|
@@ -1255,7 +1280,12 @@ function createResearchMemoryTools(params) {
|
|
|
1255
1280
|
];
|
|
1256
1281
|
}
|
|
1257
1282
|
function slugify(text) {
|
|
1258
|
-
|
|
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;
|
|
1259
1289
|
}
|
|
1260
1290
|
function memoryFilename(type, name) {
|
|
1261
1291
|
return `${type}_${slugify(name)}.md`;
|
|
@@ -1267,22 +1297,40 @@ function ensureMemoryDir(projectPath) {
|
|
|
1267
1297
|
const dir = memoryDir(projectPath);
|
|
1268
1298
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
1269
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
|
+
}
|
|
1270
1308
|
function formatFrontmatter(fm) {
|
|
1271
1309
|
return [
|
|
1272
1310
|
"---",
|
|
1273
|
-
`name: ${fm.name}`,
|
|
1274
|
-
`description: ${fm.description}`,
|
|
1311
|
+
`name: ${yamlSafe(fm.name)}`,
|
|
1312
|
+
`description: ${yamlSafe(fm.description)}`,
|
|
1275
1313
|
`type: ${fm.type}`,
|
|
1276
1314
|
"---"
|
|
1277
1315
|
].join("\n");
|
|
1278
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
|
+
}
|
|
1279
1327
|
function parseFrontmatter(text) {
|
|
1280
1328
|
const match = text.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
1281
1329
|
if (!match) return null;
|
|
1282
1330
|
const fm = {};
|
|
1283
1331
|
for (const line of match[1].split("\n")) {
|
|
1284
1332
|
const kv = line.match(/^(\w+):\s*(.+)$/);
|
|
1285
|
-
if (kv) fm[kv[1]] = kv[2]
|
|
1333
|
+
if (kv) fm[kv[1]] = yamlUnescape(kv[2]);
|
|
1286
1334
|
}
|
|
1287
1335
|
if (!fm.name || !fm.type) return null;
|
|
1288
1336
|
const validTypes = ["user", "feedback", "project", "reference"];
|
|
@@ -1339,9 +1387,24 @@ function listMemoryFiles(projectPath) {
|
|
|
1339
1387
|
return [];
|
|
1340
1388
|
}
|
|
1341
1389
|
}
|
|
1342
|
-
function findMemoryByName(projectPath, name) {
|
|
1390
|
+
function findMemoryByName(projectPath, name, type) {
|
|
1343
1391
|
const lower = name.toLowerCase();
|
|
1344
|
-
|
|
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;
|
|
1345
1408
|
}
|
|
1346
1409
|
function buildMemoryIndex(entries) {
|
|
1347
1410
|
if (entries.length === 0) return "";
|
|
@@ -1381,6 +1444,9 @@ function migrateAgentMemoryToFile(projectPath) {
|
|
|
1381
1444
|
if (markerIdx < 0) return false;
|
|
1382
1445
|
const agentMemory = content.slice(markerIdx + marker.length).trim();
|
|
1383
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;
|
|
1384
1450
|
ensureMemoryDir(projectPath);
|
|
1385
1451
|
const entry = {
|
|
1386
1452
|
frontmatter: {
|
|
@@ -1435,73 +1501,98 @@ function createSaveMemoryTool(projectPath) {
|
|
|
1435
1501
|
if (!content) return toolError("MISSING_PARAMETER", "content is required.", {
|
|
1436
1502
|
suggestions: ["Provide the memory content to save."]
|
|
1437
1503
|
});
|
|
1438
|
-
|
|
1439
|
-
const
|
|
1440
|
-
const description =
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
message: `Memory saved: ${name} (${type})`,
|
|
1462
|
-
filename,
|
|
1463
|
-
totalMemories: allEntries.length,
|
|
1464
|
-
agentMdChars: indexResult.charCount
|
|
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
|
+
);
|
|
1465
1527
|
}
|
|
1466
|
-
|
|
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
|
+
});
|
|
1467
1538
|
}
|
|
1468
1539
|
};
|
|
1469
1540
|
}
|
|
1470
1541
|
function createDeleteMemoryTool(projectPath) {
|
|
1471
1542
|
return {
|
|
1472
1543
|
name: "delete-memory",
|
|
1473
|
-
description: "Delete a memory by name. Removes the file and its index entry in agent.md.",
|
|
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.",
|
|
1474
1545
|
parameters: {
|
|
1475
1546
|
type: "object",
|
|
1476
1547
|
properties: {
|
|
1477
1548
|
name: {
|
|
1478
1549
|
type: "string",
|
|
1479
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"
|
|
1480
1556
|
}
|
|
1481
1557
|
},
|
|
1482
1558
|
required: ["name"]
|
|
1483
1559
|
},
|
|
1484
1560
|
execute: async (input) => {
|
|
1485
1561
|
const name = String(input.name || "").trim();
|
|
1562
|
+
const type = input.type ? String(input.type) : void 0;
|
|
1486
1563
|
if (!name) return toolError("MISSING_PARAMETER", "name is required.", {
|
|
1487
1564
|
suggestions: ["Provide the name of the memory to delete."]
|
|
1488
1565
|
});
|
|
1489
|
-
const
|
|
1490
|
-
if (
|
|
1566
|
+
const allMatches = findAllMemoriesByName(projectPath, name);
|
|
1567
|
+
if (allMatches.length === 0) {
|
|
1491
1568
|
return toolError("NOT_FOUND", `Memory not found: "${name}"`, {
|
|
1492
1569
|
suggestions: ["Check the memory name — it is case-insensitive. Current memories are listed in agent.md."]
|
|
1493
1570
|
});
|
|
1494
1571
|
}
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
}
|
|
1504
|
-
|
|
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
|
+
});
|
|
1505
1596
|
}
|
|
1506
1597
|
};
|
|
1507
1598
|
}
|
|
@@ -1526,8 +1617,11 @@ const WEB_DEFAULTS = {
|
|
|
1526
1617
|
maxFetchMaxChars: 2e5,
|
|
1527
1618
|
defaultFetchTimeoutMs: 3e4,
|
|
1528
1619
|
maxArxivCacheEntries: 100,
|
|
1529
|
-
arxivSearchCacheTtlMs: 10 * 60 * 1e3
|
|
1620
|
+
arxivSearchCacheTtlMs: 10 * 60 * 1e3,
|
|
1530
1621
|
// 10 min
|
|
1622
|
+
/** Content above this size is saved to disk; agent gets preview + file path */
|
|
1623
|
+
fetchPersistThresholdChars: 3e4,
|
|
1624
|
+
fetchPreviewChars: 2e3
|
|
1531
1625
|
};
|
|
1532
1626
|
class ProviderRateGate {
|
|
1533
1627
|
constructor(minIntervalMs) {
|
|
@@ -1774,7 +1868,6 @@ function createWebSearchTool(ctx) {
|
|
|
1774
1868
|
const providerRequested = normalizeSearchProvider(params.provider);
|
|
1775
1869
|
const braveApiKey = process.env.BRAVE_API_KEY?.trim();
|
|
1776
1870
|
let effectiveProvider = providerRequested === "auto" ? braveApiKey ? "brave" : "arxiv" : providerRequested;
|
|
1777
|
-
ctx.onToolCall?.("web_search", { query, count, provider: effectiveProvider });
|
|
1778
1871
|
let results = [];
|
|
1779
1872
|
try {
|
|
1780
1873
|
if (effectiveProvider === "brave") {
|
|
@@ -1818,7 +1911,6 @@ function createWebSearchTool(ctx) {
|
|
|
1818
1911
|
count: results.length,
|
|
1819
1912
|
results
|
|
1820
1913
|
};
|
|
1821
|
-
ctx.onToolResult?.("web_search", payload);
|
|
1822
1914
|
return toAgentResult("web_search", {
|
|
1823
1915
|
success: true,
|
|
1824
1916
|
data: payload
|
|
@@ -1830,7 +1922,7 @@ function createWebFetchTool(ctx) {
|
|
|
1830
1922
|
return {
|
|
1831
1923
|
name: "web_fetch",
|
|
1832
1924
|
label: "Web Fetch",
|
|
1833
|
-
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.",
|
|
1834
1926
|
parameters: WebFetchSchema,
|
|
1835
1927
|
execute: async (_toolCallId, rawParams) => {
|
|
1836
1928
|
const params = rawParams;
|
|
@@ -1859,7 +1951,6 @@ function createWebFetchTool(ctx) {
|
|
|
1859
1951
|
const maxChars = typeof maxCharsRaw === "number" ? Math.max(100, Math.min(WEB_DEFAULTS.maxFetchMaxChars, Math.floor(maxCharsRaw))) : WEB_DEFAULTS.defaultFetchMaxChars;
|
|
1860
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);
|
|
1861
1953
|
const timeoutMs = typeof timeoutSecRaw === "number" ? Math.max(1e3, Math.floor(timeoutSecRaw * 1e3)) : WEB_DEFAULTS.defaultFetchTimeoutMs;
|
|
1862
|
-
ctx.onToolCall?.("web_fetch", { url: url.toString(), extractMode, maxChars });
|
|
1863
1954
|
let response;
|
|
1864
1955
|
const controller = new AbortController();
|
|
1865
1956
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -1897,16 +1988,37 @@ Source: ${url.toString()}
|
|
|
1897
1988
|
---
|
|
1898
1989
|
|
|
1899
1990
|
${sliced}` : sliced;
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
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
|
+
}
|
|
1910
2022
|
return toAgentResult("web_fetch", {
|
|
1911
2023
|
success: response.ok,
|
|
1912
2024
|
data: payload,
|
|
@@ -2774,7 +2886,6 @@ function createLiteratureSearchTool(ctx) {
|
|
|
2774
2886
|
suggestions: ["Ensure the agent runtime has an LLM provider configured (callLlm in ResearchToolContext)."]
|
|
2775
2887
|
}));
|
|
2776
2888
|
}
|
|
2777
|
-
ctx.onToolCall?.("literature-search", { query, context: extraContext });
|
|
2778
2889
|
const planUserPrompt = extraContext ? `Research request: ${query}
|
|
2779
2890
|
|
|
2780
2891
|
Additional context: ${extraContext}` : `Research request: ${query}`;
|
|
@@ -2951,9 +3062,9 @@ Additional context: ${extraContext}` : `Research request: ${query}`;
|
|
|
2951
3062
|
}
|
|
2952
3063
|
}
|
|
2953
3064
|
}
|
|
2954
|
-
const reviewDir = path.join(ctx.projectPath, ".research-pilot", "literature-runs", runId);
|
|
3065
|
+
const reviewDir = path$1.join(ctx.projectPath, ".research-pilot", "literature-runs", runId);
|
|
2955
3066
|
fs.mkdirSync(reviewDir, { recursive: true });
|
|
2956
|
-
const fullReviewPath = path.join(reviewDir, "review.json");
|
|
3067
|
+
const fullReviewPath = path$1.join(reviewDir, "review.json");
|
|
2957
3068
|
fs.writeFileSync(fullReviewPath, JSON.stringify({
|
|
2958
3069
|
plan,
|
|
2959
3070
|
allPapersCount: deduplicated.length,
|
|
@@ -2962,7 +3073,7 @@ Additional context: ${extraContext}` : `Research request: ${query}`;
|
|
|
2962
3073
|
queriesUsed,
|
|
2963
3074
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2964
3075
|
}, null, 2), "utf-8");
|
|
2965
|
-
const relReviewPath = path.relative(ctx.projectPath, fullReviewPath);
|
|
3076
|
+
const relReviewPath = path$1.relative(ctx.projectPath, fullReviewPath);
|
|
2966
3077
|
const payload = {
|
|
2967
3078
|
totalFound: deduplicated.length,
|
|
2968
3079
|
reviewedCount: review.relevantPapers.length,
|
|
@@ -2985,7 +3096,6 @@ Additional context: ${extraContext}` : `Research request: ${query}`;
|
|
|
2985
3096
|
runId,
|
|
2986
3097
|
queriesUsed: queriesUsed.slice(0, 10)
|
|
2987
3098
|
};
|
|
2988
|
-
ctx.onToolResult?.("literature-search", payload);
|
|
2989
3099
|
return toAgentResult("literature-search", toolSuccess(payload, pipelineWarnings.length > 0 ? pipelineWarnings : void 0));
|
|
2990
3100
|
}
|
|
2991
3101
|
};
|
|
@@ -3028,16 +3138,16 @@ const FORMAT_EXTENSIONS = {
|
|
|
3028
3138
|
zip: "zip"
|
|
3029
3139
|
};
|
|
3030
3140
|
function resolveWithinProject(projectPath, targetPath) {
|
|
3031
|
-
const root = path.resolve(projectPath);
|
|
3032
|
-
const resolved = targetPath.startsWith("/") ? path.resolve(targetPath) : path.resolve(root, targetPath);
|
|
3033
|
-
const rel = path.relative(root, resolved);
|
|
3034
|
-
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)) {
|
|
3035
3145
|
throw new Error(`Path escapes project directory: ${targetPath}`);
|
|
3036
3146
|
}
|
|
3037
3147
|
return resolved;
|
|
3038
3148
|
}
|
|
3039
3149
|
function toProjectRelative(projectPath, absolutePath) {
|
|
3040
|
-
return path.relative(path.resolve(projectPath), absolutePath);
|
|
3150
|
+
return path$1.relative(path$1.resolve(projectPath), absolutePath);
|
|
3041
3151
|
}
|
|
3042
3152
|
function isHttpUrl(value) {
|
|
3043
3153
|
try {
|
|
@@ -3057,7 +3167,7 @@ function sanitizeBaseName(value) {
|
|
|
3057
3167
|
function inferUrlBaseName(sourceUrl) {
|
|
3058
3168
|
try {
|
|
3059
3169
|
const parsed = new URL(sourceUrl);
|
|
3060
|
-
const candidate = path.parse(parsed.pathname).name || parsed.hostname;
|
|
3170
|
+
const candidate = path$1.parse(parsed.pathname).name || parsed.hostname;
|
|
3061
3171
|
return sanitizeBaseName(candidate);
|
|
3062
3172
|
} catch {
|
|
3063
3173
|
return sanitizeBaseName(sourceUrl);
|
|
@@ -3067,7 +3177,7 @@ function outputExtensionForMode(mode) {
|
|
|
3067
3177
|
return mode === "text" ? ".txt" : ".md";
|
|
3068
3178
|
}
|
|
3069
3179
|
function extensionFromPath(filePath) {
|
|
3070
|
-
const ext = path.extname(filePath).replace(".", "").toLowerCase();
|
|
3180
|
+
const ext = path$1.extname(filePath).replace(".", "").toLowerCase();
|
|
3071
3181
|
return ext || void 0;
|
|
3072
3182
|
}
|
|
3073
3183
|
function normalizeMode(value) {
|
|
@@ -3301,15 +3411,15 @@ async function downloadToProject(projectPath, sourceUrl) {
|
|
|
3301
3411
|
if (bytes.length > DEFAULT_MAX_DOWNLOAD_BYTES) {
|
|
3302
3412
|
return { ok: false, error: `Downloaded file too large (${bytes.length} bytes > ${DEFAULT_MAX_DOWNLOAD_BYTES} bytes)` };
|
|
3303
3413
|
}
|
|
3304
|
-
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");
|
|
3305
3415
|
await fsp.mkdir(downloadDir, { recursive: true });
|
|
3306
3416
|
const urlObj = new URL(sourceUrl);
|
|
3307
3417
|
const fromExt = extensionFromPath(urlObj.pathname);
|
|
3308
3418
|
const fromContentType = detectFormatFromContentType(response.headers.get("content-type"));
|
|
3309
3419
|
const chosenExt = FORMAT_EXTENSIONS[fromContentType || ""] || FORMAT_EXTENSIONS[fromExt || ""] || "bin";
|
|
3310
|
-
const hash = createHash("sha256").update(sourceUrl).digest("hex").slice(0, 12);
|
|
3420
|
+
const hash = createHash$1("sha256").update(sourceUrl).digest("hex").slice(0, 12);
|
|
3311
3421
|
const fileName = `${isoStamp()}-${hash}.${chosenExt}`;
|
|
3312
|
-
const inputPath = path.join(downloadDir, fileName);
|
|
3422
|
+
const inputPath = path$1.join(downloadDir, fileName);
|
|
3313
3423
|
await fsp.writeFile(inputPath, bytes);
|
|
3314
3424
|
return {
|
|
3315
3425
|
ok: true,
|
|
@@ -3389,9 +3499,9 @@ ${sections}
|
|
|
3389
3499
|
`;
|
|
3390
3500
|
}
|
|
3391
3501
|
function buildPerRangeOutputPath(baseAbsolutePath, range, mode) {
|
|
3392
|
-
const parsed = path.parse(baseAbsolutePath);
|
|
3502
|
+
const parsed = path$1.parse(baseAbsolutePath);
|
|
3393
3503
|
const ext = parsed.ext || outputExtensionForMode(mode);
|
|
3394
|
-
return path.join(parsed.dir, `${parsed.name}-${rangeFileToken(range)}${ext}`);
|
|
3504
|
+
return path$1.join(parsed.dir, `${parsed.name}-${rangeFileToken(range)}${ext}`);
|
|
3395
3505
|
}
|
|
3396
3506
|
function buildPreview(text, maxPreviewChars = DEFAULT_PREVIEW_CHARS) {
|
|
3397
3507
|
const content = text.slice(0, maxPreviewChars);
|
|
@@ -3403,14 +3513,14 @@ function buildPreview(text, maxPreviewChars = DEFAULT_PREVIEW_CHARS) {
|
|
|
3403
3513
|
};
|
|
3404
3514
|
}
|
|
3405
3515
|
function defaultOutputPath(args) {
|
|
3406
|
-
const outputDir = path.join(
|
|
3407
|
-
path.resolve(args.projectPath),
|
|
3516
|
+
const outputDir = path$1.join(
|
|
3517
|
+
path$1.resolve(args.projectPath),
|
|
3408
3518
|
".research-pilot",
|
|
3409
3519
|
"cache",
|
|
3410
3520
|
"converted"
|
|
3411
3521
|
);
|
|
3412
|
-
const baseName = args.isUrl ? inferUrlBaseName(args.sourceRaw) : sanitizeBaseName(path.parse(args.inputPath || "document").name || "document");
|
|
3413
|
-
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)}`);
|
|
3414
3524
|
}
|
|
3415
3525
|
function failure(payload) {
|
|
3416
3526
|
return toAgentResult("convert_document", { success: false, error: payload.error, data: payload });
|
|
@@ -3593,7 +3703,7 @@ function createConvertDocumentTool(ctx) {
|
|
|
3593
3703
|
mode
|
|
3594
3704
|
});
|
|
3595
3705
|
}
|
|
3596
|
-
await fsp.mkdir(path.dirname(outputAbsolutePath), { recursive: true });
|
|
3706
|
+
await fsp.mkdir(path$1.dirname(outputAbsolutePath), { recursive: true });
|
|
3597
3707
|
let converter;
|
|
3598
3708
|
let producedText = "";
|
|
3599
3709
|
let pageCount;
|
|
@@ -3626,7 +3736,7 @@ function createConvertDocumentTool(ctx) {
|
|
|
3626
3736
|
anyTruncated = true;
|
|
3627
3737
|
}
|
|
3628
3738
|
const segmentAbsPath = buildPerRangeOutputPath(outputAbsolutePath, range, mode);
|
|
3629
|
-
await fsp.mkdir(path.dirname(segmentAbsPath), { recursive: true });
|
|
3739
|
+
await fsp.mkdir(path$1.dirname(segmentAbsPath), { recursive: true });
|
|
3630
3740
|
await fsp.writeFile(segmentAbsPath, segmentText, "utf8");
|
|
3631
3741
|
const segmentRelPath = toProjectRelative(projectPath, segmentAbsPath);
|
|
3632
3742
|
const segmentPreview = buildPreview(segmentText);
|
|
@@ -3765,7 +3875,7 @@ function createDataAnalyzeTool(ctx) {
|
|
|
3765
3875
|
suggestions: ["Valid task types: analyze, visualize, transform, model."]
|
|
3766
3876
|
}));
|
|
3767
3877
|
}
|
|
3768
|
-
const absDataFile = path.resolve(ctx.workspacePath, filePath);
|
|
3878
|
+
const absDataFile = path$1.resolve(ctx.workspacePath, filePath);
|
|
3769
3879
|
if (!fs.existsSync(absDataFile)) {
|
|
3770
3880
|
return toAgentResult("data_analyze", toolError("FILE_NOT_FOUND", `File not found: ${filePath}`, {
|
|
3771
3881
|
suggestions: [
|
|
@@ -3775,18 +3885,17 @@ function createDataAnalyzeTool(ctx) {
|
|
|
3775
3885
|
context: { workspacePath: ctx.workspacePath, resolvedPath: absDataFile }
|
|
3776
3886
|
}));
|
|
3777
3887
|
}
|
|
3778
|
-
ctx.onToolCall?.("data_analyze", { file_path: filePath, instructions, task_type: taskType });
|
|
3779
3888
|
const runId = Date.now().toString(36);
|
|
3780
|
-
const outputBase = path.join(ctx.workspacePath, ".research-pilot", "data-runs", runId);
|
|
3781
|
-
const figuresDir = path.join(outputBase, "figures");
|
|
3782
|
-
const tablesDir = path.join(outputBase, "tables");
|
|
3783
|
-
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");
|
|
3784
3893
|
fs.mkdirSync(figuresDir, { recursive: true });
|
|
3785
3894
|
fs.mkdirSync(tablesDir, { recursive: true });
|
|
3786
3895
|
fs.mkdirSync(dataDir, { recursive: true });
|
|
3787
|
-
const resultsFile = path.join(outputBase, "results.json");
|
|
3896
|
+
const resultsFile = path$1.join(outputBase, "results.json");
|
|
3788
3897
|
const rawPreview = fs.readFileSync(absDataFile, "utf-8").slice(0, 2e3);
|
|
3789
|
-
const ext = path.extname(absDataFile).toLowerCase();
|
|
3898
|
+
const ext = path$1.extname(absDataFile).toLowerCase();
|
|
3790
3899
|
const formatHint = ext === ".csv" ? "CSV" : ext === ".tsv" ? "TSV" : ext === ".json" ? "JSON" : ext === ".xlsx" ? "XLSX" : "unknown";
|
|
3791
3900
|
if (!ctx.callLlm) {
|
|
3792
3901
|
return toAgentResult("data_analyze", toolError("LLM_UNAVAILABLE", "LLM not available for code generation.", {
|
|
@@ -3834,7 +3943,7 @@ function createDataAnalyzeTool(ctx) {
|
|
|
3834
3943
|
""
|
|
3835
3944
|
].join("\n");
|
|
3836
3945
|
const fullScript = DATA_CODE_TEMPLATE + pathDefinitions + "\n" + generatedCode;
|
|
3837
|
-
const scriptPath = path.join(outputBase, "script.py");
|
|
3946
|
+
const scriptPath = path$1.join(outputBase, "script.py");
|
|
3838
3947
|
fs.writeFileSync(scriptPath, fullScript, "utf-8");
|
|
3839
3948
|
try {
|
|
3840
3949
|
const { stdout, stderr } = await execFileAsync("python3", [scriptPath], {
|
|
@@ -3861,7 +3970,7 @@ function createDataAnalyzeTool(ctx) {
|
|
|
3861
3970
|
outputs.push({
|
|
3862
3971
|
name: f,
|
|
3863
3972
|
type,
|
|
3864
|
-
path: path.relative(ctx.workspacePath, path.join(dir, f))
|
|
3973
|
+
path: path$1.relative(ctx.workspacePath, path$1.join(dir, f))
|
|
3865
3974
|
});
|
|
3866
3975
|
}
|
|
3867
3976
|
}
|
|
@@ -3874,26 +3983,25 @@ function createDataAnalyzeTool(ctx) {
|
|
|
3874
3983
|
summary: manifest.summary,
|
|
3875
3984
|
warnings: manifest.warnings
|
|
3876
3985
|
} : void 0,
|
|
3877
|
-
scriptPath: path.relative(ctx.workspacePath, scriptPath),
|
|
3986
|
+
scriptPath: path$1.relative(ctx.workspacePath, scriptPath),
|
|
3878
3987
|
runId
|
|
3879
3988
|
};
|
|
3880
|
-
ctx.onToolResult?.("data_analyze", payload);
|
|
3881
3989
|
return toAgentResult("data_analyze", { success: true, data: payload });
|
|
3882
3990
|
} catch (err) {
|
|
3883
3991
|
const errorDetail = err.stderr?.slice(0, 2e3) || err.message;
|
|
3884
3992
|
return toAgentResult("data_analyze", toolError("EXECUTION_FAILED", `Python execution failed: ${errorDetail}`, {
|
|
3885
3993
|
retryable: true,
|
|
3886
3994
|
suggestions: [
|
|
3887
|
-
`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.`,
|
|
3888
3996
|
"Check that required Python packages (pandas, matplotlib, seaborn, etc.) are installed.",
|
|
3889
3997
|
"Try simplifying the analysis instructions."
|
|
3890
3998
|
],
|
|
3891
3999
|
context: {
|
|
3892
|
-
scriptPath: path.relative(ctx.workspacePath, scriptPath),
|
|
4000
|
+
scriptPath: path$1.relative(ctx.workspacePath, scriptPath),
|
|
3893
4001
|
runId
|
|
3894
4002
|
},
|
|
3895
4003
|
data: {
|
|
3896
|
-
scriptPath: path.relative(ctx.workspacePath, scriptPath),
|
|
4004
|
+
scriptPath: path$1.relative(ctx.workspacePath, scriptPath),
|
|
3897
4005
|
runId
|
|
3898
4006
|
}
|
|
3899
4007
|
}));
|
|
@@ -3941,7 +4049,7 @@ function wrapResearchTool(tool) {
|
|
|
3941
4049
|
}
|
|
3942
4050
|
function createResearchTools(ctx) {
|
|
3943
4051
|
const tools = [];
|
|
3944
|
-
tools.push(createWebSearchTool(
|
|
4052
|
+
tools.push(createWebSearchTool());
|
|
3945
4053
|
tools.push(createWebFetchTool(ctx));
|
|
3946
4054
|
tools.push(createLiteratureSearchTool(ctx));
|
|
3947
4055
|
tools.push(createConvertDocumentTool(ctx));
|
|
@@ -4021,7 +4129,7 @@ function agentCalledSaveMemoryThisTurn(messages) {
|
|
|
4021
4129
|
return false;
|
|
4022
4130
|
}
|
|
4023
4131
|
async function maybeExtractMemories(config, messages, turnCount, extractEveryN = 3) {
|
|
4024
|
-
if (process.env.RESEARCH_COPILOT_AUTO_EXTRACT
|
|
4132
|
+
if (process.env.RESEARCH_COPILOT_AUTO_EXTRACT !== "1") return;
|
|
4025
4133
|
if (turnCount % extractEveryN !== 0) return;
|
|
4026
4134
|
if (agentCalledSaveMemoryThisTurn(messages)) {
|
|
4027
4135
|
if (config.debug) console.log("[Extractor] Skipped — agent called save-memory this turn");
|
|
@@ -4045,33 +4153,57 @@ async function maybeExtractMemories(config, messages, turnCount, extractEveryN =
|
|
|
4045
4153
|
const textContent = result.content.find((c) => c.type === "text");
|
|
4046
4154
|
const text = textContent?.text?.trim() ?? "";
|
|
4047
4155
|
if (!text || text === "[]") return;
|
|
4048
|
-
|
|
4049
|
-
const
|
|
4050
|
-
|
|
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
|
+
}
|
|
4051
4178
|
if (!Array.isArray(extracted) || extracted.length === 0) return;
|
|
4052
4179
|
ensureMemoryDir(config.projectPath);
|
|
4053
|
-
|
|
4180
|
+
const validEntries = [];
|
|
4054
4181
|
for (const mem of extracted) {
|
|
4055
4182
|
if (!mem.type || !mem.name || !mem.content) continue;
|
|
4056
4183
|
if (!VALID_TYPES.includes(mem.type)) continue;
|
|
4057
|
-
const
|
|
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({
|
|
4058
4188
|
frontmatter: {
|
|
4059
4189
|
name: mem.name,
|
|
4060
|
-
description
|
|
4190
|
+
description,
|
|
4061
4191
|
type: mem.type
|
|
4062
4192
|
},
|
|
4063
4193
|
content: mem.content,
|
|
4064
4194
|
filename: memoryFilename(mem.type, mem.name)
|
|
4065
|
-
};
|
|
4066
|
-
writeMemoryFile(config.projectPath, entry);
|
|
4067
|
-
written++;
|
|
4195
|
+
});
|
|
4068
4196
|
}
|
|
4069
|
-
if (
|
|
4197
|
+
if (validEntries.length === 0) return;
|
|
4198
|
+
await withIndexLock(() => {
|
|
4199
|
+
for (const entry of validEntries) {
|
|
4200
|
+
writeMemoryFile(config.projectPath, entry);
|
|
4201
|
+
}
|
|
4070
4202
|
const allEntries = listMemoryFiles(config.projectPath);
|
|
4071
4203
|
updateAgentMdIndex(config.projectPath, allEntries);
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
}
|
|
4204
|
+
});
|
|
4205
|
+
if (config.debug) {
|
|
4206
|
+
console.log(`[Extractor] Saved ${validEntries.length} memories from conversation`);
|
|
4075
4207
|
}
|
|
4076
4208
|
} catch (err) {
|
|
4077
4209
|
if (config.debug) {
|
|
@@ -4123,7 +4255,7 @@ function discoverSkillFiles(rootDir) {
|
|
|
4123
4255
|
continue;
|
|
4124
4256
|
}
|
|
4125
4257
|
for (const entry of entries) {
|
|
4126
|
-
const abs = path.join(current.dir, entry.name);
|
|
4258
|
+
const abs = path$1.join(current.dir, entry.name);
|
|
4127
4259
|
if (entry.isFile() && entry.name === SKILL_FILE_NAME) {
|
|
4128
4260
|
files.push(abs);
|
|
4129
4261
|
continue;
|
|
@@ -4154,7 +4286,7 @@ function parseSkillFile(skillFile, displayPath, source) {
|
|
|
4154
4286
|
tags,
|
|
4155
4287
|
triggers,
|
|
4156
4288
|
path: displayPath,
|
|
4157
|
-
dir: path.dirname(skillFile),
|
|
4289
|
+
dir: path$1.dirname(skillFile),
|
|
4158
4290
|
source,
|
|
4159
4291
|
content
|
|
4160
4292
|
};
|
|
@@ -4170,34 +4302,34 @@ function inferCategory(name, description) {
|
|
|
4170
4302
|
return "General";
|
|
4171
4303
|
}
|
|
4172
4304
|
function loadBuiltinSkills() {
|
|
4173
|
-
const skillsRoot = _builtinSkillsRoot ?? path.dirname(fileURLToPath(import.meta.url));
|
|
4305
|
+
const skillsRoot = _builtinSkillsRoot ?? path$1.dirname(fileURLToPath(import.meta.url));
|
|
4174
4306
|
if (!fs.existsSync(skillsRoot) || !fs.statSync(skillsRoot).isDirectory()) {
|
|
4175
4307
|
return [];
|
|
4176
4308
|
}
|
|
4177
4309
|
const files = discoverSkillFiles(skillsRoot);
|
|
4178
4310
|
const byName = /* @__PURE__ */ new Map();
|
|
4179
4311
|
for (const file of files) {
|
|
4180
|
-
const entry = parseSkillFile(file, `[builtin] ${path.relative(skillsRoot, file)}`, "builtin");
|
|
4312
|
+
const entry = parseSkillFile(file, `[builtin] ${path$1.relative(skillsRoot, file)}`, "builtin");
|
|
4181
4313
|
if (entry) byName.set(entry.name, entry);
|
|
4182
4314
|
}
|
|
4183
4315
|
return Array.from(byName.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
4184
4316
|
}
|
|
4185
4317
|
function loadWorkspaceSkills(workspacePath) {
|
|
4186
|
-
const skillRoot = path.resolve(workspacePath, ".research-pilot", "skills");
|
|
4318
|
+
const skillRoot = path$1.resolve(workspacePath, ".research-pilot", "skills");
|
|
4187
4319
|
if (!fs.existsSync(skillRoot) || !fs.statSync(skillRoot).isDirectory()) {
|
|
4188
4320
|
return [];
|
|
4189
4321
|
}
|
|
4190
4322
|
const files = discoverSkillFiles(skillRoot);
|
|
4191
|
-
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);
|
|
4192
4324
|
return entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
4193
4325
|
}
|
|
4194
4326
|
function loadUserSkills() {
|
|
4195
|
-
const userRoot = path.resolve(os.homedir(), ".research-pilot", "skills");
|
|
4327
|
+
const userRoot = path$1.resolve(os.homedir(), ".research-pilot", "skills");
|
|
4196
4328
|
if (!fs.existsSync(userRoot) || !fs.statSync(userRoot).isDirectory()) {
|
|
4197
4329
|
return [];
|
|
4198
4330
|
}
|
|
4199
4331
|
const files = discoverSkillFiles(userRoot);
|
|
4200
|
-
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);
|
|
4201
4333
|
return entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
4202
4334
|
}
|
|
4203
4335
|
function loadAllSkills(workspacePath) {
|
|
@@ -4242,7 +4374,7 @@ function resolveSkillDependencies(allSkills, directSelection) {
|
|
|
4242
4374
|
return result;
|
|
4243
4375
|
}
|
|
4244
4376
|
function readEnabledSkills(workspacePath) {
|
|
4245
|
-
const configPath = path.resolve(workspacePath, ".research-pilot", "skills-config.json");
|
|
4377
|
+
const configPath = path$1.resolve(workspacePath, ".research-pilot", "skills-config.json");
|
|
4246
4378
|
try {
|
|
4247
4379
|
const raw = fs.readFileSync(configPath, "utf8");
|
|
4248
4380
|
const config = JSON.parse(raw);
|
|
@@ -4252,9 +4384,9 @@ function readEnabledSkills(workspacePath) {
|
|
|
4252
4384
|
return null;
|
|
4253
4385
|
}
|
|
4254
4386
|
function writeEnabledSkills(workspacePath, enabledSkills) {
|
|
4255
|
-
const configDir = path.resolve(workspacePath, ".research-pilot");
|
|
4387
|
+
const configDir = path$1.resolve(workspacePath, ".research-pilot");
|
|
4256
4388
|
if (!fs.existsSync(configDir)) fs.mkdirSync(configDir, { recursive: true });
|
|
4257
|
-
const configPath = path.join(configDir, "skills-config.json");
|
|
4389
|
+
const configPath = path$1.join(configDir, "skills-config.json");
|
|
4258
4390
|
fs.writeFileSync(configPath, JSON.stringify({ enabledSkills }, null, 2), "utf8");
|
|
4259
4391
|
}
|
|
4260
4392
|
function buildSkillManifests(workspacePath) {
|
|
@@ -4278,15 +4410,15 @@ function buildSkillManifests(workspacePath) {
|
|
|
4278
4410
|
});
|
|
4279
4411
|
}
|
|
4280
4412
|
function installSkillToWorkspace(workspacePath, skillName, skillDir) {
|
|
4281
|
-
const destDir = path.resolve(workspacePath, ".research-pilot", "skills", skillName);
|
|
4413
|
+
const destDir = path$1.resolve(workspacePath, ".research-pilot", "skills", skillName);
|
|
4282
4414
|
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
|
|
4283
4415
|
copyDirSync(skillDir, destDir);
|
|
4284
4416
|
}
|
|
4285
4417
|
function copyDirSync(src, dest) {
|
|
4286
4418
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
4287
4419
|
for (const entry of entries) {
|
|
4288
|
-
const srcPath = path.join(src, entry.name);
|
|
4289
|
-
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);
|
|
4290
4422
|
if (entry.isDirectory()) {
|
|
4291
4423
|
if (!fs.existsSync(destPath)) fs.mkdirSync(destPath, { recursive: true });
|
|
4292
4424
|
copyDirSync(srcPath, destPath);
|
|
@@ -4487,6 +4619,7 @@ async function createCoordinator(config) {
|
|
|
4487
4619
|
onStream,
|
|
4488
4620
|
onToolCall,
|
|
4489
4621
|
onToolResult,
|
|
4622
|
+
onToolProgress,
|
|
4490
4623
|
onUsage,
|
|
4491
4624
|
onSkillLoaded
|
|
4492
4625
|
} = config;
|
|
@@ -4558,11 +4691,11 @@ async function createCoordinator(config) {
|
|
|
4558
4691
|
}
|
|
4559
4692
|
}
|
|
4560
4693
|
}
|
|
4561
|
-
const wrappedOnToolResult = (tool, result, args) => {
|
|
4694
|
+
const wrappedOnToolResult = (tool, result, args, toolCallId) => {
|
|
4562
4695
|
if (activeTurnToolCallCount !== null) {
|
|
4563
4696
|
activeTurnToolCallCount++;
|
|
4564
4697
|
}
|
|
4565
|
-
onToolResult?.(tool, result, args);
|
|
4698
|
+
onToolResult?.(tool, result, args, toolCallId);
|
|
4566
4699
|
};
|
|
4567
4700
|
const toolCtx = {
|
|
4568
4701
|
workspacePath: projectPath,
|
|
@@ -4685,14 +4818,14 @@ The conversation continues below.`,
|
|
|
4685
4818
|
}
|
|
4686
4819
|
},
|
|
4687
4820
|
beforeToolCall: async (ctx) => {
|
|
4688
|
-
onToolCall?.(ctx.toolCall.name, ctx.args);
|
|
4821
|
+
onToolCall?.(ctx.toolCall.name, ctx.args, ctx.toolCall.id);
|
|
4689
4822
|
if (debug) {
|
|
4690
4823
|
console.log(` [Tool] ${ctx.toolCall.name}(${JSON.stringify(ctx.args).slice(0, 120)}...)`);
|
|
4691
4824
|
}
|
|
4692
4825
|
return void 0;
|
|
4693
4826
|
},
|
|
4694
4827
|
afterToolCall: async (ctx) => {
|
|
4695
|
-
wrappedOnToolResult(ctx.toolCall.name, ctx.result, ctx.args);
|
|
4828
|
+
wrappedOnToolResult(ctx.toolCall.name, ctx.result, ctx.args, ctx.toolCall.id);
|
|
4696
4829
|
if (ctx.toolCall.name === "load_skill" && onSkillLoaded) {
|
|
4697
4830
|
const args = ctx.args;
|
|
4698
4831
|
const result = ctx.result;
|
|
@@ -4703,7 +4836,7 @@ The conversation continues below.`,
|
|
|
4703
4836
|
return void 0;
|
|
4704
4837
|
}
|
|
4705
4838
|
});
|
|
4706
|
-
if (onStream || onUsage) {
|
|
4839
|
+
if (onStream || onUsage || onToolProgress) {
|
|
4707
4840
|
agent.subscribe((event) => {
|
|
4708
4841
|
if (event.type === "message_update" && onStream) {
|
|
4709
4842
|
if (event.assistantMessageEvent.type === "text_delta") {
|
|
@@ -4717,6 +4850,15 @@ The conversation continues below.`,
|
|
|
4717
4850
|
onUsage(usage, usage.cost);
|
|
4718
4851
|
}
|
|
4719
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
|
+
}
|
|
4720
4862
|
});
|
|
4721
4863
|
}
|
|
4722
4864
|
async function clearSessionMemory() {
|
|
@@ -4826,24 +4968,17 @@ ${historyText}`,
|
|
|
4826
4968
|
## User Instructions (agent.md)
|
|
4827
4969
|
|
|
4828
4970
|
${agentMdContent}`;
|
|
4829
|
-
}
|
|
4830
|
-
if (skillSummariesPrompt) {
|
|
4831
|
-
enrichedSystem = `${enrichedSystem}
|
|
4832
|
-
|
|
4833
|
-
${skillSummariesPrompt}`;
|
|
4834
4971
|
}
|
|
4835
4972
|
agent.setSystemPrompt(enrichedSystem);
|
|
4836
|
-
|
|
4837
|
-
if (
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
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")}
|
|
4842
4978
|
|
|
4843
4979
|
---
|
|
4844
4980
|
|
|
4845
|
-
${message}
|
|
4846
|
-
}
|
|
4981
|
+
${message}` : message;
|
|
4847
4982
|
let perTurnToolCallCount = 0;
|
|
4848
4983
|
activeTurnToolCallCount = 0;
|
|
4849
4984
|
try {
|
|
@@ -4949,10 +5084,10 @@ function sessionSummaryGet(projectPath, sessionId) {
|
|
|
4949
5084
|
}
|
|
4950
5085
|
}
|
|
4951
5086
|
class RateLimiter {
|
|
4952
|
-
constructor(
|
|
5087
|
+
constructor(configs2) {
|
|
4953
5088
|
this.timestamps = /* @__PURE__ */ new Map();
|
|
4954
5089
|
this.activeCounts = /* @__PURE__ */ new Map();
|
|
4955
|
-
this.configs =
|
|
5090
|
+
this.configs = configs2;
|
|
4956
5091
|
}
|
|
4957
5092
|
/**
|
|
4958
5093
|
* Wait until a request slot is available for the given source.
|
|
@@ -5565,7 +5700,7 @@ function parseMentions(message) {
|
|
|
5565
5700
|
return { cleanMessage, mentions };
|
|
5566
5701
|
}
|
|
5567
5702
|
function getCacheKey(filePath, mtime) {
|
|
5568
|
-
const hash = createHash
|
|
5703
|
+
const hash = createHash("sha256").update(`${filePath}:${mtime}`).digest("hex").slice(0, 16);
|
|
5569
5704
|
const name = basename(filePath).replace(/[^a-zA-Z0-9.-]/g, "_");
|
|
5570
5705
|
return `${name}-${hash}.json`;
|
|
5571
5706
|
}
|
|
@@ -6037,6 +6172,10 @@ class RealtimeBuffer {
|
|
|
6037
6172
|
isStreaming = false;
|
|
6038
6173
|
progressItems = [];
|
|
6039
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();
|
|
6040
6179
|
/** Append a streaming text chunk (called from onStream callback) */
|
|
6041
6180
|
appendChunk(chunk) {
|
|
6042
6181
|
this.streamingText += chunk;
|
|
@@ -6051,18 +6190,45 @@ class RealtimeBuffer {
|
|
|
6051
6190
|
this.progressItems.push(item);
|
|
6052
6191
|
}
|
|
6053
6192
|
}
|
|
6054
|
-
/** Record an activity event */
|
|
6193
|
+
/** Record an activity event and track tool-call start times */
|
|
6055
6194
|
pushActivity(event) {
|
|
6195
|
+
if (event.type === "tool-call" && event.toolCallId) {
|
|
6196
|
+
this.toolCallStartTimes.set(event.toolCallId, Date.now());
|
|
6197
|
+
}
|
|
6056
6198
|
this.activityEvents.push(event);
|
|
6057
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
|
+
}
|
|
6058
6221
|
/** Clear progress and activity (called on project close or explicit reset) */
|
|
6059
6222
|
clearRun() {
|
|
6060
6223
|
this.progressItems = [];
|
|
6061
6224
|
this.activityEvents = [];
|
|
6225
|
+
this.toolEvents = [];
|
|
6062
6226
|
}
|
|
6063
6227
|
/** Clear only activity events (called on new agent run) */
|
|
6064
6228
|
clearActivity() {
|
|
6065
6229
|
this.activityEvents = [];
|
|
6230
|
+
this.toolEvents = [];
|
|
6231
|
+
this.toolCallStartTimes.clear();
|
|
6066
6232
|
}
|
|
6067
6233
|
/** Mark streaming finished (called on agent:done) */
|
|
6068
6234
|
finishStreaming() {
|
|
@@ -6075,6 +6241,8 @@ class RealtimeBuffer {
|
|
|
6075
6241
|
this.isStreaming = false;
|
|
6076
6242
|
this.progressItems = [];
|
|
6077
6243
|
this.activityEvents = [];
|
|
6244
|
+
this.toolEvents = [];
|
|
6245
|
+
this.toolCallStartTimes.clear();
|
|
6078
6246
|
}
|
|
6079
6247
|
/** Return a snapshot the renderer can use to hydrate stores */
|
|
6080
6248
|
getSnapshot() {
|
|
@@ -6082,17 +6250,366 @@ class RealtimeBuffer {
|
|
|
6082
6250
|
streamingText: this.streamingText,
|
|
6083
6251
|
isStreaming: this.isStreaming,
|
|
6084
6252
|
progressItems: [...this.progressItems],
|
|
6085
|
-
activityEvents: [...this.activityEvents]
|
|
6253
|
+
activityEvents: [...this.activityEvents],
|
|
6254
|
+
toolEvents: [...this.toolEvents]
|
|
6086
6255
|
};
|
|
6087
6256
|
}
|
|
6088
6257
|
}
|
|
6089
6258
|
function createRealtimeBuffer() {
|
|
6090
6259
|
return new RealtimeBuffer();
|
|
6091
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
|
+
}
|
|
6092
6609
|
const EMPTY = {
|
|
6093
6610
|
version: 1,
|
|
6094
6611
|
updatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
6095
|
-
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 }
|
|
6096
6613
|
};
|
|
6097
6614
|
function usagePath(baseDir) {
|
|
6098
6615
|
return join(baseDir, "usage.json");
|
|
@@ -6102,6 +6619,9 @@ function loadUsageTotals(baseDir) {
|
|
|
6102
6619
|
const raw = readFileSync(usagePath(baseDir), "utf-8");
|
|
6103
6620
|
const parsed = JSON.parse(raw);
|
|
6104
6621
|
if (!parsed?.totals) return { ...EMPTY };
|
|
6622
|
+
const t = parsed.totals;
|
|
6623
|
+
t.completionTokens ??= 0;
|
|
6624
|
+
t.cacheWriteTokens ??= 0;
|
|
6105
6625
|
return parsed;
|
|
6106
6626
|
} catch {
|
|
6107
6627
|
return { ...EMPTY };
|
|
@@ -6130,15 +6650,17 @@ function writeAtomically(filePath, data) {
|
|
|
6130
6650
|
}
|
|
6131
6651
|
}
|
|
6132
6652
|
}
|
|
6133
|
-
function accumulateUsage(baseDir, promptTokens, completionTokens, cachedTokens, cost) {
|
|
6653
|
+
function accumulateUsage(baseDir, promptTokens, completionTokens, cachedTokens, cacheWriteTokens, cost) {
|
|
6134
6654
|
const existing = loadUsageTotals(baseDir);
|
|
6135
6655
|
const next = {
|
|
6136
6656
|
version: 1,
|
|
6137
6657
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6138
6658
|
totals: {
|
|
6139
|
-
tokens: existing.totals.tokens + promptTokens + completionTokens,
|
|
6659
|
+
tokens: existing.totals.tokens + promptTokens + completionTokens + cachedTokens,
|
|
6140
6660
|
promptTokens: existing.totals.promptTokens + promptTokens,
|
|
6661
|
+
completionTokens: existing.totals.completionTokens + completionTokens,
|
|
6141
6662
|
cachedTokens: existing.totals.cachedTokens + cachedTokens,
|
|
6663
|
+
cacheWriteTokens: existing.totals.cacheWriteTokens + cacheWriteTokens,
|
|
6142
6664
|
cost: existing.totals.cost + cost,
|
|
6143
6665
|
calls: existing.totals.calls + 1
|
|
6144
6666
|
}
|
|
@@ -6150,98 +6672,45 @@ function resetUsageTotals(baseDir) {
|
|
|
6150
6672
|
const cleared = {
|
|
6151
6673
|
version: 1,
|
|
6152
6674
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6153
|
-
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 }
|
|
6154
6676
|
};
|
|
6155
6677
|
writeAtomically(usagePath(baseDir), JSON.stringify(cleared, null, 2));
|
|
6156
6678
|
return cleared;
|
|
6157
6679
|
}
|
|
6158
6680
|
function formatToolCall(tool, args) {
|
|
6159
6681
|
const a = args && typeof args === "object" ? args : {};
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
case "lit-autosave":
|
|
6168
|
-
return { label: a._summary || "Saving papers", icon: "file" };
|
|
6169
|
-
case "data-analyze":
|
|
6170
|
-
return { label: `Analyze: ${getFileName(a.filePath || "") || "data"}`, icon: "file" };
|
|
6171
|
-
case "convert_to_markdown": {
|
|
6172
|
-
const sourcePath = a.path || a.uri || "";
|
|
6173
|
-
return { label: `Convert: ${getFileName(sourcePath)}`, icon: "file" };
|
|
6174
|
-
}
|
|
6175
|
-
case "artifact-create": {
|
|
6176
|
-
const type = (a.type || "artifact").toLowerCase();
|
|
6177
|
-
const title = (a.title || type).slice(0, 35);
|
|
6178
|
-
return { label: `Create ${type}: ${title}`, icon: "file" };
|
|
6179
|
-
}
|
|
6180
|
-
case "read":
|
|
6181
|
-
return { label: `Read: ${getFileName(a.path || "")}`, icon: "file" };
|
|
6182
|
-
case "write":
|
|
6183
|
-
return { label: `Write: ${getFileName(a.path || "")}`, icon: "file" };
|
|
6184
|
-
case "edit":
|
|
6185
|
-
return { label: `Edit: ${getFileName(a.path || "")}`, icon: "file" };
|
|
6186
|
-
case "bash":
|
|
6187
|
-
return { label: `Run command`, icon: "terminal" };
|
|
6188
|
-
case "glob":
|
|
6189
|
-
return { label: `Search files: ${a.pattern || ""}`, icon: "search" };
|
|
6190
|
-
case "grep":
|
|
6191
|
-
return { label: `Search content: ${(a.pattern || "").slice(0, 30)}`, icon: "search" };
|
|
6192
|
-
case "fetch":
|
|
6193
|
-
return { label: `Fetch: ${(a.url || "").slice(0, 40)}`, icon: "network" };
|
|
6194
|
-
default:
|
|
6195
|
-
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
|
+
};
|
|
6196
6689
|
}
|
|
6690
|
+
return { label: `${tool}`, icon: "tool" };
|
|
6197
6691
|
}
|
|
6198
6692
|
function formatToolResult(tool, result, args) {
|
|
6199
|
-
const r = result && typeof result === "object" ? result : {};
|
|
6200
6693
|
const a = args && typeof args === "object" ? args : {};
|
|
6201
|
-
const
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
if (saved > 0) summary2 += `, saved ${saved}`;
|
|
6211
|
-
return { label: summary2, icon: "search" };
|
|
6212
|
-
}
|
|
6213
|
-
const local = data.localPapersUsed ?? 0;
|
|
6214
|
-
const external = data.externalPapersUsed ?? 0;
|
|
6215
|
-
const savedV1 = data.savedPapers ?? 0;
|
|
6216
|
-
let summary = `Found ${local + external} papers`;
|
|
6217
|
-
if (local > 0) summary += ` (${local} local)`;
|
|
6218
|
-
if (savedV1 > 0) summary += `, saved ${savedV1}`;
|
|
6219
|
-
return { label: summary, icon: "search" };
|
|
6220
|
-
}
|
|
6221
|
-
case "lit-subtopic":
|
|
6222
|
-
return { label: r.data || "Search completed", icon: "search" };
|
|
6223
|
-
case "lit-enrich":
|
|
6224
|
-
return { label: r.data || "Enriched metadata", icon: "search" };
|
|
6225
|
-
case "lit-autosave":
|
|
6226
|
-
return { label: r.data || "Saved papers", icon: "file" };
|
|
6227
|
-
case "convert_to_markdown": {
|
|
6228
|
-
const sourcePath = a.path || a.uri || "";
|
|
6229
|
-
const skill = typeof data.converterSkill === "string" ? data.converterSkill : "";
|
|
6230
|
-
const script = typeof data.converterScript === "string" ? data.converterScript : "";
|
|
6231
|
-
if (skill && script) return { label: `Converted ${getFileName(sourcePath)} via ${skill}/${script}`, icon: "file" };
|
|
6232
|
-
if (skill) return { label: `Converted ${getFileName(sourcePath)} via ${skill}`, icon: "file" };
|
|
6233
|
-
return { label: `Converted ${getFileName(sourcePath)}`, icon: "file" };
|
|
6234
|
-
}
|
|
6235
|
-
case "artifact-create": {
|
|
6236
|
-
const type = data.type || "artifact";
|
|
6237
|
-
const title = data.title || "";
|
|
6238
|
-
return { label: title ? `Created ${type}: ${title.slice(0, 30)}` : `Created ${type}`, icon: "file" };
|
|
6239
|
-
}
|
|
6240
|
-
default: {
|
|
6241
|
-
const success2 = r.success !== false;
|
|
6242
|
-
return { label: success2 ? `${tool} completed` : `${tool} failed`, icon: "tool" };
|
|
6243
|
-
}
|
|
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
|
+
};
|
|
6244
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
|
+
};
|
|
6712
|
+
}
|
|
6713
|
+
return { label: success2 ? `${tool} completed` : `${tool} failed`, icon: "tool", detail: { success: success2 } };
|
|
6245
6714
|
}
|
|
6246
6715
|
const windowStates = /* @__PURE__ */ new Map();
|
|
6247
6716
|
let ipcHandlersRegistered = false;
|
|
@@ -6393,13 +6862,15 @@ async function ensureCoordinator(state, win, model, options) {
|
|
|
6393
6862
|
state.realtimeBuffer.appendChunk(chunk);
|
|
6394
6863
|
safeSend(win, "agent:stream-chunk", chunk);
|
|
6395
6864
|
},
|
|
6396
|
-
onToolCall: (tool, args) => {
|
|
6397
|
-
const
|
|
6398
|
-
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 };
|
|
6399
6869
|
state.realtimeBuffer.pushActivity(event);
|
|
6870
|
+
state.realtimeBuffer.pushToolEvent({ type: "tool-call", tool, toolCallId: id, summary: label, detail });
|
|
6400
6871
|
safeSend(win, "agent:activity", event);
|
|
6401
6872
|
},
|
|
6402
|
-
onToolResult: (tool, result, args) => {
|
|
6873
|
+
onToolResult: (tool, result, args, toolCallId) => {
|
|
6403
6874
|
if (tool.startsWith("todo-") && result && typeof result === "object" && "success" in result) {
|
|
6404
6875
|
const r2 = result;
|
|
6405
6876
|
if (r2.success && r2.item) {
|
|
@@ -6414,16 +6885,16 @@ async function ensureCoordinator(state, win, model, options) {
|
|
|
6414
6885
|
safeSend(win, "agent:file-created", absPath);
|
|
6415
6886
|
}
|
|
6416
6887
|
}
|
|
6417
|
-
if (tool === "
|
|
6888
|
+
if (tool === "convert_document" && result && typeof result === "object" && "success" in result) {
|
|
6418
6889
|
const r2 = result;
|
|
6419
6890
|
if (r2.success && r2.data?.outputFile) {
|
|
6420
6891
|
safeSend(win, "agent:file-created", r2.data.outputFile);
|
|
6421
6892
|
}
|
|
6422
6893
|
}
|
|
6423
|
-
if (tool === "
|
|
6894
|
+
if (tool === "convert_document" && result && typeof result === "object" && "success" in result) {
|
|
6424
6895
|
const r2 = result;
|
|
6425
|
-
if (r2.success && r2.data?.outputFile && args && typeof args === "object" && "
|
|
6426
|
-
const sourcePath = args.
|
|
6896
|
+
if (r2.success && r2.data?.outputFile && args && typeof args === "object" && "source" in args) {
|
|
6897
|
+
const sourcePath = args.source;
|
|
6427
6898
|
const absSourcePath = isAbsolute(sourcePath) ? sourcePath : resolve(runProjectPath, sourcePath);
|
|
6428
6899
|
const absOutputPath = resolve(runProjectPath, r2.data.outputFile);
|
|
6429
6900
|
if (existsSync(absOutputPath)) {
|
|
@@ -6457,11 +6928,26 @@ async function ensureCoordinator(state, win, model, options) {
|
|
|
6457
6928
|
const r = result;
|
|
6458
6929
|
const success2 = r?.success !== false;
|
|
6459
6930
|
const error = !success2 ? r?.error || "Unknown error" : void 0;
|
|
6460
|
-
const
|
|
6461
|
-
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 };
|
|
6462
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
|
+
}
|
|
6463
6945
|
safeSend(win, "agent:activity", actEvent);
|
|
6464
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
|
+
},
|
|
6465
6951
|
// Skill activation tracking
|
|
6466
6952
|
onSkillLoaded: (skillName) => {
|
|
6467
6953
|
safeSend(win, "agent:skill-loaded", skillName);
|
|
@@ -6473,12 +6959,14 @@ async function ensureCoordinator(state, win, model, options) {
|
|
|
6473
6959
|
const promptTokens = usage.input ?? 0;
|
|
6474
6960
|
const completionTokens = usage.output ?? 0;
|
|
6475
6961
|
const cachedTokens = usage.cacheRead ?? 0;
|
|
6962
|
+
const cacheWriteTokens = usage.cacheWrite ?? 0;
|
|
6476
6963
|
const baseDir = join(runProjectPath, PATHS.root);
|
|
6477
|
-
accumulateUsage(baseDir, promptTokens, completionTokens, cachedTokens, rawCost);
|
|
6964
|
+
accumulateUsage(baseDir, promptTokens, completionTokens, cachedTokens, cacheWriteTokens, rawCost);
|
|
6478
6965
|
const usageEvent = {
|
|
6479
6966
|
promptTokens,
|
|
6480
6967
|
completionTokens,
|
|
6481
6968
|
cachedTokens,
|
|
6969
|
+
cacheWriteTokens,
|
|
6482
6970
|
cost: rawCost,
|
|
6483
6971
|
rawCost,
|
|
6484
6972
|
billableCost: rawCost,
|
|
@@ -7104,7 +7592,11 @@ function destroyAllTerminals() {
|
|
|
7104
7592
|
terminals.delete(id);
|
|
7105
7593
|
}
|
|
7106
7594
|
}
|
|
7595
|
+
setMaxListeners(20);
|
|
7107
7596
|
loadApiKeysFromConfig();
|
|
7597
|
+
if (!process.env.PI_CACHE_RETENTION) {
|
|
7598
|
+
process.env.PI_CACHE_RETENTION = "long";
|
|
7599
|
+
}
|
|
7108
7600
|
if (process.platform === "darwin" && !is.dev) {
|
|
7109
7601
|
try {
|
|
7110
7602
|
const shellPath = process.env.SHELL || "/bin/zsh";
|