@ridit/lens 0.2.0 → 0.2.2
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/dist/index.mjs +2075 -1493
- package/package.json +1 -1
- package/src/components/chat/ChatMessage.tsx +15 -1
- package/src/components/chat/ChatOverlays.tsx +81 -47
- package/src/components/chat/ChatRunner.tsx +553 -369
- package/src/index.tsx +3 -0
- package/src/prompts/fewshot.ts +377 -0
- package/src/prompts/index.ts +2 -0
- package/src/prompts/system.ts +167 -0
- package/src/tools/files.ts +261 -0
- package/src/tools/index.ts +13 -0
- package/src/tools/pdf.ts +106 -0
- package/src/tools/shell.ts +96 -0
- package/src/tools/web.ts +216 -0
- package/src/utils/chat.ts +114 -1463
- package/src/utils/chatHistory.ts +121 -0
- package/src/utils/files.ts +1 -0
- package/src/utils/memory.ts +137 -0
- package/src/utils/tools/builtins.ts +324 -0
- package/src/utils/tools/registry.ts +119 -0
- package/src/utils/history.ts +0 -86
package/dist/index.mjs
CHANGED
|
@@ -44302,6 +44302,8 @@ function fetchFileTree(repoPath) {
|
|
|
44302
44302
|
});
|
|
44303
44303
|
}
|
|
44304
44304
|
function readImportantFiles(repoPath, files) {
|
|
44305
|
+
if (files.length > 100)
|
|
44306
|
+
return [];
|
|
44305
44307
|
return files.filter(isImportantFile).flatMap((filePath) => {
|
|
44306
44308
|
try {
|
|
44307
44309
|
const content = readFileSync2(path2.join(repoPath, filePath), "utf-8");
|
|
@@ -48630,15 +48632,16 @@ var TaskCommand = ({
|
|
|
48630
48632
|
};
|
|
48631
48633
|
|
|
48632
48634
|
// src/commands/chat.tsx
|
|
48633
|
-
import { existsSync as
|
|
48634
|
-
import
|
|
48635
|
+
import { existsSync as existsSync14 } from "fs";
|
|
48636
|
+
import path19 from "path";
|
|
48635
48637
|
|
|
48636
48638
|
// src/components/chat/ChatRunner.tsx
|
|
48637
48639
|
var import_react48 = __toESM(require_react(), 1);
|
|
48638
|
-
|
|
48639
|
-
import
|
|
48640
|
+
var import_react49 = __toESM(require_react(), 1);
|
|
48641
|
+
import path18 from "path";
|
|
48642
|
+
import os9 from "os";
|
|
48640
48643
|
|
|
48641
|
-
// src/
|
|
48644
|
+
// src/tools/files.ts
|
|
48642
48645
|
import path14 from "path";
|
|
48643
48646
|
import {
|
|
48644
48647
|
existsSync as existsSync10,
|
|
@@ -48648,576 +48651,1009 @@ import {
|
|
|
48648
48651
|
statSync as statSync4,
|
|
48649
48652
|
writeFileSync as writeFileSync6
|
|
48650
48653
|
} from "fs";
|
|
48651
|
-
|
|
48652
|
-
|
|
48653
|
-
|
|
48654
|
-
|
|
48655
|
-
|
|
48656
|
-
|
|
48657
|
-
|
|
48658
|
-
|
|
48659
|
-
|
|
48660
|
-
|
|
48661
|
-
|
|
48662
|
-
|
|
48663
|
-
|
|
48664
|
-
|
|
48665
|
-
|
|
48666
|
-
|
|
48667
|
-
|
|
48668
|
-
|
|
48669
|
-
|
|
48670
|
-
|
|
48671
|
-
|
|
48672
|
-
|
|
48673
|
-
|
|
48674
|
-
|
|
48675
|
-
|
|
48676
|
-
|
|
48677
|
-
|
|
48678
|
-
{
|
|
48679
|
-
|
|
48680
|
-
|
|
48681
|
-
|
|
48682
|
-
|
|
48683
|
-
|
|
48684
|
-
|
|
48685
|
-
|
|
48686
|
-
|
|
48687
|
-
|
|
48688
|
-
|
|
48689
|
-
|
|
48690
|
-
|
|
48691
|
-
|
|
48692
|
-
|
|
48693
|
-
|
|
48694
|
-
|
|
48695
|
-
|
|
48696
|
-
|
|
48697
|
-
|
|
48698
|
-
|
|
48699
|
-
|
|
48700
|
-
|
|
48701
|
-
|
|
48702
|
-
|
|
48703
|
-
|
|
48704
|
-
|
|
48705
|
-
|
|
48706
|
-
|
|
48707
|
-
|
|
48708
|
-
<search>how to use React useEffect cleanup function</search>
|
|
48709
|
-
|
|
48710
|
-
### 12. clone — clone a GitHub repo so you can explore and discuss it
|
|
48711
|
-
<clone>https://github.com/owner/repo</clone>
|
|
48654
|
+
var SKIP_DIRS4 = new Set([
|
|
48655
|
+
"node_modules",
|
|
48656
|
+
".git",
|
|
48657
|
+
"dist",
|
|
48658
|
+
"build",
|
|
48659
|
+
".next",
|
|
48660
|
+
"out",
|
|
48661
|
+
"coverage",
|
|
48662
|
+
"__pycache__",
|
|
48663
|
+
".venv",
|
|
48664
|
+
"venv"
|
|
48665
|
+
]);
|
|
48666
|
+
function walkDir3(dir, base2 = dir, results = []) {
|
|
48667
|
+
let entries;
|
|
48668
|
+
try {
|
|
48669
|
+
entries = readdirSync3(dir, { encoding: "utf-8" });
|
|
48670
|
+
} catch {
|
|
48671
|
+
return results;
|
|
48672
|
+
}
|
|
48673
|
+
for (const entry of entries) {
|
|
48674
|
+
if (results.length >= 100)
|
|
48675
|
+
return results;
|
|
48676
|
+
if (SKIP_DIRS4.has(entry))
|
|
48677
|
+
continue;
|
|
48678
|
+
const full = path14.join(dir, entry);
|
|
48679
|
+
const rel = path14.relative(base2, full).replace(/\\/g, "/");
|
|
48680
|
+
let isDir = false;
|
|
48681
|
+
try {
|
|
48682
|
+
isDir = statSync4(full).isDirectory();
|
|
48683
|
+
} catch {
|
|
48684
|
+
continue;
|
|
48685
|
+
}
|
|
48686
|
+
if (isDir)
|
|
48687
|
+
walkDir3(full, base2, results);
|
|
48688
|
+
else
|
|
48689
|
+
results.push(rel);
|
|
48690
|
+
}
|
|
48691
|
+
return results;
|
|
48692
|
+
}
|
|
48693
|
+
function applyPatches3(repoPath, patches) {
|
|
48694
|
+
for (const patch of patches) {
|
|
48695
|
+
const fullPath = path14.join(repoPath, patch.path);
|
|
48696
|
+
const dir = path14.dirname(fullPath);
|
|
48697
|
+
if (!existsSync10(dir))
|
|
48698
|
+
mkdirSync4(dir, { recursive: true });
|
|
48699
|
+
writeFileSync6(fullPath, patch.content, "utf-8");
|
|
48700
|
+
}
|
|
48701
|
+
}
|
|
48702
|
+
function readFile(filePath, repoPath) {
|
|
48703
|
+
const candidates = path14.isAbsolute(filePath) ? [filePath] : [filePath, path14.join(repoPath, filePath)];
|
|
48704
|
+
for (const candidate of candidates) {
|
|
48705
|
+
if (existsSync10(candidate)) {
|
|
48706
|
+
try {
|
|
48707
|
+
const content = readFileSync10(candidate, "utf-8");
|
|
48708
|
+
const lines = content.split(`
|
|
48709
|
+
`).length;
|
|
48710
|
+
return `File: ${candidate} (${lines} lines)
|
|
48712
48711
|
|
|
48713
|
-
|
|
48714
|
-
<changes>
|
|
48715
|
-
{"summary": "what changed and why", "patches": [{"path": "src/foo.ts", "content": "COMPLETE file content", "isNew": false}]}
|
|
48716
|
-
</changes>
|
|
48712
|
+
${content.slice(0, 8000)}${content.length > 8000 ? `
|
|
48717
48713
|
|
|
48718
|
-
|
|
48714
|
+
… (truncated)` : ""}`;
|
|
48715
|
+
} catch (err) {
|
|
48716
|
+
return `Error reading file: ${err instanceof Error ? err.message : String(err)}`;
|
|
48717
|
+
}
|
|
48718
|
+
}
|
|
48719
|
+
}
|
|
48720
|
+
return `File not found: ${filePath}. If reading from a cloned repo, use the full absolute path e.g. C:\\Users\\...\\repo\\file.ts`;
|
|
48721
|
+
}
|
|
48722
|
+
function readFolder(folderPath, repoPath) {
|
|
48723
|
+
const sanitized = folderPath.replace(/^(ls|dir|find|tree|cat|read|ls -la?|ls -al?)\s+/i, "").trim();
|
|
48724
|
+
const candidates = path14.isAbsolute(sanitized) ? [sanitized] : [sanitized, path14.join(repoPath, sanitized)];
|
|
48725
|
+
for (const candidate of candidates) {
|
|
48726
|
+
if (!existsSync10(candidate))
|
|
48727
|
+
continue;
|
|
48728
|
+
let stat;
|
|
48729
|
+
try {
|
|
48730
|
+
stat = statSync4(candidate);
|
|
48731
|
+
} catch {
|
|
48732
|
+
continue;
|
|
48733
|
+
}
|
|
48734
|
+
if (!stat.isDirectory()) {
|
|
48735
|
+
return `Not a directory: ${candidate}. Use read-file to read a file.`;
|
|
48736
|
+
}
|
|
48737
|
+
let entries;
|
|
48738
|
+
try {
|
|
48739
|
+
entries = readdirSync3(candidate, { encoding: "utf-8" });
|
|
48740
|
+
} catch (err) {
|
|
48741
|
+
return `Error reading folder: ${err instanceof Error ? err.message : String(err)}`;
|
|
48742
|
+
}
|
|
48743
|
+
const files = [];
|
|
48744
|
+
const subfolders = [];
|
|
48745
|
+
for (const entry of entries) {
|
|
48746
|
+
if (entry.startsWith(".") && entry !== ".env")
|
|
48747
|
+
continue;
|
|
48748
|
+
const full = path14.join(candidate, entry);
|
|
48749
|
+
try {
|
|
48750
|
+
if (statSync4(full).isDirectory())
|
|
48751
|
+
subfolders.push(`${entry}/`);
|
|
48752
|
+
else
|
|
48753
|
+
files.push(entry);
|
|
48754
|
+
} catch {}
|
|
48755
|
+
}
|
|
48756
|
+
const total = files.length + subfolders.length;
|
|
48757
|
+
const lines = [`Folder: ${candidate} (${total} entries)`, ""];
|
|
48758
|
+
if (files.length > 0) {
|
|
48759
|
+
lines.push("Files:");
|
|
48760
|
+
files.forEach((f) => lines.push(` ${f}`));
|
|
48761
|
+
}
|
|
48762
|
+
if (subfolders.length > 0) {
|
|
48763
|
+
if (files.length > 0)
|
|
48764
|
+
lines.push("");
|
|
48765
|
+
lines.push("Subfolders:");
|
|
48766
|
+
subfolders.forEach((d) => lines.push(` ${d}`));
|
|
48767
|
+
}
|
|
48768
|
+
if (total === 0)
|
|
48769
|
+
lines.push("(empty folder)");
|
|
48770
|
+
return lines.join(`
|
|
48771
|
+
`);
|
|
48772
|
+
}
|
|
48773
|
+
return `Folder not found: ${sanitized}`;
|
|
48774
|
+
}
|
|
48775
|
+
function grepFiles(pattern, glob, repoPath) {
|
|
48776
|
+
let regex2;
|
|
48777
|
+
try {
|
|
48778
|
+
regex2 = new RegExp(pattern, "i");
|
|
48779
|
+
} catch {
|
|
48780
|
+
regex2 = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i");
|
|
48781
|
+
}
|
|
48782
|
+
const globToFilter = (g) => {
|
|
48783
|
+
const cleaned = g.replace(/^\*\*\//, "");
|
|
48784
|
+
const parts = cleaned.split("/");
|
|
48785
|
+
const ext = parts[parts.length - 1];
|
|
48786
|
+
const prefix = parts.slice(0, -1).join("/");
|
|
48787
|
+
return (rel) => {
|
|
48788
|
+
if (ext?.startsWith("*.")) {
|
|
48789
|
+
if (!rel.endsWith(ext.slice(1)))
|
|
48790
|
+
return false;
|
|
48791
|
+
} else if (ext && !ext.includes("*")) {
|
|
48792
|
+
if (!rel.endsWith(ext))
|
|
48793
|
+
return false;
|
|
48794
|
+
}
|
|
48795
|
+
if (prefix && !prefix.includes("*")) {
|
|
48796
|
+
if (!rel.startsWith(prefix))
|
|
48797
|
+
return false;
|
|
48798
|
+
}
|
|
48799
|
+
return true;
|
|
48800
|
+
};
|
|
48801
|
+
};
|
|
48802
|
+
const filter2 = globToFilter(glob);
|
|
48803
|
+
const allFiles = walkDir3(repoPath);
|
|
48804
|
+
const matchedFiles = allFiles.filter(filter2);
|
|
48805
|
+
if (matchedFiles.length === 0)
|
|
48806
|
+
return `No files matched glob: ${glob}`;
|
|
48807
|
+
const results = [];
|
|
48808
|
+
let totalMatches = 0;
|
|
48809
|
+
for (const relPath of matchedFiles) {
|
|
48810
|
+
const fullPath = path14.join(repoPath, relPath);
|
|
48811
|
+
let content;
|
|
48812
|
+
try {
|
|
48813
|
+
content = readFileSync10(fullPath, "utf-8");
|
|
48814
|
+
} catch {
|
|
48815
|
+
continue;
|
|
48816
|
+
}
|
|
48817
|
+
const fileLines = content.split(`
|
|
48818
|
+
`);
|
|
48819
|
+
const fileMatches = [];
|
|
48820
|
+
fileLines.forEach((line, i) => {
|
|
48821
|
+
if (regex2.test(line)) {
|
|
48822
|
+
fileMatches.push(` ${i + 1}: ${line.trimEnd()}`);
|
|
48823
|
+
totalMatches++;
|
|
48824
|
+
}
|
|
48825
|
+
});
|
|
48826
|
+
if (fileMatches.length > 0)
|
|
48827
|
+
results.push(`${relPath}
|
|
48828
|
+
${fileMatches.join(`
|
|
48829
|
+
`)}`);
|
|
48830
|
+
if (totalMatches >= 200) {
|
|
48831
|
+
results.push("(truncated — too many matches)");
|
|
48832
|
+
break;
|
|
48833
|
+
}
|
|
48834
|
+
}
|
|
48835
|
+
if (results.length === 0)
|
|
48836
|
+
return `No matches for /${pattern}/ in ${matchedFiles.length} file(s) matching ${glob}`;
|
|
48837
|
+
return `grep /${pattern}/ ${glob} — ${totalMatches} match(es) in ${results.length} file(s)
|
|
48719
48838
|
|
|
48720
|
-
|
|
48721
|
-
2. ONE tool per response — emit the tag, then stop completely
|
|
48722
|
-
3. After the user approves and you get the result, continue your analysis in the next response
|
|
48723
|
-
4. NEVER print a URL, command, filename, or JSON blob as plain text when you should be using a tool
|
|
48724
|
-
5. NEVER say "I'll fetch" / "run this command" / "here's the write-file" — just emit the tag
|
|
48725
|
-
6. NEVER use shell to run git clone — always use the clone tag instead
|
|
48726
|
-
7. NEVER use shell to list files or folders (no ls, dir, find, git ls-files, tree) — ALWAYS use read-folder instead
|
|
48727
|
-
8. NEVER use shell to read a file (no cat, type, Get-Content) — ALWAYS use read-file instead
|
|
48728
|
-
9. NEVER use shell grep, findstr, or Select-String to search file contents — ALWAYS use grep instead
|
|
48729
|
-
10. shell is ONLY for running code, installing packages, building, testing — not for filesystem inspection
|
|
48730
|
-
11. write-file content field must be the COMPLETE file content, never empty or placeholder
|
|
48731
|
-
12. After a write-file succeeds, do NOT repeat it — trust the result and move on
|
|
48732
|
-
13. After a write-file succeeds, tell the user it is done immediately — do NOT auto-read the file back to verify
|
|
48733
|
-
14. NEVER apologize and redo a tool call you already made — if write-file or shell ran and returned a result, it worked, do not run it again
|
|
48734
|
-
15. NEVER say "I made a mistake" and repeat the same tool — one attempt is enough, trust the output
|
|
48735
|
-
16. NEVER second-guess yourself mid-response — commit to your answer
|
|
48736
|
-
17. If a read-folder or read-file returns "not found", accept it and move on — do NOT retry the same path
|
|
48737
|
-
18. If you have already retrieved a result for a path in this conversation, do NOT request it again — use the result you already have
|
|
48738
|
-
19. Every shell command runs from the repo root — \`cd\` has NO persistent effect. NEVER use \`cd\` alone. Use full paths or combine with && e.g. \`cd list && bun run index.ts\`
|
|
48739
|
-
20. write-file paths are relative to the repo root — if creating files in a subfolder write the full relative path e.g. \`list/src/index.tsx\` NOT \`src/index.tsx\`
|
|
48740
|
-
21. When scaffolding a new project in a subfolder, ALL write-file paths must start with that subfolder name e.g. \`list/package.json\`, \`list/src/index.tsx\`
|
|
48741
|
-
22. For JSX/TSX files always use \`.tsx\` extension and include \`/** @jsxImportSource react */\` or ensure tsconfig has jsx set — bun needs this to parse JSX
|
|
48742
|
-
23. When explaining how to use a tool in text, use [tag] bracket notation or a fenced code block — NEVER emit a real XML tool tag as part of an explanation or example
|
|
48743
|
-
24. NEVER chain tool calls unless the user's request explicitly requires multiple steps
|
|
48744
|
-
25. NEVER read files, list folders, or run tools that were not asked for in the current user message
|
|
48745
|
-
26. NEVER use markdown formatting in plain text responses — no **bold**, no *italics*, no # headings, no bullet points with -, *, or +, no numbered lists, no backtick inline code. Write in plain prose. Only use fenced \`\`\` code blocks when showing actual code.
|
|
48839
|
+
${results.join(`
|
|
48746
48840
|
|
|
48747
|
-
|
|
48748
|
-
|
|
48749
|
-
|
|
48841
|
+
`)}`;
|
|
48842
|
+
}
|
|
48843
|
+
function writeFile(filePath, content, repoPath) {
|
|
48844
|
+
const fullPath = path14.isAbsolute(filePath) ? filePath : path14.join(repoPath, filePath);
|
|
48845
|
+
try {
|
|
48846
|
+
const dir = path14.dirname(fullPath);
|
|
48847
|
+
if (!existsSync10(dir))
|
|
48848
|
+
mkdirSync4(dir, { recursive: true });
|
|
48849
|
+
writeFileSync6(fullPath, content, "utf-8");
|
|
48850
|
+
const lines = content.split(`
|
|
48851
|
+
`).length;
|
|
48852
|
+
return `Written: ${fullPath} (${lines} lines, ${content.length} bytes)`;
|
|
48853
|
+
} catch (err) {
|
|
48854
|
+
return `Error writing file: ${err instanceof Error ? err.message : String(err)}`;
|
|
48855
|
+
}
|
|
48856
|
+
}
|
|
48857
|
+
function deleteFile(filePath, repoPath) {
|
|
48858
|
+
const fullPath = path14.isAbsolute(filePath) ? filePath : path14.join(repoPath, filePath);
|
|
48859
|
+
try {
|
|
48860
|
+
if (!existsSync10(fullPath))
|
|
48861
|
+
return `File not found: ${fullPath}`;
|
|
48862
|
+
const { unlinkSync } = __require("fs");
|
|
48863
|
+
unlinkSync(fullPath);
|
|
48864
|
+
return `Deleted: ${fullPath}`;
|
|
48865
|
+
} catch (err) {
|
|
48866
|
+
return `Error deleting file: ${err instanceof Error ? err.message : String(err)}`;
|
|
48867
|
+
}
|
|
48868
|
+
}
|
|
48869
|
+
function deleteFolder(folderPath, repoPath) {
|
|
48870
|
+
const fullPath = path14.isAbsolute(folderPath) ? folderPath : path14.join(repoPath, folderPath);
|
|
48871
|
+
try {
|
|
48872
|
+
if (!existsSync10(fullPath))
|
|
48873
|
+
return `Folder not found: ${fullPath}`;
|
|
48874
|
+
const { rmSync } = __require("fs");
|
|
48875
|
+
rmSync(fullPath, { recursive: true, force: true });
|
|
48876
|
+
return `Deleted folder: ${fullPath}`;
|
|
48877
|
+
} catch (err) {
|
|
48878
|
+
return `Error deleting folder: ${err instanceof Error ? err.message : String(err)}`;
|
|
48879
|
+
}
|
|
48880
|
+
}
|
|
48881
|
+
// src/tools/shell.ts
|
|
48882
|
+
async function runShell(command, cwd2) {
|
|
48883
|
+
return new Promise((resolve) => {
|
|
48884
|
+
const { spawn: spawn2 } = __require("child_process");
|
|
48885
|
+
const isWin = process.platform === "win32";
|
|
48886
|
+
const shell = isWin ? "cmd.exe" : "/bin/sh";
|
|
48887
|
+
const shellFlag = isWin ? "/c" : "-c";
|
|
48888
|
+
const proc = spawn2(shell, [shellFlag, command], {
|
|
48889
|
+
cwd: cwd2,
|
|
48890
|
+
env: process.env,
|
|
48891
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
48892
|
+
});
|
|
48893
|
+
const chunks = [];
|
|
48894
|
+
const errChunks = [];
|
|
48895
|
+
proc.stdout.on("data", (d) => chunks.push(d));
|
|
48896
|
+
proc.stderr.on("data", (d) => errChunks.push(d));
|
|
48897
|
+
const killTimer = setTimeout(() => {
|
|
48898
|
+
proc.kill();
|
|
48899
|
+
resolve("(command timed out after 5 minutes)");
|
|
48900
|
+
}, 5 * 60 * 1000);
|
|
48901
|
+
proc.on("close", (code) => {
|
|
48902
|
+
clearTimeout(killTimer);
|
|
48903
|
+
const stdout = Buffer.concat(chunks).toString("utf-8").trim();
|
|
48904
|
+
const stderr = Buffer.concat(errChunks).toString("utf-8").trim();
|
|
48905
|
+
const combined = [stdout, stderr].filter(Boolean).join(`
|
|
48906
|
+
`);
|
|
48907
|
+
resolve(combined || (code === 0 ? "(no output)" : `exit code ${code}`));
|
|
48908
|
+
});
|
|
48909
|
+
proc.on("error", (err) => {
|
|
48910
|
+
clearTimeout(killTimer);
|
|
48911
|
+
resolve(`Error: ${err.message}`);
|
|
48912
|
+
});
|
|
48913
|
+
});
|
|
48914
|
+
}
|
|
48915
|
+
function openUrl(url) {
|
|
48916
|
+
try {
|
|
48917
|
+
const { execSync: exec5 } = __require("child_process");
|
|
48918
|
+
const platform2 = process.platform;
|
|
48919
|
+
if (platform2 === "win32") {
|
|
48920
|
+
exec5(`start "" "${url}"`, { stdio: "ignore" });
|
|
48921
|
+
} else if (platform2 === "darwin") {
|
|
48922
|
+
exec5(`open "${url}"`, { stdio: "ignore" });
|
|
48923
|
+
} else {
|
|
48924
|
+
exec5(`xdg-open "${url}"`, { stdio: "ignore" });
|
|
48925
|
+
}
|
|
48926
|
+
return `Opened: ${url}`;
|
|
48927
|
+
} catch (err) {
|
|
48928
|
+
return `Error opening URL: ${err instanceof Error ? err.message : String(err)}`;
|
|
48929
|
+
}
|
|
48930
|
+
}
|
|
48931
|
+
// src/tools/web.ts
|
|
48932
|
+
function stripTags(html) {
|
|
48933
|
+
return html.replace(/<[^>]+>/g, " ").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/&#\d+;/g, " ").replace(/\s+/g, " ").trim();
|
|
48934
|
+
}
|
|
48935
|
+
function extractTables(html) {
|
|
48936
|
+
const tables = [];
|
|
48937
|
+
const tableRe = /<table[\s\S]*?<\/table>/gi;
|
|
48938
|
+
let tMatch;
|
|
48939
|
+
while ((tMatch = tableRe.exec(html)) !== null) {
|
|
48940
|
+
const tableHtml = tMatch[0];
|
|
48941
|
+
const rows = [];
|
|
48942
|
+
const rowRe = /<tr[\s\S]*?<\/tr>/gi;
|
|
48943
|
+
let rMatch;
|
|
48944
|
+
while ((rMatch = rowRe.exec(tableHtml)) !== null) {
|
|
48945
|
+
const cells = [];
|
|
48946
|
+
const cellRe = /<t[dh][^>]*>([\s\S]*?)<\/t[dh]>/gi;
|
|
48947
|
+
let cMatch;
|
|
48948
|
+
while ((cMatch = cellRe.exec(rMatch[0])) !== null) {
|
|
48949
|
+
cells.push(stripTags(cMatch[1] ?? ""));
|
|
48950
|
+
}
|
|
48951
|
+
if (cells.length > 0)
|
|
48952
|
+
rows.push(cells);
|
|
48953
|
+
}
|
|
48954
|
+
if (rows.length < 2)
|
|
48955
|
+
continue;
|
|
48956
|
+
const cols = Math.max(...rows.map((r) => r.length));
|
|
48957
|
+
const padded = rows.map((r) => {
|
|
48958
|
+
while (r.length < cols)
|
|
48959
|
+
r.push("");
|
|
48960
|
+
return r;
|
|
48961
|
+
});
|
|
48962
|
+
const widths = Array.from({ length: cols }, (_, ci) => Math.max(...padded.map((r) => (r[ci] ?? "").length), 3));
|
|
48963
|
+
const fmt = (r) => r.map((c, ci) => c.padEnd(widths[ci] ?? 0)).join(" | ");
|
|
48964
|
+
const header = fmt(padded[0]);
|
|
48965
|
+
const sep = widths.map((w) => "-".repeat(w)).join("-|-");
|
|
48966
|
+
const body = padded.slice(1).map(fmt).join(`
|
|
48967
|
+
`);
|
|
48968
|
+
tables.push(`${header}
|
|
48969
|
+
${sep}
|
|
48970
|
+
${body}`);
|
|
48971
|
+
}
|
|
48972
|
+
return tables.length > 0 ? `=== TABLES (${tables.length}) ===
|
|
48750
48973
|
|
|
48751
|
-
|
|
48752
|
-
1. ALWAYS use read-file on the exact file you plan to change FIRST
|
|
48753
|
-
2. Study the full current content — understand every import, every export, every type, every existing feature
|
|
48754
|
-
3. Your changes patch MUST preserve ALL existing functionality — do not remove or rewrite things that were not part of the request
|
|
48755
|
-
4. If you are unsure what other files import from the file you are editing, use read-folder on the parent directory first to see what exists nearby, then read-file the relevant ones
|
|
48974
|
+
${tables.join(`
|
|
48756
48975
|
|
|
48757
|
-
|
|
48758
|
-
1. Use read-folder on the relevant directory to see what files exist
|
|
48759
|
-
2. Use read-file on each file you plan to touch
|
|
48760
|
-
3. Only then emit a changes tag — with patches that are surgical additions, not wholesale rewrites
|
|
48976
|
+
---
|
|
48761
48977
|
|
|
48762
|
-
|
|
48763
|
-
|
|
48764
|
-
|
|
48765
|
-
|
|
48978
|
+
`)}` : "";
|
|
48979
|
+
}
|
|
48980
|
+
function extractLists(html) {
|
|
48981
|
+
const lists = [];
|
|
48982
|
+
const listRe = /<[ou]l[\s\S]*?<\/[ou]l>/gi;
|
|
48983
|
+
let lMatch;
|
|
48984
|
+
while ((lMatch = listRe.exec(html)) !== null) {
|
|
48985
|
+
const items = [];
|
|
48986
|
+
const itemRe = /<li[^>]*>([\s\S]*?)<\/li>/gi;
|
|
48987
|
+
let iMatch;
|
|
48988
|
+
while ((iMatch = itemRe.exec(lMatch[0])) !== null) {
|
|
48989
|
+
const text = stripTags(iMatch[1] ?? "");
|
|
48990
|
+
if (text.length > 2)
|
|
48991
|
+
items.push(`• ${text}`);
|
|
48992
|
+
}
|
|
48993
|
+
if (items.length > 1)
|
|
48994
|
+
lists.push(items.join(`
|
|
48995
|
+
`));
|
|
48996
|
+
}
|
|
48997
|
+
return lists.length > 0 ? `=== LISTS ===
|
|
48766
48998
|
|
|
48767
|
-
|
|
48768
|
-
- Before editing files in an unfamiliar directory — list it first to understand the structure
|
|
48769
|
-
- When a feature spans multiple files and you are not sure what exists
|
|
48770
|
-
- When the user asks you to explore or explain a part of the codebase
|
|
48771
|
-
|
|
48772
|
-
## SCAFFOLDING A NEW PROJECT (follow this exactly)
|
|
48773
|
-
|
|
48774
|
-
When the user asks to create a new CLI/app in a subfolder (e.g. "make a todo app called list"):
|
|
48775
|
-
1. Create all files first using write-file with paths like \`list/package.json\`, \`list/src/index.tsx\`
|
|
48776
|
-
2. Then run \`cd list && bun install\` (or npm/pnpm) in one shell command
|
|
48777
|
-
3. Then run the project with \`cd list && bun run index.ts\` or whatever the entry point is
|
|
48778
|
-
4. NEVER run \`bun init\` — it is interactive and will hang. Create package.json manually with write-file instead
|
|
48779
|
-
5. TSX files need either tsconfig.json with \`"jsx": "react-jsx"\` or \`/** @jsxImportSource react */\` at the top
|
|
48780
|
-
|
|
48781
|
-
## FETCH → WRITE FLOW (follow this exactly when saving fetched data)
|
|
48782
|
-
|
|
48783
|
-
1. fetch the URL
|
|
48784
|
-
2. Analyze the result — count the rows, identify columns, check completeness
|
|
48785
|
-
3. Tell the user what you found: "Found X rows with columns: A, B, C. Writing now."
|
|
48786
|
-
4. emit write-file with correctly structured, complete content
|
|
48787
|
-
5. After write-file confirms success, emit read-file to verify
|
|
48788
|
-
6. Only after read-file confirms content is correct, tell the user it is done
|
|
48789
|
-
|
|
48790
|
-
## WHEN TO USE TOOLS
|
|
48791
|
-
|
|
48792
|
-
- User shares any URL → fetch it immediately
|
|
48793
|
-
- User asks to run anything → shell it immediately
|
|
48794
|
-
- User asks to open a link, open a URL, or visit a website → open-url it immediately, do NOT use fetch
|
|
48795
|
-
- User asks to delete a file → delete-file it immediately (requires approval)
|
|
48796
|
-
- User asks to delete a folder or directory → delete-folder it immediately (requires approval)
|
|
48797
|
-
- User asks to search for a pattern in files, find usages, find where something is defined → grep it immediately, NEVER use shell grep/findstr/Select-String
|
|
48798
|
-
- User asks to read a file → read-file it immediately, NEVER use shell cat/type
|
|
48799
|
-
- User asks what files are in a folder, or to explore/list a directory → read-folder it immediately, NEVER use shell ls/dir/find/git ls-files
|
|
48800
|
-
- User asks to explore a folder or directory → read-folder it immediately
|
|
48801
|
-
- User asks to save/create/write a file → write-file it immediately, then read-file to verify
|
|
48802
|
-
- User asks to modify/edit/add to an existing file → read-file it FIRST, then emit changes
|
|
48803
|
-
- User shares a GitHub URL and wants to clone/explore/discuss it → use clone immediately, NEVER use shell git clone
|
|
48804
|
-
- After clone succeeds, you will see context about the clone in the conversation. Wait for the user to ask a specific question before using any tools. Do NOT auto-read files, do NOT emit any tool tags until the user asks.
|
|
48805
|
-
- You are unsure about an API, library, error, concept, or piece of code → search it immediately
|
|
48806
|
-
- User asks about something recent or that you might not know → search it immediately
|
|
48807
|
-
- You are about to say "I'm not sure" or "I don't know" → search instead of guessing
|
|
48808
|
-
|
|
48809
|
-
## shell IS ONLY FOR:
|
|
48810
|
-
- Running code: \`node script.js\`, \`bun run dev\`, \`python main.py\`
|
|
48811
|
-
- Installing packages: \`npm install\`, \`pip install\`
|
|
48812
|
-
- Building/testing: \`npm run build\`, \`bun test\`
|
|
48813
|
-
- Git operations other than clone: \`git status\`, \`git log\`, \`git diff\`
|
|
48814
|
-
- Anything that EXECUTES — not reads or lists
|
|
48999
|
+
${lists.slice(0, 5).join(`
|
|
48815
49000
|
|
|
48816
|
-
|
|
49001
|
+
`)}` : "";
|
|
49002
|
+
}
|
|
49003
|
+
async function fetchUrl(url) {
|
|
49004
|
+
const res = await fetch(url, {
|
|
49005
|
+
headers: {
|
|
49006
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
49007
|
+
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
49008
|
+
"Accept-Language": "en-US,en;q=0.5"
|
|
49009
|
+
},
|
|
49010
|
+
signal: AbortSignal.timeout(15000)
|
|
49011
|
+
});
|
|
49012
|
+
if (!res.ok)
|
|
49013
|
+
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
49014
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
49015
|
+
if (contentType.includes("application/json")) {
|
|
49016
|
+
const json = await res.json();
|
|
49017
|
+
return JSON.stringify(json, null, 2).slice(0, 8000);
|
|
49018
|
+
}
|
|
49019
|
+
const html = await res.text();
|
|
49020
|
+
const titleMatch = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
49021
|
+
const title = titleMatch ? stripTags(titleMatch[1]) : "No title";
|
|
49022
|
+
const tables = extractTables(html);
|
|
49023
|
+
const lists = extractLists(html);
|
|
49024
|
+
const bodyText = stripTags(html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<nav[\s\S]*?<\/nav>/gi, "").replace(/<footer[\s\S]*?<\/footer>/gi, "").replace(/<header[\s\S]*?<\/header>/gi, "")).replace(/\s{3,}/g, `
|
|
48817
49025
|
|
|
48818
|
-
|
|
49026
|
+
`).slice(0, 3000);
|
|
49027
|
+
const parts = [`PAGE: ${title}`, `URL: ${url}`];
|
|
49028
|
+
if (tables)
|
|
49029
|
+
parts.push(tables);
|
|
49030
|
+
if (lists)
|
|
49031
|
+
parts.push(lists);
|
|
49032
|
+
parts.push(`=== TEXT ===
|
|
49033
|
+
${bodyText}`);
|
|
49034
|
+
return parts.join(`
|
|
48819
49035
|
|
|
48820
|
-
|
|
49036
|
+
`);
|
|
48821
49037
|
}
|
|
48822
|
-
|
|
48823
|
-
|
|
48824
|
-
|
|
48825
|
-
|
|
48826
|
-
|
|
48827
|
-
|
|
48828
|
-
|
|
48829
|
-
|
|
48830
|
-
|
|
48831
|
-
|
|
48832
|
-
|
|
48833
|
-
|
|
48834
|
-
|
|
48835
|
-
|
|
49038
|
+
async function searchWeb(query) {
|
|
49039
|
+
const encoded = encodeURIComponent(query);
|
|
49040
|
+
const ddgUrl = `https://api.duckduckgo.com/?q=${encoded}&format=json&no_html=1&skip_disambig=1`;
|
|
49041
|
+
try {
|
|
49042
|
+
const res = await fetch(ddgUrl, {
|
|
49043
|
+
headers: { "User-Agent": "Lens/1.0" },
|
|
49044
|
+
signal: AbortSignal.timeout(8000)
|
|
49045
|
+
});
|
|
49046
|
+
if (res.ok) {
|
|
49047
|
+
const data = await res.json();
|
|
49048
|
+
const parts = [`Search: ${query}`];
|
|
49049
|
+
if (data.Answer)
|
|
49050
|
+
parts.push(`Answer: ${data.Answer}`);
|
|
49051
|
+
if (data.AbstractText) {
|
|
49052
|
+
parts.push(`Summary: ${data.AbstractText}`);
|
|
49053
|
+
if (data.AbstractURL)
|
|
49054
|
+
parts.push(`Source: ${data.AbstractURL}`);
|
|
49055
|
+
}
|
|
49056
|
+
if (data.Infobox?.content?.length) {
|
|
49057
|
+
const fields = data.Infobox.content.slice(0, 8).map((f) => ` ${f.label}: ${f.value}`).join(`
|
|
49058
|
+
`);
|
|
49059
|
+
parts.push(`Info:
|
|
49060
|
+
${fields}`);
|
|
49061
|
+
}
|
|
49062
|
+
if (data.RelatedTopics?.length) {
|
|
49063
|
+
const topics = data.RelatedTopics.filter((t) => t.Text).slice(0, 5).map((t) => ` - ${t.Text}`).join(`
|
|
49064
|
+
`);
|
|
49065
|
+
if (topics)
|
|
49066
|
+
parts.push(`Related:
|
|
49067
|
+
${topics}`);
|
|
49068
|
+
}
|
|
49069
|
+
const result2 = parts.join(`
|
|
48836
49070
|
|
|
48837
|
-
|
|
48838
|
-
|
|
48839
|
-
|
|
48840
|
-
|
|
48841
|
-
|
|
48842
|
-
|
|
48843
|
-
|
|
48844
|
-
|
|
48845
|
-
|
|
48846
|
-
|
|
48847
|
-
|
|
48848
|
-
|
|
48849
|
-
|
|
48850
|
-
|
|
48851
|
-
|
|
48852
|
-
|
|
48853
|
-
|
|
49071
|
+
`);
|
|
49072
|
+
if (result2.length > 60)
|
|
49073
|
+
return result2;
|
|
49074
|
+
}
|
|
49075
|
+
} catch {}
|
|
49076
|
+
try {
|
|
49077
|
+
const htmlUrl = `https://html.duckduckgo.com/html/?q=${encoded}`;
|
|
49078
|
+
const res = await fetch(htmlUrl, {
|
|
49079
|
+
headers: {
|
|
49080
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
49081
|
+
Accept: "text/html"
|
|
49082
|
+
},
|
|
49083
|
+
signal: AbortSignal.timeout(1e4)
|
|
49084
|
+
});
|
|
49085
|
+
if (!res.ok)
|
|
49086
|
+
throw new Error(`HTTP ${res.status}`);
|
|
49087
|
+
const html = await res.text();
|
|
49088
|
+
const snippets = [];
|
|
49089
|
+
const snippetRe = /class="result__snippet"[^>]*>([\s\S]*?)<\/a>/g;
|
|
49090
|
+
let m;
|
|
49091
|
+
while ((m = snippetRe.exec(html)) !== null && snippets.length < 6) {
|
|
49092
|
+
const text = m[1].replace(/<[^>]+>/g, " ").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/\s+/g, " ").trim();
|
|
49093
|
+
if (text.length > 20)
|
|
49094
|
+
snippets.push(`- ${text}`);
|
|
49095
|
+
}
|
|
49096
|
+
const links = [];
|
|
49097
|
+
const linkRe = /class="result__a"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/g;
|
|
49098
|
+
while ((m = linkRe.exec(html)) !== null && links.length < 5) {
|
|
49099
|
+
const title = m[2].replace(/<[^>]+>/g, "").trim();
|
|
49100
|
+
const href = m[1];
|
|
49101
|
+
if (title && href)
|
|
49102
|
+
links.push(` ${title} — ${href}`);
|
|
49103
|
+
}
|
|
49104
|
+
if (snippets.length === 0 && links.length === 0)
|
|
49105
|
+
return `No results found for: ${query}`;
|
|
49106
|
+
const parts = [`Search results for: ${query}`];
|
|
49107
|
+
if (snippets.length > 0)
|
|
49108
|
+
parts.push(`Snippets:
|
|
49109
|
+
${snippets.join(`
|
|
49110
|
+
`)}`);
|
|
49111
|
+
if (links.length > 0)
|
|
49112
|
+
parts.push(`Links:
|
|
49113
|
+
${links.join(`
|
|
49114
|
+
`)}`);
|
|
49115
|
+
return parts.join(`
|
|
48854
49116
|
|
|
48855
|
-
|
|
49117
|
+
`);
|
|
49118
|
+
} catch (err) {
|
|
49119
|
+
return `Search failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
49120
|
+
}
|
|
49121
|
+
}
|
|
49122
|
+
// src/tools/pdf.ts
|
|
49123
|
+
import path15 from "path";
|
|
49124
|
+
import os6 from "os";
|
|
49125
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
49126
|
+
function generatePdf(filePath, content, repoPath) {
|
|
49127
|
+
const fullPath = path15.isAbsolute(filePath) ? filePath : path15.join(repoPath, filePath);
|
|
49128
|
+
try {
|
|
49129
|
+
const dir = path15.dirname(fullPath);
|
|
49130
|
+
if (!existsSync11(dir))
|
|
49131
|
+
mkdirSync5(dir, { recursive: true });
|
|
49132
|
+
const escaped = content.replace(/\\/g, "\\\\").replace(/"""/g, "\\\"\\\"\\\"").replace(/\r/g, "");
|
|
49133
|
+
const script = `
|
|
49134
|
+
import sys
|
|
49135
|
+
try:
|
|
49136
|
+
from reportlab.lib.pagesizes import letter
|
|
49137
|
+
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, HRFlowable
|
|
49138
|
+
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
|
49139
|
+
from reportlab.lib.units import inch
|
|
49140
|
+
from reportlab.lib import colors
|
|
49141
|
+
except ImportError:
|
|
49142
|
+
import subprocess
|
|
49143
|
+
subprocess.check_call([sys.executable, "-m", "pip", "install", "reportlab", "--break-system-packages", "-q"])
|
|
49144
|
+
from reportlab.lib.pagesizes import letter
|
|
49145
|
+
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, HRFlowable
|
|
49146
|
+
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
|
49147
|
+
from reportlab.lib.units import inch
|
|
49148
|
+
from reportlab.lib import colors
|
|
48856
49149
|
|
|
48857
|
-
|
|
48858
|
-
|
|
48859
|
-
|
|
48860
|
-
|
|
48861
|
-
|
|
48862
|
-
|
|
48863
|
-
|
|
48864
|
-
|
|
48865
|
-
content: "open https://github.com/microsoft/typescript"
|
|
48866
|
-
},
|
|
48867
|
-
{
|
|
48868
|
-
role: "assistant",
|
|
48869
|
-
content: "<open-url>https://github.com/microsoft/typescript</open-url>"
|
|
48870
|
-
},
|
|
48871
|
-
{
|
|
48872
|
-
role: "user",
|
|
48873
|
-
content: `Here is the output from open-url https://github.com/microsoft/typescript:
|
|
49150
|
+
doc = SimpleDocTemplate(
|
|
49151
|
+
r"""${fullPath}""",
|
|
49152
|
+
pagesize=letter,
|
|
49153
|
+
rightMargin=inch,
|
|
49154
|
+
leftMargin=inch,
|
|
49155
|
+
topMargin=inch,
|
|
49156
|
+
bottomMargin=inch,
|
|
49157
|
+
)
|
|
48874
49158
|
|
|
48875
|
-
|
|
49159
|
+
styles = getSampleStyleSheet()
|
|
49160
|
+
styles.add(ParagraphStyle(name="H1", parent=styles["Heading1"], fontSize=22, spaceAfter=10))
|
|
49161
|
+
styles.add(ParagraphStyle(name="H2", parent=styles["Heading2"], fontSize=16, spaceAfter=8))
|
|
49162
|
+
styles.add(ParagraphStyle(name="H3", parent=styles["Heading3"], fontSize=13, spaceAfter=6))
|
|
49163
|
+
styles.add(ParagraphStyle(name="Body", parent=styles["Normal"], fontSize=11, leading=16, spaceAfter=8))
|
|
49164
|
+
styles.add(ParagraphStyle(name="Bullet", parent=styles["Normal"], fontSize=11, leading=16, leftIndent=20, spaceAfter=4, bulletIndent=10))
|
|
48876
49165
|
|
|
48877
|
-
|
|
48878
|
-
},
|
|
48879
|
-
{
|
|
48880
|
-
role: "assistant",
|
|
48881
|
-
content: "Opened the TypeScript GitHub page in your browser."
|
|
48882
|
-
},
|
|
48883
|
-
{
|
|
48884
|
-
role: "user",
|
|
48885
|
-
content: "generate a PDF report about the project and save it to docs/report.pdf"
|
|
48886
|
-
},
|
|
48887
|
-
{
|
|
48888
|
-
role: "assistant",
|
|
48889
|
-
content: `<generate-pdf>
|
|
48890
|
-
{"path": "docs/report.pdf", "content": "# Project Report\\n\\n## Overview\\n\\nThis document summarizes the project.\\n\\n## Details\\n\\nMore content here."}
|
|
48891
|
-
</generate-pdf>`
|
|
48892
|
-
},
|
|
48893
|
-
{
|
|
48894
|
-
role: "user",
|
|
48895
|
-
content: `Here is the output from generate-pdf to docs/report.pdf:
|
|
49166
|
+
raw = """${escaped}"""
|
|
48896
49167
|
|
|
48897
|
-
|
|
49168
|
+
story = []
|
|
49169
|
+
for line in raw.split("\\n"):
|
|
49170
|
+
s = line.rstrip()
|
|
49171
|
+
if s.startswith("### "):
|
|
49172
|
+
story.append(Paragraph(s[4:], styles["H3"]))
|
|
49173
|
+
elif s.startswith("## "):
|
|
49174
|
+
story.append(Spacer(1, 6))
|
|
49175
|
+
story.append(Paragraph(s[3:], styles["H2"]))
|
|
49176
|
+
story.append(HRFlowable(width="100%", thickness=0.5, color=colors.grey, spaceAfter=4))
|
|
49177
|
+
elif s.startswith("# "):
|
|
49178
|
+
story.append(Paragraph(s[2:], styles["H1"]))
|
|
49179
|
+
story.append(HRFlowable(width="100%", thickness=1, color=colors.black, spaceAfter=6))
|
|
49180
|
+
elif s.startswith("- ") or s.startswith("* "):
|
|
49181
|
+
story.append(Paragraph(u"\\u2022 " + s[2:], styles["Bullet"]))
|
|
49182
|
+
elif s.startswith("---"):
|
|
49183
|
+
story.append(HRFlowable(width="100%", thickness=0.5, color=colors.grey, spaceAfter=4))
|
|
49184
|
+
elif s == "":
|
|
49185
|
+
story.append(Spacer(1, 6))
|
|
49186
|
+
else:
|
|
49187
|
+
import re
|
|
49188
|
+
s = re.sub(r"\\*\\*(.+?)\\*\\*", r"<b>\\1</b>", s)
|
|
49189
|
+
s = re.sub(r"\\*(.+?)\\*", r"<i>\\1</i>", s)
|
|
49190
|
+
s = re.sub(r"\`(.+?)\`", r"<font name='Courier'>\\1</font>", s)
|
|
49191
|
+
story.append(Paragraph(s, styles["Body"]))
|
|
48898
49192
|
|
|
48899
|
-
|
|
48900
|
-
|
|
48901
|
-
|
|
48902
|
-
|
|
48903
|
-
|
|
48904
|
-
|
|
48905
|
-
|
|
48906
|
-
|
|
48907
|
-
|
|
48908
|
-
|
|
48909
|
-
|
|
48910
|
-
|
|
48911
|
-
|
|
48912
|
-
|
|
48913
|
-
|
|
48914
|
-
|
|
48915
|
-
|
|
48916
|
-
|
|
48917
|
-
|
|
49193
|
+
doc.build(story)
|
|
49194
|
+
print("OK")
|
|
49195
|
+
`.replace("${fullPath}", fullPath.replace(/\\/g, "/")).replace("${escaped}", escaped);
|
|
49196
|
+
const tmpFile = path15.join(os6.tmpdir(), `lens_pdf_${Date.now()}.py`);
|
|
49197
|
+
writeFileSync7(tmpFile, script, "utf-8");
|
|
49198
|
+
const { execSync: execSync2 } = __require("child_process");
|
|
49199
|
+
execSync2(`python "${tmpFile}"`, { stdio: "pipe" });
|
|
49200
|
+
try {
|
|
49201
|
+
__require("fs").unlinkSync(tmpFile);
|
|
49202
|
+
} catch {}
|
|
49203
|
+
return `PDF generated: ${fullPath}`;
|
|
49204
|
+
} catch (err) {
|
|
49205
|
+
return `Error generating PDF: ${err instanceof Error ? err.message : String(err)}`;
|
|
49206
|
+
}
|
|
49207
|
+
}
|
|
49208
|
+
// src/prompts/system.ts
|
|
49209
|
+
function buildSystemPrompt(files, memorySummary = "", toolsSection) {
|
|
49210
|
+
const fileList = files.map((f) => `### ${f.path}
|
|
49211
|
+
\`\`\`
|
|
49212
|
+
${f.content.slice(0, 2000)}
|
|
49213
|
+
\`\`\``).join(`
|
|
48918
49214
|
|
|
48919
|
-
|
|
49215
|
+
`);
|
|
49216
|
+
const tools = toolsSection ?? BUILTIN_TOOLS_SECTION;
|
|
49217
|
+
return `You are an expert software engineer assistant with access to the user's codebase and tools.
|
|
48920
49218
|
|
|
48921
|
-
|
|
48922
|
-
12: import { ChatRunner } from "./components/chat/ChatRunner";
|
|
49219
|
+
${tools}
|
|
48923
49220
|
|
|
48924
|
-
|
|
48925
|
-
1: export const ChatRunner = ...
|
|
49221
|
+
## MEMORY OPERATIONS
|
|
48926
49222
|
|
|
48927
|
-
|
|
48928
|
-
|
|
48929
|
-
{
|
|
48930
|
-
role: "assistant",
|
|
48931
|
-
content: "`ChatRunner` is defined in `src/components/chat/ChatRunner.tsx` and imported in `src/index.tsx`."
|
|
48932
|
-
},
|
|
48933
|
-
{
|
|
48934
|
-
role: "user",
|
|
48935
|
-
content: "find all usages of useInput in the codebase"
|
|
48936
|
-
},
|
|
48937
|
-
{
|
|
48938
|
-
role: "assistant",
|
|
48939
|
-
content: `<grep>
|
|
48940
|
-
{"pattern": "useInput", "glob": "src/**/*.tsx"}
|
|
48941
|
-
</grep>`
|
|
48942
|
-
},
|
|
48943
|
-
{
|
|
48944
|
-
role: "user",
|
|
48945
|
-
content: `Here is the output from grep for "useInput":
|
|
49223
|
+
You can save and delete memories at any time by emitting these tags alongside your normal response.
|
|
49224
|
+
They are stripped before display — the user will not see the raw tags.
|
|
48946
49225
|
|
|
48947
|
-
|
|
49226
|
+
### memory-add — save something important to long-term memory for this repo
|
|
49227
|
+
<memory-add>User prefers TypeScript strict mode in all new files</memory-add>
|
|
48948
49228
|
|
|
48949
|
-
|
|
48950
|
-
|
|
48951
|
-
210: useInput((input, key) => {
|
|
49229
|
+
### memory-delete — delete a memory by its ID (shown in brackets like [abc123])
|
|
49230
|
+
<memory-delete>abc123</memory-delete>
|
|
48952
49231
|
|
|
48953
|
-
|
|
48954
|
-
|
|
48955
|
-
|
|
48956
|
-
|
|
48957
|
-
content: "`useInput` is used in `src/components/chat/ChatRunner.tsx` — imported on line 5 and called on line 210."
|
|
48958
|
-
},
|
|
48959
|
-
{
|
|
48960
|
-
role: "user",
|
|
48961
|
-
content: "show me how to use the read-file tool"
|
|
48962
|
-
},
|
|
48963
|
-
{
|
|
48964
|
-
role: "assistant",
|
|
48965
|
-
content: "To read a file, emit the tag alone in your response — nothing else on that turn:\n\n```text\n[read-file]src/components/Header.tsx[/read-file]\n```\n\nThe result comes back as the next user message and you can then analyze it."
|
|
48966
|
-
},
|
|
48967
|
-
{
|
|
48968
|
-
role: "user",
|
|
48969
|
-
content: "show me an example of the shell tool"
|
|
48970
|
-
},
|
|
48971
|
-
{
|
|
48972
|
-
role: "assistant",
|
|
48973
|
-
content: "To run a shell command, emit only the tag:\n\n```text\n[shell]node -v[/shell]\n```\n\nThe stdout/stderr is returned and you can read it in the next turn."
|
|
48974
|
-
},
|
|
48975
|
-
{
|
|
48976
|
-
role: "user",
|
|
48977
|
-
content: "read src folder"
|
|
48978
|
-
},
|
|
48979
|
-
{
|
|
48980
|
-
role: "assistant",
|
|
48981
|
-
content: "<read-folder>src</read-folder>"
|
|
48982
|
-
},
|
|
48983
|
-
{
|
|
48984
|
-
role: "user",
|
|
48985
|
-
content: `Here is the output from read-folder of src:
|
|
49232
|
+
Use memory-add when:
|
|
49233
|
+
- The user explicitly asks you to remember something ("remember that...", "don't forget...")
|
|
49234
|
+
- You learn something project-specific that would be useful in future sessions
|
|
49235
|
+
(e.g. preferred patterns, architecture decisions, known gotchas, user preferences)
|
|
48986
49236
|
|
|
48987
|
-
|
|
49237
|
+
Use memory-delete when:
|
|
49238
|
+
- The user asks you to forget something
|
|
49239
|
+
- A memory is outdated or wrong and you are replacing it with a new one
|
|
48988
49240
|
|
|
48989
|
-
|
|
48990
|
-
index.ts
|
|
48991
|
-
App.tsx
|
|
49241
|
+
You may emit multiple memory operations in a single response alongside normal content.
|
|
48992
49242
|
|
|
48993
|
-
|
|
48994
|
-
components/
|
|
48995
|
-
utils/
|
|
49243
|
+
## RULES
|
|
48996
49244
|
|
|
48997
|
-
|
|
48998
|
-
|
|
48999
|
-
|
|
49000
|
-
|
|
49001
|
-
|
|
49002
|
-
|
|
49245
|
+
1. When you need to use a tool, output ONLY the XML tag — nothing before or after it in that response
|
|
49246
|
+
2. ONE tool per response — emit the tag, then stop completely
|
|
49247
|
+
3. After the user approves and you get the result, continue your analysis in the next response
|
|
49248
|
+
4. NEVER print a URL, command, filename, or JSON blob as plain text when you should be using a tool
|
|
49249
|
+
5. NEVER say "I'll fetch" / "run this command" / "here's the write-file" — just emit the tag
|
|
49250
|
+
6. NEVER use shell to run git clone — always use the clone tag instead
|
|
49251
|
+
7. NEVER use shell to list files or folders (no ls, dir, find, git ls-files, tree) — ALWAYS use read-folder instead
|
|
49252
|
+
8. NEVER use shell to read a file (no cat, type, Get-Content) — ALWAYS use read-file instead
|
|
49253
|
+
9. NEVER use shell grep, findstr, or Select-String to search file contents — ALWAYS use grep instead
|
|
49254
|
+
10. shell is ONLY for running code, installing packages, building, testing — not for filesystem inspection
|
|
49255
|
+
11. write-file content field must be the COMPLETE file content, never empty or placeholder
|
|
49256
|
+
12. After a write-file succeeds, do NOT repeat it — trust the result and move on
|
|
49257
|
+
13. After a write-file succeeds, tell the user it is done immediately — do NOT auto-read the file back to verify
|
|
49258
|
+
14. NEVER apologize and redo a tool call you already made — if write-file or shell ran and returned a result, it worked, do not run it again
|
|
49259
|
+
15. NEVER say "I made a mistake" and repeat the same tool — one attempt is enough, trust the output
|
|
49260
|
+
16. NEVER second-guess yourself mid-response — commit to your answer
|
|
49261
|
+
17. If a read-folder or read-file returns "not found", accept it and move on — do NOT retry the same path
|
|
49262
|
+
18. If you have already retrieved a result for a path in this conversation, do NOT request it again — use the result you already have
|
|
49263
|
+
19. Every shell command runs from the repo root — \`cd\` has NO persistent effect. NEVER use \`cd\` alone. Use full paths or combine with && e.g. \`cd list && bun run index.ts\`
|
|
49264
|
+
20. write-file paths are relative to the repo root — if creating files in a subfolder write the full relative path e.g. \`list/src/index.tsx\` NOT \`src/index.tsx\`
|
|
49265
|
+
21. When scaffolding a new project in a subfolder, ALL write-file paths must start with that subfolder name e.g. \`list/package.json\`, \`list/src/index.tsx\`
|
|
49266
|
+
22. When scaffolding a multi-file project, after each write-file succeeds, immediately proceed to writing the NEXT file — NEVER rewrite a file you already wrote in this session. Each file is written ONCE and ONLY ONCE.
|
|
49267
|
+
23. For JSX/TSX files always use \`.tsx\` extension and include \`/** @jsxImportSource react */\` or ensure tsconfig has jsx set — bun needs this to parse JSX
|
|
49268
|
+
24. When explaining how to use a tool in text, use [tag] bracket notation or a fenced code block — NEVER emit a real XML tool tag as part of an explanation or example
|
|
49269
|
+
25. NEVER read files, list folders, or run tools that were not asked for in the current user message
|
|
49270
|
+
26. NEVER use markdown formatting in plain text responses — no **bold**, no *italics*, no # headings, no bullet points with -, *, or +, no numbered lists, no backtick inline code. Write in plain prose. Only use fenced \`\`\` code blocks when showing actual code.
|
|
49271
|
+
|
|
49272
|
+
## SCAFFOLDING — CHAINING WRITE-FILE CALLS
|
|
49273
|
+
|
|
49274
|
+
When creating multiple files (e.g. scaffolding a project or creating 10 files), emit ALL of them
|
|
49275
|
+
in a single response by chaining the tags back-to-back with no text between them:
|
|
49276
|
+
|
|
49277
|
+
<write-file>
|
|
49278
|
+
{"path": "test/file1.txt", "content": "File 1 content"}
|
|
49279
|
+
</write-file>
|
|
49280
|
+
<write-file>
|
|
49281
|
+
{"path": "test/file2.txt", "content": "File 2 content"}
|
|
49282
|
+
</write-file>
|
|
49283
|
+
<write-file>
|
|
49284
|
+
{"path": "test/file3.txt", "content": "File 3 content"}
|
|
49285
|
+
</write-file>
|
|
49286
|
+
|
|
49287
|
+
The system processes each tag sequentially and automatically continues to the next one.
|
|
49288
|
+
Do NOT wait for a user message between files — emit all tags at once.
|
|
49289
|
+
|
|
49290
|
+
## CRITICAL: READ BEFORE YOU WRITE
|
|
49291
|
+
|
|
49292
|
+
These rules are mandatory whenever you plan to edit or create a file:
|
|
49293
|
+
|
|
49294
|
+
### Before modifying ANY existing file:
|
|
49295
|
+
1. ALWAYS use read-file on the exact file you plan to change FIRST
|
|
49296
|
+
2. Study the full current content — understand every import, every export, every type, every existing feature
|
|
49297
|
+
3. Your changes patch MUST preserve ALL existing functionality — do not remove or rewrite things that were not part of the request
|
|
49298
|
+
4. If you are unsure what other files import from the file you are editing, use read-folder on the parent directory first to see what exists nearby, then read-file the relevant ones
|
|
49299
|
+
|
|
49300
|
+
### Before adding a feature that touches multiple files:
|
|
49301
|
+
1. Use read-folder on the relevant directory to see what files exist
|
|
49302
|
+
2. Use read-file on each file you plan to touch
|
|
49303
|
+
3. Only then emit a changes tag — with patches that are surgical additions, not wholesale rewrites
|
|
49304
|
+
|
|
49305
|
+
### The golden rule for write-file and changes:
|
|
49306
|
+
- The output file must contain EVERYTHING the original had, PLUS your new additions
|
|
49307
|
+
- NEVER produce a file that is shorter than the original unless you are explicitly asked to delete things
|
|
49308
|
+
- If you catch yourself rewriting a file from scratch, STOP — go back and read the original first
|
|
49309
|
+
|
|
49310
|
+
## CODEBASE
|
|
49311
|
+
|
|
49312
|
+
${fileList.length > 0 ? fileList : "(no files indexed)"}
|
|
49313
|
+
|
|
49314
|
+
${memorySummary}`;
|
|
49315
|
+
}
|
|
49316
|
+
var BUILTIN_TOOLS_SECTION = `## TOOLS
|
|
49317
|
+
|
|
49318
|
+
You have exactly thirteen tools. To use a tool you MUST wrap it in the exact XML tags shown below — no other format will work.
|
|
49319
|
+
|
|
49320
|
+
### 1. fetch — load a URL
|
|
49321
|
+
<fetch>https://example.com</fetch>
|
|
49322
|
+
|
|
49323
|
+
### 2. shell — run a terminal command
|
|
49324
|
+
<shell>node -v</shell>
|
|
49325
|
+
|
|
49326
|
+
### 3. read-file — read a file from the repo
|
|
49327
|
+
<read-file>src/foo.ts</read-file>
|
|
49328
|
+
|
|
49329
|
+
### 4. read-folder — list contents of a folder (files + subfolders, one level deep)
|
|
49330
|
+
<read-folder>src/components</read-folder>
|
|
49331
|
+
|
|
49332
|
+
### 5. grep — search for a pattern across files in the repo (cross-platform, no shell needed)
|
|
49333
|
+
<grep>
|
|
49334
|
+
{"pattern": "ChatRunner", "glob": "src/**/*.tsx"}
|
|
49335
|
+
</grep>
|
|
49336
|
+
|
|
49337
|
+
### 6. write-file — create or overwrite a file
|
|
49338
|
+
<write-file>
|
|
49339
|
+
{"path": "data/output.csv", "content": "col1,col2\\nval1,val2"}
|
|
49340
|
+
</write-file>
|
|
49341
|
+
|
|
49342
|
+
### 7. delete-file — permanently delete a single file
|
|
49343
|
+
<delete-file>src/old-component.tsx</delete-file>
|
|
49344
|
+
|
|
49345
|
+
### 8. delete-folder — permanently delete a folder and all its contents
|
|
49346
|
+
<delete-folder>src/legacy</delete-folder>
|
|
49347
|
+
|
|
49348
|
+
### 9. open-url — open a URL in the user's default browser
|
|
49349
|
+
<open-url>https://github.com/owner/repo</open-url>
|
|
49350
|
+
|
|
49351
|
+
### 10. generate-pdf — generate a PDF file from markdown-style content
|
|
49352
|
+
<generate-pdf>
|
|
49353
|
+
{"path": "output/report.pdf", "content": "# Title\\n\\nSome body text.\\n\\n## Section\\n\\nMore content."}
|
|
49354
|
+
</generate-pdf>
|
|
49355
|
+
|
|
49356
|
+
### 11. search — search the internet for anything you are unsure about
|
|
49357
|
+
<search>how to use React useEffect cleanup function</search>
|
|
49358
|
+
|
|
49359
|
+
### 12. clone — clone a GitHub repo so you can explore and discuss it
|
|
49360
|
+
<clone>https://github.com/owner/repo</clone>
|
|
49361
|
+
|
|
49362
|
+
### 13. changes — propose code edits (shown as a diff for user approval)
|
|
49363
|
+
<changes>
|
|
49364
|
+
{"summary": "what changed and why", "patches": [{"path": "src/foo.ts", "content": "COMPLETE file content", "isNew": false}]}
|
|
49365
|
+
</changes>`;
|
|
49366
|
+
// src/prompts/fewshot.ts
|
|
49367
|
+
var FEW_SHOT_MESSAGES = [
|
|
49003
49368
|
{
|
|
49004
49369
|
role: "user",
|
|
49005
|
-
content: "
|
|
49370
|
+
content: "delete src/old-component.tsx"
|
|
49006
49371
|
},
|
|
49007
49372
|
{
|
|
49008
49373
|
role: "assistant",
|
|
49009
|
-
content: "<
|
|
49374
|
+
content: "<delete-file>src/old-component.tsx</delete-file>"
|
|
49010
49375
|
},
|
|
49011
49376
|
{
|
|
49012
49377
|
role: "user",
|
|
49013
|
-
content: `Here is the output from
|
|
49014
|
-
|
|
49015
|
-
Folder: src (4 entries)
|
|
49016
|
-
|
|
49017
|
-
Files:
|
|
49018
|
-
index.ts
|
|
49019
|
-
App.tsx
|
|
49378
|
+
content: `Here is the output from delete-file of src/old-component.tsx:
|
|
49020
49379
|
|
|
49021
|
-
|
|
49022
|
-
components/
|
|
49023
|
-
utils/
|
|
49380
|
+
Deleted: /repo/src/old-component.tsx
|
|
49024
49381
|
|
|
49025
49382
|
Please continue your response based on this output.`
|
|
49026
49383
|
},
|
|
49027
49384
|
{
|
|
49028
49385
|
role: "assistant",
|
|
49029
|
-
content: "
|
|
49386
|
+
content: "Done — `src/old-component.tsx` has been deleted."
|
|
49030
49387
|
},
|
|
49031
49388
|
{
|
|
49032
49389
|
role: "user",
|
|
49033
|
-
content: "
|
|
49390
|
+
content: "delete the legacy folder"
|
|
49034
49391
|
},
|
|
49035
49392
|
{
|
|
49036
49393
|
role: "assistant",
|
|
49037
|
-
content: "<
|
|
49394
|
+
content: "<delete-folder>src/legacy</delete-folder>"
|
|
49038
49395
|
},
|
|
49039
49396
|
{
|
|
49040
49397
|
role: "user",
|
|
49041
|
-
content: `Here is the output from
|
|
49042
|
-
|
|
49043
|
-
Folder: src/components (5 entries)
|
|
49044
|
-
|
|
49045
|
-
Files:
|
|
49046
|
-
Header.tsx
|
|
49047
|
-
Footer.tsx
|
|
49048
|
-
Button.tsx
|
|
49398
|
+
content: `Here is the output from delete-folder of src/legacy:
|
|
49049
49399
|
|
|
49050
|
-
|
|
49051
|
-
ui/
|
|
49052
|
-
forms/
|
|
49400
|
+
Deleted folder: /repo/src/legacy
|
|
49053
49401
|
|
|
49054
49402
|
Please continue your response based on this output.`
|
|
49055
49403
|
},
|
|
49056
49404
|
{
|
|
49057
49405
|
role: "assistant",
|
|
49058
|
-
content: "
|
|
49406
|
+
content: "Done — the `src/legacy` folder and all its contents have been deleted."
|
|
49059
49407
|
},
|
|
49060
49408
|
{
|
|
49061
49409
|
role: "user",
|
|
49062
|
-
content: "
|
|
49410
|
+
content: "open https://github.com/microsoft/typescript"
|
|
49063
49411
|
},
|
|
49064
49412
|
{
|
|
49065
49413
|
role: "assistant",
|
|
49066
|
-
content: "<
|
|
49414
|
+
content: "<open-url>https://github.com/microsoft/typescript</open-url>"
|
|
49067
49415
|
},
|
|
49068
49416
|
{
|
|
49069
49417
|
role: "user",
|
|
49070
|
-
content: `Here is the output from
|
|
49071
|
-
|
|
49072
|
-
Folder: src/utils (3 entries)
|
|
49418
|
+
content: `Here is the output from open-url https://github.com/microsoft/typescript:
|
|
49073
49419
|
|
|
49074
|
-
|
|
49075
|
-
api.ts
|
|
49076
|
-
helpers.ts
|
|
49077
|
-
format.ts
|
|
49420
|
+
Opened: https://github.com/microsoft/typescript
|
|
49078
49421
|
|
|
49079
49422
|
Please continue your response based on this output.`
|
|
49080
49423
|
},
|
|
49081
49424
|
{
|
|
49082
49425
|
role: "assistant",
|
|
49083
|
-
content: "
|
|
49426
|
+
content: "Opened the TypeScript GitHub page in your browser."
|
|
49084
49427
|
},
|
|
49085
49428
|
{
|
|
49086
49429
|
role: "user",
|
|
49087
|
-
content: "
|
|
49430
|
+
content: "generate a PDF report about the project and save it to docs/report.pdf"
|
|
49088
49431
|
},
|
|
49089
49432
|
{
|
|
49090
49433
|
role: "assistant",
|
|
49091
|
-
content:
|
|
49434
|
+
content: `<generate-pdf>
|
|
49435
|
+
{"path": "docs/report.pdf", "content": "# Project Report\\n\\n## Overview\\n\\nThis document summarizes the project.\\n\\n## Details\\n\\nMore content here."}
|
|
49436
|
+
</generate-pdf>`
|
|
49092
49437
|
},
|
|
49093
49438
|
{
|
|
49094
49439
|
role: "user",
|
|
49095
|
-
content: `Here is the output from
|
|
49096
|
-
|
|
49097
|
-
Folder: src (4 entries)
|
|
49098
|
-
|
|
49099
|
-
Files:
|
|
49100
|
-
index.ts
|
|
49101
|
-
App.tsx
|
|
49440
|
+
content: `Here is the output from generate-pdf to docs/report.pdf:
|
|
49102
49441
|
|
|
49103
|
-
|
|
49104
|
-
components/
|
|
49105
|
-
utils/
|
|
49442
|
+
PDF generated: /repo/docs/report.pdf
|
|
49106
49443
|
|
|
49107
49444
|
Please continue your response based on this output.`
|
|
49108
49445
|
},
|
|
49109
49446
|
{
|
|
49110
49447
|
role: "assistant",
|
|
49111
|
-
content: "
|
|
49448
|
+
content: "Done — the PDF report has been saved to `docs/report.pdf`."
|
|
49112
49449
|
},
|
|
49113
49450
|
{
|
|
49114
49451
|
role: "user",
|
|
49115
|
-
content:
|
|
49452
|
+
content: 'grep -R "ChatRunner" -n src'
|
|
49116
49453
|
},
|
|
49117
49454
|
{
|
|
49118
49455
|
role: "assistant",
|
|
49119
|
-
content:
|
|
49456
|
+
content: `<grep>
|
|
49457
|
+
{"pattern": "ChatRunner", "glob": "src/**/*"}
|
|
49458
|
+
</grep>`
|
|
49120
49459
|
},
|
|
49121
49460
|
{
|
|
49122
49461
|
role: "user",
|
|
49123
|
-
content: `Here is the output from
|
|
49462
|
+
content: `Here is the output from grep for "ChatRunner":
|
|
49124
49463
|
|
|
49125
|
-
|
|
49464
|
+
grep /ChatRunner/ src/**/* — 3 match(es) in 2 file(s)
|
|
49126
49465
|
|
|
49127
|
-
|
|
49128
|
-
|
|
49129
|
-
tsconfig.json
|
|
49130
|
-
README.md
|
|
49466
|
+
src/index.tsx
|
|
49467
|
+
12: import { ChatRunner } from "./components/chat/ChatRunner";
|
|
49131
49468
|
|
|
49132
|
-
|
|
49133
|
-
|
|
49134
|
-
node_modules/
|
|
49469
|
+
src/components/chat/ChatRunner.tsx
|
|
49470
|
+
1: export const ChatRunner = ...
|
|
49135
49471
|
|
|
49136
49472
|
Please continue your response based on this output.`
|
|
49137
49473
|
},
|
|
49138
49474
|
{
|
|
49139
49475
|
role: "assistant",
|
|
49140
|
-
content: "
|
|
49476
|
+
content: "`ChatRunner` is defined in `src/components/chat/ChatRunner.tsx` and imported in `src/index.tsx`."
|
|
49141
49477
|
},
|
|
49142
49478
|
{
|
|
49143
49479
|
role: "user",
|
|
49144
|
-
content: "
|
|
49480
|
+
content: "find all usages of useInput in the codebase"
|
|
49145
49481
|
},
|
|
49146
49482
|
{
|
|
49147
49483
|
role: "assistant",
|
|
49148
|
-
content:
|
|
49484
|
+
content: `<grep>
|
|
49485
|
+
{"pattern": "useInput", "glob": "src/**/*.tsx"}
|
|
49486
|
+
</grep>`
|
|
49149
49487
|
},
|
|
49150
49488
|
{
|
|
49151
49489
|
role: "user",
|
|
49152
|
-
content: `Here is the output from
|
|
49153
|
-
|
|
49154
|
-
Folder: . (4 entries)
|
|
49490
|
+
content: `Here is the output from grep for "useInput":
|
|
49155
49491
|
|
|
49156
|
-
|
|
49157
|
-
package.json
|
|
49158
|
-
README.md
|
|
49492
|
+
grep /useInput/ src/**/*.tsx — 2 match(es) in 1 file(s)
|
|
49159
49493
|
|
|
49160
|
-
|
|
49161
|
-
|
|
49162
|
-
|
|
49494
|
+
src/components/chat/ChatRunner.tsx
|
|
49495
|
+
5: import { useInput } from "ink";
|
|
49496
|
+
210: useInput((input, key) => {
|
|
49163
49497
|
|
|
49164
49498
|
Please continue your response based on this output.`
|
|
49165
49499
|
},
|
|
49166
49500
|
{
|
|
49167
49501
|
role: "assistant",
|
|
49168
|
-
content: "
|
|
49502
|
+
content: "`useInput` is used in `src/components/chat/ChatRunner.tsx` — imported on line 5 and called on line 210."
|
|
49169
49503
|
},
|
|
49170
49504
|
{
|
|
49171
49505
|
role: "user",
|
|
49172
|
-
content: "
|
|
49506
|
+
content: "show me how to use the read-file tool"
|
|
49173
49507
|
},
|
|
49174
49508
|
{
|
|
49175
49509
|
role: "assistant",
|
|
49176
|
-
content: "
|
|
49510
|
+
content: "To read a file, emit the tag alone in your response — nothing else on that turn:\n\n```text\n[read-file]src/components/Header.tsx[/read-file]\n```\n\nThe result comes back as the next user message and you can then analyze it."
|
|
49177
49511
|
},
|
|
49178
49512
|
{
|
|
49179
49513
|
role: "user",
|
|
49180
|
-
content:
|
|
49181
|
-
|
|
49182
|
-
{"name":"TypeScript","stargazers_count":100000}
|
|
49183
|
-
|
|
49184
|
-
Please continue your response based on this output.`
|
|
49514
|
+
content: "show me an example of the shell tool"
|
|
49185
49515
|
},
|
|
49186
49516
|
{
|
|
49187
49517
|
role: "assistant",
|
|
49188
|
-
content: "
|
|
49518
|
+
content: "To run a shell command, emit only the tag:\n\n```text\n[shell]node -v[/shell]\n```\n\nThe stdout/stderr is returned and you can read it in the next turn."
|
|
49189
49519
|
},
|
|
49190
49520
|
{
|
|
49191
49521
|
role: "user",
|
|
49192
|
-
content: "
|
|
49522
|
+
content: "read src folder"
|
|
49193
49523
|
},
|
|
49194
49524
|
{
|
|
49195
49525
|
role: "assistant",
|
|
49196
|
-
content:
|
|
49197
|
-
{"path": "ts-info.json", "content": "{"name":"TypeScript","stars":100000}"}
|
|
49198
|
-
</write-file>`
|
|
49526
|
+
content: "<read-folder>src</read-folder>"
|
|
49199
49527
|
},
|
|
49200
49528
|
{
|
|
49201
49529
|
role: "user",
|
|
49202
|
-
content: `Here is the output from
|
|
49530
|
+
content: `Here is the output from read-folder of src:
|
|
49203
49531
|
|
|
49204
|
-
|
|
49532
|
+
Folder: src (4 entries)
|
|
49533
|
+
|
|
49534
|
+
Files:
|
|
49535
|
+
index.ts
|
|
49536
|
+
App.tsx
|
|
49537
|
+
|
|
49538
|
+
Subfolders:
|
|
49539
|
+
components/
|
|
49540
|
+
utils/
|
|
49205
49541
|
|
|
49206
49542
|
Please continue your response based on this output.`
|
|
49207
49543
|
},
|
|
49208
49544
|
{
|
|
49209
49545
|
role: "assistant",
|
|
49210
|
-
content: "
|
|
49546
|
+
content: "The `src` folder contains `index.ts`, `App.tsx`, plus subfolders `components/` and `utils/`."
|
|
49211
49547
|
},
|
|
49212
49548
|
{
|
|
49213
49549
|
role: "user",
|
|
49214
|
-
content: "
|
|
49550
|
+
content: "what files are in src/components?"
|
|
49215
49551
|
},
|
|
49216
49552
|
{
|
|
49217
49553
|
role: "assistant",
|
|
49218
|
-
content:
|
|
49219
|
-
|
|
49220
|
-
|
|
49554
|
+
content: "<read-folder>src/components</read-folder>"
|
|
49555
|
+
},
|
|
49556
|
+
{
|
|
49557
|
+
role: "user",
|
|
49558
|
+
content: `Here is the output from read-folder of src/components:
|
|
49559
|
+
|
|
49560
|
+
Folder: src/components (5 entries)
|
|
49561
|
+
|
|
49562
|
+
Files:
|
|
49563
|
+
Header.tsx
|
|
49564
|
+
Footer.tsx
|
|
49565
|
+
Button.tsx
|
|
49566
|
+
|
|
49567
|
+
Subfolders:
|
|
49568
|
+
ui/
|
|
49569
|
+
forms/
|
|
49570
|
+
|
|
49571
|
+
Please continue your response based on this output.`
|
|
49572
|
+
},
|
|
49573
|
+
{
|
|
49574
|
+
role: "assistant",
|
|
49575
|
+
content: "The `src/components` folder has 3 files — `Header.tsx`, `Footer.tsx`, `Button.tsx` — plus two subfolders: `ui/` and `forms/`."
|
|
49576
|
+
},
|
|
49577
|
+
{
|
|
49578
|
+
role: "user",
|
|
49579
|
+
content: "show me the project structure"
|
|
49580
|
+
},
|
|
49581
|
+
{
|
|
49582
|
+
role: "assistant",
|
|
49583
|
+
content: "<read-folder>.</read-folder>"
|
|
49584
|
+
},
|
|
49585
|
+
{
|
|
49586
|
+
role: "user",
|
|
49587
|
+
content: `Here is the output from read-folder of .:
|
|
49588
|
+
|
|
49589
|
+
Folder: . (5 entries)
|
|
49590
|
+
|
|
49591
|
+
Files:
|
|
49592
|
+
package.json
|
|
49593
|
+
tsconfig.json
|
|
49594
|
+
README.md
|
|
49595
|
+
|
|
49596
|
+
Subfolders:
|
|
49597
|
+
src/
|
|
49598
|
+
node_modules/
|
|
49599
|
+
|
|
49600
|
+
Please continue your response based on this output.`
|
|
49601
|
+
},
|
|
49602
|
+
{
|
|
49603
|
+
role: "assistant",
|
|
49604
|
+
content: "The project root has 3 files (`package.json`, `tsconfig.json`, `README.md`) and the `src/` folder for source code."
|
|
49605
|
+
},
|
|
49606
|
+
{
|
|
49607
|
+
role: "user",
|
|
49608
|
+
content: "fetch https://api.github.com/repos/microsoft/typescript"
|
|
49609
|
+
},
|
|
49610
|
+
{
|
|
49611
|
+
role: "assistant",
|
|
49612
|
+
content: "<fetch>https://api.github.com/repos/microsoft/typescript</fetch>"
|
|
49613
|
+
},
|
|
49614
|
+
{
|
|
49615
|
+
role: "user",
|
|
49616
|
+
content: `Here is the output from fetch of https://api.github.com/repos/microsoft/typescript:
|
|
49617
|
+
|
|
49618
|
+
{"name":"TypeScript","stargazers_count":100000}
|
|
49619
|
+
|
|
49620
|
+
Please continue your response based on this output.`
|
|
49621
|
+
},
|
|
49622
|
+
{
|
|
49623
|
+
role: "assistant",
|
|
49624
|
+
content: "Found 1 object with fields: name, stargazers_count. Writing to ts-info.json now."
|
|
49625
|
+
},
|
|
49626
|
+
{
|
|
49627
|
+
role: "user",
|
|
49628
|
+
content: "ok go ahead"
|
|
49629
|
+
},
|
|
49630
|
+
{
|
|
49631
|
+
role: "assistant",
|
|
49632
|
+
content: `<write-file>
|
|
49633
|
+
{"path": "ts-info.json", "content": "{"name":"TypeScript","stars":100000}"}
|
|
49634
|
+
</write-file>`
|
|
49635
|
+
},
|
|
49636
|
+
{
|
|
49637
|
+
role: "user",
|
|
49638
|
+
content: `Here is the output from write-file to ts-info.json:
|
|
49639
|
+
|
|
49640
|
+
Written: /repo/ts-info.json (1 lines, 44 bytes)
|
|
49641
|
+
|
|
49642
|
+
Please continue your response based on this output.`
|
|
49643
|
+
},
|
|
49644
|
+
{
|
|
49645
|
+
role: "assistant",
|
|
49646
|
+
content: "Done — `ts-info.json` has been saved."
|
|
49647
|
+
},
|
|
49648
|
+
{
|
|
49649
|
+
role: "user",
|
|
49650
|
+
content: "write it to a file called hello.py"
|
|
49651
|
+
},
|
|
49652
|
+
{
|
|
49653
|
+
role: "assistant",
|
|
49654
|
+
content: `<write-file>
|
|
49655
|
+
{"path": "hello.py", "content": "# hello.py\\nprint('hello')"}
|
|
49656
|
+
</write-file>`
|
|
49221
49657
|
},
|
|
49222
49658
|
{
|
|
49223
49659
|
role: "user",
|
|
@@ -49314,770 +49750,351 @@ Please continue your response based on this output.`
|
|
|
49314
49750
|
},
|
|
49315
49751
|
{
|
|
49316
49752
|
role: "user",
|
|
49317
|
-
content: "
|
|
49753
|
+
content: "scaffold a new project called myapp with package.json and src/index.ts"
|
|
49318
49754
|
},
|
|
49319
49755
|
{
|
|
49320
49756
|
role: "assistant",
|
|
49321
|
-
content:
|
|
49322
|
-
|
|
49323
|
-
|
|
49324
|
-
|
|
49325
|
-
|
|
49326
|
-
|
|
49327
|
-
|
|
49328
|
-
{ kind: "fetch", re: /<fetch>([\s\S]*?)<\/fetch>/g },
|
|
49329
|
-
{ kind: "shell", re: /<shell>([\s\S]*?)<\/shell>/g },
|
|
49330
|
-
{ kind: "read-file", re: /<read-file>([\s\S]*?)<\/read-file>/g },
|
|
49331
|
-
{ kind: "read-folder", re: /<read-folder>([\s\S]*?)<\/read-folder>/g },
|
|
49332
|
-
{ kind: "grep", re: /<grep>([\s\S]*?)<\/grep>/g },
|
|
49333
|
-
{ kind: "delete-file", re: /<delete-file>([\s\S]*?)<\/delete-file>/g },
|
|
49334
|
-
{
|
|
49335
|
-
kind: "delete-folder",
|
|
49336
|
-
re: /<delete-folder>([\s\S]*?)<\/delete-folder>/g
|
|
49337
|
-
},
|
|
49338
|
-
{ kind: "open-url", re: /<open-url>([\s\S]*?)<\/open-url>/g },
|
|
49339
|
-
{ kind: "generate-pdf", re: /<generate-pdf>([\s\S]*?)<\/generate-pdf>/g },
|
|
49340
|
-
{ kind: "write-file", re: /<write-file>([\s\S]*?)<\/write-file>/g },
|
|
49341
|
-
{ kind: "search", re: /<search>([\s\S]*?)<\/search>/g },
|
|
49342
|
-
{ kind: "clone", re: /<clone>([\s\S]*?)<\/clone>/g },
|
|
49343
|
-
{ kind: "changes", re: /<changes>([\s\S]*?)<\/changes>/g },
|
|
49344
|
-
{ kind: "fetch", re: /```fetch\r?\n([\s\S]*?)\r?\n```/g },
|
|
49345
|
-
{ kind: "shell", re: /```shell\r?\n([\s\S]*?)\r?\n```/g },
|
|
49346
|
-
{ kind: "read-file", re: /```read-file\r?\n([\s\S]*?)\r?\n```/g },
|
|
49347
|
-
{ kind: "read-folder", re: /```read-folder\r?\n([\s\S]*?)\r?\n```/g },
|
|
49348
|
-
{ kind: "write-file", re: /```write-file\r?\n([\s\S]*?)\r?\n```/g },
|
|
49349
|
-
{ kind: "search", re: /```search\r?\n([\s\S]*?)\r?\n```/g },
|
|
49350
|
-
{ kind: "changes", re: /```changes\r?\n([\s\S]*?)\r?\n```/g }
|
|
49351
|
-
];
|
|
49352
|
-
for (const { kind: kind2, re } of patterns) {
|
|
49353
|
-
re.lastIndex = 0;
|
|
49354
|
-
const m = re.exec(scanText);
|
|
49355
|
-
if (m) {
|
|
49356
|
-
const originalRe = new RegExp(re.source, re.flags.replace("g", ""));
|
|
49357
|
-
const originalMatch = originalRe.exec(text.slice(m.index));
|
|
49358
|
-
if (originalMatch) {
|
|
49359
|
-
const fakeMatch = Object.assign([
|
|
49360
|
-
text.slice(m.index, m.index + originalMatch[0].length),
|
|
49361
|
-
originalMatch[1]
|
|
49362
|
-
], { index: m.index, input: text, groups: undefined });
|
|
49363
|
-
candidates.push({ index: m.index, kind: kind2, match: fakeMatch });
|
|
49364
|
-
}
|
|
49365
|
-
}
|
|
49366
|
-
}
|
|
49367
|
-
if (candidates.length === 0)
|
|
49368
|
-
return { kind: "text", content: text.trim() };
|
|
49369
|
-
candidates.sort((a, b) => a.index - b.index);
|
|
49370
|
-
const { kind, match } = candidates[0];
|
|
49371
|
-
const before2 = text.slice(0, match.index).replace(/<(fetch|shell|read-file|read-folder|write-file|search|clone|changes)[^>]*>[\s\S]*?<\/\1>/g, "").trim();
|
|
49372
|
-
const body = (match[1] ?? "").trim();
|
|
49373
|
-
if (kind === "changes") {
|
|
49374
|
-
try {
|
|
49375
|
-
const parsed = JSON.parse(body);
|
|
49376
|
-
const display = [before2, parsed.summary].filter(Boolean).join(`
|
|
49377
|
-
|
|
49378
|
-
`);
|
|
49379
|
-
return { kind: "changes", content: display, patches: parsed.patches };
|
|
49380
|
-
} catch {}
|
|
49381
|
-
}
|
|
49382
|
-
if (kind === "shell")
|
|
49383
|
-
return { kind: "shell", content: before2, command: body };
|
|
49384
|
-
if (kind === "fetch") {
|
|
49385
|
-
const url = body.replace(/^<|>$/g, "").trim();
|
|
49386
|
-
return { kind: "fetch", content: before2, url };
|
|
49387
|
-
}
|
|
49388
|
-
if (kind === "read-file")
|
|
49389
|
-
return { kind: "read-file", content: before2, filePath: body };
|
|
49390
|
-
if (kind === "read-folder")
|
|
49391
|
-
return { kind: "read-folder", content: before2, folderPath: body };
|
|
49392
|
-
if (kind === "delete-file")
|
|
49393
|
-
return { kind: "delete-file", content: before2, filePath: body };
|
|
49394
|
-
if (kind === "delete-folder")
|
|
49395
|
-
return { kind: "delete-folder", content: before2, folderPath: body };
|
|
49396
|
-
if (kind === "open-url") {
|
|
49397
|
-
const url = body.replace(/^<|>$/g, "").trim();
|
|
49398
|
-
return { kind: "open-url", content: before2, url };
|
|
49399
|
-
}
|
|
49400
|
-
if (kind === "generate-pdf") {
|
|
49401
|
-
try {
|
|
49402
|
-
const parsed = JSON.parse(body);
|
|
49403
|
-
return {
|
|
49404
|
-
kind: "generate-pdf",
|
|
49405
|
-
content: before2,
|
|
49406
|
-
filePath: parsed.path ?? parsed.filePath ?? "output.pdf",
|
|
49407
|
-
pdfContent: parsed.content ?? ""
|
|
49408
|
-
};
|
|
49409
|
-
} catch {
|
|
49410
|
-
return { kind: "text", content: text };
|
|
49411
|
-
}
|
|
49412
|
-
}
|
|
49413
|
-
if (kind === "grep") {
|
|
49414
|
-
try {
|
|
49415
|
-
const parsed = JSON.parse(body);
|
|
49416
|
-
return {
|
|
49417
|
-
kind: "grep",
|
|
49418
|
-
content: before2,
|
|
49419
|
-
pattern: parsed.pattern,
|
|
49420
|
-
glob: parsed.glob ?? "**/*"
|
|
49421
|
-
};
|
|
49422
|
-
} catch {
|
|
49423
|
-
return { kind: "grep", content: before2, pattern: body, glob: "**/*" };
|
|
49424
|
-
}
|
|
49425
|
-
}
|
|
49426
|
-
if (kind === "write-file") {
|
|
49427
|
-
try {
|
|
49428
|
-
const parsed = JSON.parse(body);
|
|
49429
|
-
return {
|
|
49430
|
-
kind: "write-file",
|
|
49431
|
-
content: before2,
|
|
49432
|
-
filePath: parsed.path,
|
|
49433
|
-
fileContent: parsed.content
|
|
49434
|
-
};
|
|
49435
|
-
} catch {}
|
|
49436
|
-
}
|
|
49437
|
-
if (kind === "search")
|
|
49438
|
-
return { kind: "search", content: before2, query: body };
|
|
49439
|
-
if (kind === "clone") {
|
|
49440
|
-
const url = body.replace(/^<|>$/g, "").trim();
|
|
49441
|
-
return { kind: "clone", content: before2, repoUrl: url };
|
|
49442
|
-
}
|
|
49443
|
-
return { kind: "text", content: text.trim() };
|
|
49444
|
-
}
|
|
49445
|
-
function extractGithubUrl(text) {
|
|
49446
|
-
const match = text.match(/https?:\/\/github\.com\/[\w.-]+\/[\w.-]+/);
|
|
49447
|
-
return match ? match[0] : null;
|
|
49448
|
-
}
|
|
49449
|
-
function toCloneUrl(url) {
|
|
49450
|
-
const clean = url.replace(/\/+$/, "");
|
|
49451
|
-
return clean.endsWith(".git") ? clean : `${clean}.git`;
|
|
49452
|
-
}
|
|
49453
|
-
function buildApiMessages(messages) {
|
|
49454
|
-
return messages.map((m) => {
|
|
49455
|
-
if (m.type === "tool") {
|
|
49456
|
-
if (!m.approved) {
|
|
49457
|
-
return {
|
|
49458
|
-
role: "user",
|
|
49459
|
-
content: "The tool call was denied by the user. Please respond without using that tool."
|
|
49460
|
-
};
|
|
49461
|
-
}
|
|
49462
|
-
const label = m.toolName === "shell" ? `shell command \`${m.content}\`` : m.toolName === "fetch" ? `fetch of ${m.content}` : m.toolName === "read-file" ? `read-file of ${m.content}` : m.toolName === "read-folder" ? `read-folder of ${m.content}` : m.toolName === "grep" ? `grep for "${m.content}"` : m.toolName === "delete-file" ? `delete-file of ${m.content}` : m.toolName === "delete-folder" ? `delete-folder of ${m.content}` : m.toolName === "open-url" ? `open-url ${m.content}` : m.toolName === "generate-pdf" ? `generate-pdf to ${m.content}` : m.toolName === "search" ? `web search for "${m.content}"` : `write-file to ${m.content}`;
|
|
49463
|
-
return {
|
|
49464
|
-
role: "user",
|
|
49465
|
-
content: `Here is the output from the ${label}:
|
|
49757
|
+
content: `<write-file>
|
|
49758
|
+
{"path": "myapp/package.json", "content": "{"name":"myapp","version":"1.0.0"}"}
|
|
49759
|
+
</write-file>`
|
|
49760
|
+
},
|
|
49761
|
+
{
|
|
49762
|
+
role: "user",
|
|
49763
|
+
content: `Here is the output from write-file to myapp/package.json:
|
|
49466
49764
|
|
|
49467
|
-
|
|
49765
|
+
Written: /repo/myapp/package.json (1 lines, 38 bytes)
|
|
49468
49766
|
|
|
49469
49767
|
Please continue your response based on this output.`
|
|
49470
|
-
|
|
49471
|
-
|
|
49472
|
-
|
|
49473
|
-
|
|
49474
|
-
}
|
|
49475
|
-
|
|
49476
|
-
|
|
49477
|
-
|
|
49478
|
-
|
|
49479
|
-
|
|
49480
|
-
if (provider.type === "anthropic") {
|
|
49481
|
-
url = "https://api.anthropic.com/v1/messages";
|
|
49482
|
-
headers = {
|
|
49483
|
-
"Content-Type": "application/json",
|
|
49484
|
-
"x-api-key": provider.apiKey ?? "",
|
|
49485
|
-
"anthropic-version": "2023-06-01"
|
|
49486
|
-
};
|
|
49487
|
-
body = {
|
|
49488
|
-
model: provider.model,
|
|
49489
|
-
max_tokens: 4096,
|
|
49490
|
-
system: systemPrompt,
|
|
49491
|
-
messages: apiMessages
|
|
49492
|
-
};
|
|
49493
|
-
} else {
|
|
49494
|
-
const base2 = provider.baseUrl ?? "https://api.openai.com/v1";
|
|
49495
|
-
url = `${base2}/chat/completions`;
|
|
49496
|
-
headers = {
|
|
49497
|
-
"Content-Type": "application/json",
|
|
49498
|
-
Authorization: `Bearer ${provider.apiKey}`
|
|
49499
|
-
};
|
|
49500
|
-
body = {
|
|
49501
|
-
model: provider.model,
|
|
49502
|
-
max_tokens: 4096,
|
|
49503
|
-
messages: [{ role: "system", content: systemPrompt }, ...apiMessages]
|
|
49504
|
-
};
|
|
49505
|
-
}
|
|
49506
|
-
const controller = new AbortController;
|
|
49507
|
-
const timer = setTimeout(() => controller.abort(), 60000);
|
|
49508
|
-
abortSignal?.addEventListener("abort", () => controller.abort());
|
|
49509
|
-
const res = await fetch(url, {
|
|
49510
|
-
method: "POST",
|
|
49511
|
-
headers,
|
|
49512
|
-
body: JSON.stringify(body),
|
|
49513
|
-
signal: controller.signal
|
|
49514
|
-
});
|
|
49515
|
-
clearTimeout(timer);
|
|
49516
|
-
if (!res.ok)
|
|
49517
|
-
throw new Error(`API error ${res.status}: ${await res.text()}`);
|
|
49518
|
-
const data = await res.json();
|
|
49519
|
-
if (provider.type === "anthropic") {
|
|
49520
|
-
const content = data.content;
|
|
49521
|
-
return content.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
49522
|
-
} else {
|
|
49523
|
-
const choices = data.choices;
|
|
49524
|
-
return choices[0]?.message.content ?? "";
|
|
49525
|
-
}
|
|
49526
|
-
}
|
|
49527
|
-
var SKIP_DIRS4 = new Set([
|
|
49528
|
-
"node_modules",
|
|
49529
|
-
".git",
|
|
49530
|
-
"dist",
|
|
49531
|
-
"build",
|
|
49532
|
-
".next",
|
|
49533
|
-
"out",
|
|
49534
|
-
"coverage",
|
|
49535
|
-
"__pycache__",
|
|
49536
|
-
".venv",
|
|
49537
|
-
"venv"
|
|
49538
|
-
]);
|
|
49539
|
-
function walkDir3(dir, base2 = dir) {
|
|
49540
|
-
const results = [];
|
|
49541
|
-
let entries;
|
|
49542
|
-
try {
|
|
49543
|
-
entries = readdirSync3(dir, { encoding: "utf-8" });
|
|
49544
|
-
} catch {
|
|
49545
|
-
return results;
|
|
49546
|
-
}
|
|
49547
|
-
for (const entry of entries) {
|
|
49548
|
-
if (SKIP_DIRS4.has(entry))
|
|
49549
|
-
continue;
|
|
49550
|
-
const full = path14.join(dir, entry);
|
|
49551
|
-
const rel = path14.relative(base2, full).replace(/\\/g, "/");
|
|
49552
|
-
let isDir = false;
|
|
49553
|
-
try {
|
|
49554
|
-
isDir = statSync4(full).isDirectory();
|
|
49555
|
-
} catch {
|
|
49556
|
-
continue;
|
|
49557
|
-
}
|
|
49558
|
-
if (isDir)
|
|
49559
|
-
results.push(...walkDir3(full, base2));
|
|
49560
|
-
else
|
|
49561
|
-
results.push(rel);
|
|
49562
|
-
}
|
|
49563
|
-
return results;
|
|
49564
|
-
}
|
|
49565
|
-
function applyPatches3(repoPath, patches) {
|
|
49566
|
-
for (const patch of patches) {
|
|
49567
|
-
const fullPath = path14.join(repoPath, patch.path);
|
|
49568
|
-
const dir = path14.dirname(fullPath);
|
|
49569
|
-
if (!existsSync10(dir))
|
|
49570
|
-
mkdirSync4(dir, { recursive: true });
|
|
49571
|
-
writeFileSync6(fullPath, patch.content, "utf-8");
|
|
49572
|
-
}
|
|
49573
|
-
}
|
|
49574
|
-
async function runShell(command, cwd2) {
|
|
49575
|
-
return new Promise((resolve) => {
|
|
49576
|
-
const { spawn: spawn2 } = __require("child_process");
|
|
49577
|
-
const isWin = process.platform === "win32";
|
|
49578
|
-
const shell = isWin ? "cmd.exe" : "/bin/sh";
|
|
49579
|
-
const shellFlag = isWin ? "/c" : "-c";
|
|
49580
|
-
const proc = spawn2(shell, [shellFlag, command], {
|
|
49581
|
-
cwd: cwd2,
|
|
49582
|
-
env: process.env,
|
|
49583
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
49584
|
-
});
|
|
49585
|
-
const chunks = [];
|
|
49586
|
-
const errChunks = [];
|
|
49587
|
-
proc.stdout.on("data", (d) => chunks.push(d));
|
|
49588
|
-
proc.stderr.on("data", (d) => errChunks.push(d));
|
|
49589
|
-
const killTimer = setTimeout(() => {
|
|
49590
|
-
proc.kill();
|
|
49591
|
-
resolve("(command timed out after 5 minutes)");
|
|
49592
|
-
}, 5 * 60 * 1000);
|
|
49593
|
-
proc.on("close", (code) => {
|
|
49594
|
-
clearTimeout(killTimer);
|
|
49595
|
-
const stdout = Buffer.concat(chunks).toString("utf-8").trim();
|
|
49596
|
-
const stderr = Buffer.concat(errChunks).toString("utf-8").trim();
|
|
49597
|
-
const combined = [stdout, stderr].filter(Boolean).join(`
|
|
49598
|
-
`);
|
|
49599
|
-
resolve(combined || (code === 0 ? "(no output)" : `exit code ${code}`));
|
|
49600
|
-
});
|
|
49601
|
-
proc.on("error", (err) => {
|
|
49602
|
-
clearTimeout(killTimer);
|
|
49603
|
-
resolve(`Error: ${err.message}`);
|
|
49604
|
-
});
|
|
49605
|
-
});
|
|
49606
|
-
}
|
|
49607
|
-
function stripTags(html) {
|
|
49608
|
-
return html.replace(/<[^>]+>/g, " ").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/&#\d+;/g, " ").replace(/\s+/g, " ").trim();
|
|
49609
|
-
}
|
|
49610
|
-
function extractTables(html) {
|
|
49611
|
-
const tables = [];
|
|
49612
|
-
const tableRe = /<table[\s\S]*?<\/table>/gi;
|
|
49613
|
-
let tMatch;
|
|
49614
|
-
while ((tMatch = tableRe.exec(html)) !== null) {
|
|
49615
|
-
const tableHtml = tMatch[0];
|
|
49616
|
-
const rows = [];
|
|
49617
|
-
const rowRe = /<tr[\s\S]*?<\/tr>/gi;
|
|
49618
|
-
let rMatch;
|
|
49619
|
-
while ((rMatch = rowRe.exec(tableHtml)) !== null) {
|
|
49620
|
-
const cells = [];
|
|
49621
|
-
const cellRe = /<t[dh][^>]*>([\s\S]*?)<\/t[dh]>/gi;
|
|
49622
|
-
let cMatch;
|
|
49623
|
-
while ((cMatch = cellRe.exec(rMatch[0])) !== null) {
|
|
49624
|
-
cells.push(stripTags(cMatch[1] ?? ""));
|
|
49625
|
-
}
|
|
49626
|
-
if (cells.length > 0)
|
|
49627
|
-
rows.push(cells);
|
|
49628
|
-
}
|
|
49629
|
-
if (rows.length < 2)
|
|
49630
|
-
continue;
|
|
49631
|
-
const cols = Math.max(...rows.map((r) => r.length));
|
|
49632
|
-
const padded = rows.map((r) => {
|
|
49633
|
-
while (r.length < cols)
|
|
49634
|
-
r.push("");
|
|
49635
|
-
return r;
|
|
49636
|
-
});
|
|
49637
|
-
const widths = Array.from({ length: cols }, (_, ci) => Math.max(...padded.map((r) => (r[ci] ?? "").length), 3));
|
|
49638
|
-
const fmt = (r) => r.map((c, ci) => c.padEnd(widths[ci] ?? 0)).join(" | ");
|
|
49639
|
-
const header = fmt(padded[0]);
|
|
49640
|
-
const sep = widths.map((w) => "-".repeat(w)).join("-|-");
|
|
49641
|
-
const body = padded.slice(1).map(fmt).join(`
|
|
49642
|
-
`);
|
|
49643
|
-
tables.push(`${header}
|
|
49644
|
-
${sep}
|
|
49645
|
-
${body}`);
|
|
49646
|
-
}
|
|
49647
|
-
return tables.length > 0 ? `=== TABLES (${tables.length}) ===
|
|
49648
|
-
|
|
49649
|
-
${tables.join(`
|
|
49650
|
-
|
|
49651
|
-
---
|
|
49652
|
-
|
|
49653
|
-
`)}` : "";
|
|
49654
|
-
}
|
|
49655
|
-
function extractLists(html) {
|
|
49656
|
-
const lists = [];
|
|
49657
|
-
const listRe = /<[ou]l[\s\S]*?<\/[ou]l>/gi;
|
|
49658
|
-
let lMatch;
|
|
49659
|
-
while ((lMatch = listRe.exec(html)) !== null) {
|
|
49660
|
-
const items = [];
|
|
49661
|
-
const itemRe = /<li[^>]*>([\s\S]*?)<\/li>/gi;
|
|
49662
|
-
let iMatch;
|
|
49663
|
-
while ((iMatch = itemRe.exec(lMatch[0])) !== null) {
|
|
49664
|
-
const text = stripTags(iMatch[1] ?? "");
|
|
49665
|
-
if (text.length > 2)
|
|
49666
|
-
items.push(`• ${text}`);
|
|
49667
|
-
}
|
|
49668
|
-
if (items.length > 1)
|
|
49669
|
-
lists.push(items.join(`
|
|
49670
|
-
`));
|
|
49671
|
-
}
|
|
49672
|
-
return lists.length > 0 ? `=== LISTS ===
|
|
49673
|
-
|
|
49674
|
-
${lists.slice(0, 5).join(`
|
|
49675
|
-
|
|
49676
|
-
`)}` : "";
|
|
49677
|
-
}
|
|
49678
|
-
async function fetchUrl(url) {
|
|
49679
|
-
const res = await fetch(url, {
|
|
49680
|
-
headers: {
|
|
49681
|
-
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
49682
|
-
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
49683
|
-
"Accept-Language": "en-US,en;q=0.5"
|
|
49684
|
-
},
|
|
49685
|
-
signal: AbortSignal.timeout(15000)
|
|
49686
|
-
});
|
|
49687
|
-
if (!res.ok)
|
|
49688
|
-
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
49689
|
-
const contentType = res.headers.get("content-type") ?? "";
|
|
49690
|
-
if (contentType.includes("application/json")) {
|
|
49691
|
-
const json = await res.json();
|
|
49692
|
-
return JSON.stringify(json, null, 2).slice(0, 8000);
|
|
49693
|
-
}
|
|
49694
|
-
const html = await res.text();
|
|
49695
|
-
const titleMatch = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
49696
|
-
const title = titleMatch ? stripTags(titleMatch[1]) : "No title";
|
|
49697
|
-
const tables = extractTables(html);
|
|
49698
|
-
const lists = extractLists(html);
|
|
49699
|
-
const bodyText = stripTags(html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<nav[\s\S]*?<\/nav>/gi, "").replace(/<footer[\s\S]*?<\/footer>/gi, "").replace(/<header[\s\S]*?<\/header>/gi, "")).replace(/\s{3,}/g, `
|
|
49700
|
-
|
|
49701
|
-
`).slice(0, 3000);
|
|
49702
|
-
const parts = [`PAGE: ${title}`, `URL: ${url}`];
|
|
49703
|
-
if (tables)
|
|
49704
|
-
parts.push(tables);
|
|
49705
|
-
if (lists)
|
|
49706
|
-
parts.push(lists);
|
|
49707
|
-
parts.push(`=== TEXT ===
|
|
49708
|
-
${bodyText}`);
|
|
49709
|
-
return parts.join(`
|
|
49710
|
-
|
|
49711
|
-
`);
|
|
49712
|
-
}
|
|
49713
|
-
async function searchWeb(query) {
|
|
49714
|
-
const encoded = encodeURIComponent(query);
|
|
49715
|
-
const ddgUrl = `https://api.duckduckgo.com/?q=${encoded}&format=json&no_html=1&skip_disambig=1`;
|
|
49716
|
-
try {
|
|
49717
|
-
const res = await fetch(ddgUrl, {
|
|
49718
|
-
headers: { "User-Agent": "Lens/1.0" },
|
|
49719
|
-
signal: AbortSignal.timeout(8000)
|
|
49720
|
-
});
|
|
49721
|
-
if (res.ok) {
|
|
49722
|
-
const data = await res.json();
|
|
49723
|
-
const parts = [`Search: ${query}`];
|
|
49724
|
-
if (data.Answer)
|
|
49725
|
-
parts.push(`Answer: ${data.Answer}`);
|
|
49726
|
-
if (data.AbstractText) {
|
|
49727
|
-
parts.push(`Summary: ${data.AbstractText}`);
|
|
49728
|
-
if (data.AbstractURL)
|
|
49729
|
-
parts.push(`Source: ${data.AbstractURL}`);
|
|
49730
|
-
}
|
|
49731
|
-
if (data.Infobox?.content?.length) {
|
|
49732
|
-
const fields = data.Infobox.content.slice(0, 8).map((f) => ` ${f.label}: ${f.value}`).join(`
|
|
49733
|
-
`);
|
|
49734
|
-
parts.push(`Info:
|
|
49735
|
-
${fields}`);
|
|
49736
|
-
}
|
|
49737
|
-
if (data.RelatedTopics?.length) {
|
|
49738
|
-
const topics = data.RelatedTopics.filter((t) => t.Text).slice(0, 5).map((t) => ` - ${t.Text}`).join(`
|
|
49739
|
-
`);
|
|
49740
|
-
if (topics)
|
|
49741
|
-
parts.push(`Related:
|
|
49742
|
-
${topics}`);
|
|
49743
|
-
}
|
|
49744
|
-
const result2 = parts.join(`
|
|
49745
|
-
|
|
49746
|
-
`);
|
|
49747
|
-
if (result2.length > 60)
|
|
49748
|
-
return result2;
|
|
49749
|
-
}
|
|
49750
|
-
} catch {}
|
|
49751
|
-
try {
|
|
49752
|
-
const htmlUrl = `https://html.duckduckgo.com/html/?q=${encoded}`;
|
|
49753
|
-
const res = await fetch(htmlUrl, {
|
|
49754
|
-
headers: {
|
|
49755
|
-
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
49756
|
-
Accept: "text/html"
|
|
49757
|
-
},
|
|
49758
|
-
signal: AbortSignal.timeout(1e4)
|
|
49759
|
-
});
|
|
49760
|
-
if (!res.ok)
|
|
49761
|
-
throw new Error(`HTTP ${res.status}`);
|
|
49762
|
-
const html = await res.text();
|
|
49763
|
-
const snippets = [];
|
|
49764
|
-
const snippetRe = /class="result__snippet"[^>]*>([\s\S]*?)<\/a>/g;
|
|
49765
|
-
let m;
|
|
49766
|
-
while ((m = snippetRe.exec(html)) !== null && snippets.length < 6) {
|
|
49767
|
-
const text = m[1].replace(/<[^>]+>/g, " ").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/\s+/g, " ").trim();
|
|
49768
|
-
if (text.length > 20)
|
|
49769
|
-
snippets.push(`- ${text}`);
|
|
49770
|
-
}
|
|
49771
|
-
const links = [];
|
|
49772
|
-
const linkRe = /class="result__a"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/g;
|
|
49773
|
-
while ((m = linkRe.exec(html)) !== null && links.length < 5) {
|
|
49774
|
-
const title = m[2].replace(/<[^>]+>/g, "").trim();
|
|
49775
|
-
const href = m[1];
|
|
49776
|
-
if (title && href)
|
|
49777
|
-
links.push(` ${title} — ${href}`);
|
|
49778
|
-
}
|
|
49779
|
-
if (snippets.length === 0 && links.length === 0) {
|
|
49780
|
-
return `No results found for: ${query}`;
|
|
49781
|
-
}
|
|
49782
|
-
const parts = [`Search results for: ${query}`];
|
|
49783
|
-
if (snippets.length > 0)
|
|
49784
|
-
parts.push(`Snippets:
|
|
49785
|
-
${snippets.join(`
|
|
49786
|
-
`)}`);
|
|
49787
|
-
if (links.length > 0)
|
|
49788
|
-
parts.push(`Links:
|
|
49789
|
-
${links.join(`
|
|
49790
|
-
`)}`);
|
|
49791
|
-
return parts.join(`
|
|
49768
|
+
},
|
|
49769
|
+
{
|
|
49770
|
+
role: "assistant",
|
|
49771
|
+
content: `<write-file>
|
|
49772
|
+
{"path": "myapp/src/index.ts", "content": "console.log('hello')"}
|
|
49773
|
+
</write-file>`
|
|
49774
|
+
},
|
|
49775
|
+
{
|
|
49776
|
+
role: "user",
|
|
49777
|
+
content: `Here is the output from write-file to myapp/src/index.ts:
|
|
49792
49778
|
|
|
49793
|
-
|
|
49794
|
-
} catch (err) {
|
|
49795
|
-
return `Search failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
49796
|
-
}
|
|
49797
|
-
}
|
|
49798
|
-
function readFile(filePath, repoPath) {
|
|
49799
|
-
const candidates = path14.isAbsolute(filePath) ? [filePath] : [filePath, path14.join(repoPath, filePath)];
|
|
49800
|
-
for (const candidate of candidates) {
|
|
49801
|
-
if (existsSync10(candidate)) {
|
|
49802
|
-
try {
|
|
49803
|
-
const content = readFileSync10(candidate, "utf-8");
|
|
49804
|
-
const lines = content.split(`
|
|
49805
|
-
`).length;
|
|
49806
|
-
return `File: ${candidate} (${lines} lines)
|
|
49779
|
+
Written: /repo/myapp/src/index.ts (1 lines, 20 bytes)
|
|
49807
49780
|
|
|
49808
|
-
|
|
49781
|
+
Please continue your response based on this output.`
|
|
49782
|
+
},
|
|
49783
|
+
{
|
|
49784
|
+
role: "assistant",
|
|
49785
|
+
content: "Done — scaffolded `myapp/package.json` and `myapp/src/index.ts`. Run `cd myapp && npm install` to get started."
|
|
49786
|
+
},
|
|
49787
|
+
{
|
|
49788
|
+
role: "user",
|
|
49789
|
+
content: "remember that we always use bun in this project"
|
|
49790
|
+
},
|
|
49791
|
+
{
|
|
49792
|
+
role: "assistant",
|
|
49793
|
+
content: `<memory-add>Project uses bun as the package manager and runtime, not npm or node</memory-add>
|
|
49809
49794
|
|
|
49810
|
-
|
|
49811
|
-
|
|
49812
|
-
|
|
49813
|
-
|
|
49814
|
-
|
|
49795
|
+
Got it — I'll always use bun for this project.`
|
|
49796
|
+
},
|
|
49797
|
+
{
|
|
49798
|
+
role: "user",
|
|
49799
|
+
content: "forget the bun preference"
|
|
49800
|
+
},
|
|
49801
|
+
{
|
|
49802
|
+
role: "assistant",
|
|
49803
|
+
content: `<memory-delete>MEMORY_ID</memory-delete>
|
|
49804
|
+
|
|
49805
|
+
Done — removed that memory.`
|
|
49815
49806
|
}
|
|
49816
|
-
|
|
49817
|
-
|
|
49818
|
-
|
|
49819
|
-
|
|
49820
|
-
|
|
49821
|
-
|
|
49822
|
-
|
|
49823
|
-
continue;
|
|
49824
|
-
let stat;
|
|
49825
|
-
try {
|
|
49826
|
-
stat = statSync4(candidate);
|
|
49827
|
-
} catch {
|
|
49828
|
-
continue;
|
|
49829
|
-
}
|
|
49830
|
-
if (!stat.isDirectory()) {
|
|
49831
|
-
return `Not a directory: ${candidate}. Use read-file to read a file.`;
|
|
49832
|
-
}
|
|
49833
|
-
let entries;
|
|
49834
|
-
try {
|
|
49835
|
-
entries = readdirSync3(candidate, { encoding: "utf-8" });
|
|
49836
|
-
} catch (err) {
|
|
49837
|
-
return `Error reading folder: ${err instanceof Error ? err.message : String(err)}`;
|
|
49838
|
-
}
|
|
49839
|
-
const files = [];
|
|
49840
|
-
const subfolders = [];
|
|
49841
|
-
for (const entry of entries) {
|
|
49842
|
-
if (entry.startsWith(".") && entry !== ".env")
|
|
49843
|
-
continue;
|
|
49844
|
-
const full = path14.join(candidate, entry);
|
|
49845
|
-
try {
|
|
49846
|
-
if (statSync4(full).isDirectory()) {
|
|
49847
|
-
subfolders.push(`${entry}/`);
|
|
49848
|
-
} else {
|
|
49849
|
-
files.push(entry);
|
|
49850
|
-
}
|
|
49851
|
-
} catch {}
|
|
49852
|
-
}
|
|
49853
|
-
const total = files.length + subfolders.length;
|
|
49854
|
-
const lines = [`Folder: ${candidate} (${total} entries)`, ""];
|
|
49855
|
-
if (files.length > 0) {
|
|
49856
|
-
lines.push("Files:");
|
|
49857
|
-
files.forEach((f) => lines.push(` ${f}`));
|
|
49858
|
-
}
|
|
49859
|
-
if (subfolders.length > 0) {
|
|
49860
|
-
if (files.length > 0)
|
|
49861
|
-
lines.push("");
|
|
49862
|
-
lines.push("Subfolders:");
|
|
49863
|
-
subfolders.forEach((d) => lines.push(` ${d}`));
|
|
49807
|
+
];
|
|
49808
|
+
// src/utils/tools/registry.ts
|
|
49809
|
+
class ToolRegistry {
|
|
49810
|
+
tools = new Map;
|
|
49811
|
+
register(tool) {
|
|
49812
|
+
if (this.tools.has(tool.name)) {
|
|
49813
|
+
console.warn(`[ToolRegistry] Overwriting existing tool: "${tool.name}"`);
|
|
49864
49814
|
}
|
|
49865
|
-
|
|
49866
|
-
|
|
49815
|
+
this.tools.set(tool.name, tool);
|
|
49816
|
+
}
|
|
49817
|
+
unregister(name) {
|
|
49818
|
+
this.tools.delete(name);
|
|
49819
|
+
}
|
|
49820
|
+
get(name) {
|
|
49821
|
+
return this.tools.get(name);
|
|
49822
|
+
}
|
|
49823
|
+
all() {
|
|
49824
|
+
return Array.from(this.tools.values());
|
|
49825
|
+
}
|
|
49826
|
+
names() {
|
|
49827
|
+
return Array.from(this.tools.keys());
|
|
49828
|
+
}
|
|
49829
|
+
buildSystemPromptSection() {
|
|
49830
|
+
const lines = [`## TOOLS
|
|
49831
|
+
`];
|
|
49832
|
+
lines.push("You have exactly " + this.tools.size + ` tools. To use a tool you MUST wrap it in the exact XML tags shown below — no other format will work.
|
|
49833
|
+
`);
|
|
49834
|
+
let i = 1;
|
|
49835
|
+
for (const tool of this.tools.values()) {
|
|
49836
|
+
lines.push(tool.systemPromptEntry(i++));
|
|
49867
49837
|
}
|
|
49868
49838
|
return lines.join(`
|
|
49869
49839
|
`);
|
|
49870
49840
|
}
|
|
49871
|
-
return `Folder not found: ${sanitized}`;
|
|
49872
49841
|
}
|
|
49873
|
-
|
|
49874
|
-
|
|
49875
|
-
|
|
49876
|
-
|
|
49877
|
-
|
|
49878
|
-
|
|
49879
|
-
|
|
49880
|
-
|
|
49881
|
-
const
|
|
49882
|
-
|
|
49883
|
-
const
|
|
49884
|
-
|
|
49885
|
-
|
|
49886
|
-
|
|
49887
|
-
|
|
49888
|
-
|
|
49889
|
-
|
|
49890
|
-
|
|
49891
|
-
|
|
49892
|
-
|
|
49842
|
+
var registry = new ToolRegistry;
|
|
49843
|
+
|
|
49844
|
+
// src/utils/chat.ts
|
|
49845
|
+
function parseResponse(text) {
|
|
49846
|
+
const scanText = text.replace(/```[\s\S]*?```/g, (m) => " ".repeat(m.length));
|
|
49847
|
+
const candidates = [];
|
|
49848
|
+
for (const toolName2 of registry.names()) {
|
|
49849
|
+
const escaped = toolName2.replace(/[-]/g, "\\-");
|
|
49850
|
+
const xmlRe = new RegExp(`<${escaped}>([\\s\\S]*?)<\\/${escaped}>`, "g");
|
|
49851
|
+
xmlRe.lastIndex = 0;
|
|
49852
|
+
const xmlM = xmlRe.exec(scanText);
|
|
49853
|
+
if (xmlM) {
|
|
49854
|
+
const orig = new RegExp(xmlRe.source);
|
|
49855
|
+
const origM = orig.exec(text.slice(xmlM.index));
|
|
49856
|
+
if (origM) {
|
|
49857
|
+
candidates.push({
|
|
49858
|
+
index: xmlM.index,
|
|
49859
|
+
toolName: toolName2,
|
|
49860
|
+
match: Object.assign([
|
|
49861
|
+
text.slice(xmlM.index, xmlM.index + origM[0].length),
|
|
49862
|
+
origM[1]
|
|
49863
|
+
], { index: xmlM.index, input: text, groups: undefined })
|
|
49864
|
+
});
|
|
49893
49865
|
}
|
|
49894
|
-
|
|
49895
|
-
|
|
49896
|
-
|
|
49866
|
+
}
|
|
49867
|
+
const fencedRe = new RegExp(`\`\`\`${escaped}\\r?\\n([\\s\\S]*?)\\r?\\n\`\`\``, "g");
|
|
49868
|
+
fencedRe.lastIndex = 0;
|
|
49869
|
+
const fencedM = fencedRe.exec(scanText);
|
|
49870
|
+
if (fencedM) {
|
|
49871
|
+
const orig = new RegExp(fencedRe.source);
|
|
49872
|
+
const origM = orig.exec(text.slice(fencedM.index));
|
|
49873
|
+
if (origM) {
|
|
49874
|
+
candidates.push({
|
|
49875
|
+
index: fencedM.index,
|
|
49876
|
+
toolName: toolName2,
|
|
49877
|
+
match: Object.assign([
|
|
49878
|
+
text.slice(fencedM.index, fencedM.index + origM[0].length),
|
|
49879
|
+
origM[1]
|
|
49880
|
+
], { index: fencedM.index, input: text, groups: undefined })
|
|
49881
|
+
});
|
|
49897
49882
|
}
|
|
49898
|
-
|
|
49899
|
-
};
|
|
49900
|
-
};
|
|
49901
|
-
const filter2 = globToFilter(glob);
|
|
49902
|
-
const allFiles = walkDir3(repoPath);
|
|
49903
|
-
const matchedFiles = allFiles.filter(filter2);
|
|
49904
|
-
if (matchedFiles.length === 0) {
|
|
49905
|
-
return `No files matched glob: ${glob}`;
|
|
49883
|
+
}
|
|
49906
49884
|
}
|
|
49907
|
-
|
|
49908
|
-
|
|
49909
|
-
|
|
49910
|
-
|
|
49911
|
-
|
|
49885
|
+
if (candidates.length === 0)
|
|
49886
|
+
return { kind: "text", content: text.trim() };
|
|
49887
|
+
candidates.sort((a, b) => a.index - b.index);
|
|
49888
|
+
const { toolName, match } = candidates[0];
|
|
49889
|
+
const before2 = text.slice(0, match.index).trim();
|
|
49890
|
+
const body = (match[1] ?? "").trim();
|
|
49891
|
+
const afterMatch = text.slice(match.index + match[0].length).trim();
|
|
49892
|
+
const remainder = afterMatch.length > 0 ? afterMatch : undefined;
|
|
49893
|
+
if (toolName === "changes") {
|
|
49912
49894
|
try {
|
|
49913
|
-
|
|
49914
|
-
|
|
49915
|
-
|
|
49916
|
-
}
|
|
49917
|
-
const lines = content.split(`
|
|
49895
|
+
const parsed = JSON.parse(body);
|
|
49896
|
+
const display = [before2, parsed.summary].filter(Boolean).join(`
|
|
49897
|
+
|
|
49918
49898
|
`);
|
|
49919
|
-
|
|
49920
|
-
|
|
49921
|
-
|
|
49922
|
-
|
|
49923
|
-
|
|
49924
|
-
}
|
|
49925
|
-
}
|
|
49926
|
-
|
|
49927
|
-
results.push(`${relPath}
|
|
49928
|
-
${fileMatches.join(`
|
|
49929
|
-
`)}`);
|
|
49930
|
-
}
|
|
49931
|
-
if (totalMatches >= 200) {
|
|
49932
|
-
results.push("(truncated — too many matches)");
|
|
49933
|
-
break;
|
|
49899
|
+
return {
|
|
49900
|
+
kind: "changes",
|
|
49901
|
+
content: display,
|
|
49902
|
+
patches: parsed.patches,
|
|
49903
|
+
remainder
|
|
49904
|
+
};
|
|
49905
|
+
} catch {
|
|
49906
|
+
return { kind: "text", content: text.trim() };
|
|
49934
49907
|
}
|
|
49935
49908
|
}
|
|
49936
|
-
if (
|
|
49937
|
-
return
|
|
49909
|
+
if (toolName === "clone") {
|
|
49910
|
+
return {
|
|
49911
|
+
kind: "clone",
|
|
49912
|
+
content: before2,
|
|
49913
|
+
repoUrl: body.replace(/^<|>$/g, "").trim(),
|
|
49914
|
+
remainder
|
|
49915
|
+
};
|
|
49938
49916
|
}
|
|
49939
|
-
|
|
49917
|
+
const tool = registry.get(toolName);
|
|
49918
|
+
if (!tool)
|
|
49919
|
+
return { kind: "text", content: text.trim() };
|
|
49920
|
+
const input = tool.parseInput(body);
|
|
49921
|
+
if (input === null)
|
|
49922
|
+
return { kind: "text", content: text.trim() };
|
|
49923
|
+
return {
|
|
49924
|
+
kind: "tool",
|
|
49925
|
+
toolName,
|
|
49926
|
+
input,
|
|
49927
|
+
rawInput: body,
|
|
49928
|
+
content: before2,
|
|
49929
|
+
remainder
|
|
49930
|
+
};
|
|
49931
|
+
}
|
|
49932
|
+
function extractGithubUrl(text) {
|
|
49933
|
+
const match = text.match(/https?:\/\/github\.com\/[\w.-]+\/[\w.-]+/);
|
|
49934
|
+
return match ? match[0] : null;
|
|
49935
|
+
}
|
|
49936
|
+
function toCloneUrl(url) {
|
|
49937
|
+
const clean = url.replace(/\/+$/, "");
|
|
49938
|
+
return clean.endsWith(".git") ? clean : `${clean}.git`;
|
|
49939
|
+
}
|
|
49940
|
+
function buildApiMessages(messages) {
|
|
49941
|
+
return messages.map((m) => {
|
|
49942
|
+
if (m.type === "tool") {
|
|
49943
|
+
if (!m.approved) {
|
|
49944
|
+
return {
|
|
49945
|
+
role: "user",
|
|
49946
|
+
content: "The tool call was denied by the user. Please respond without using that tool."
|
|
49947
|
+
};
|
|
49948
|
+
}
|
|
49949
|
+
return {
|
|
49950
|
+
role: "user",
|
|
49951
|
+
content: `Here is the output from the ${m.toolName} of ${m.content}:
|
|
49940
49952
|
|
|
49941
|
-
${
|
|
49953
|
+
${m.result}
|
|
49942
49954
|
|
|
49943
|
-
|
|
49955
|
+
Please continue your response based on this output.`
|
|
49956
|
+
};
|
|
49957
|
+
}
|
|
49958
|
+
return { role: m.role, content: m.content };
|
|
49959
|
+
});
|
|
49944
49960
|
}
|
|
49945
|
-
function
|
|
49946
|
-
const
|
|
49947
|
-
|
|
49948
|
-
|
|
49949
|
-
|
|
49950
|
-
|
|
49951
|
-
|
|
49952
|
-
|
|
49953
|
-
|
|
49954
|
-
|
|
49955
|
-
|
|
49956
|
-
|
|
49961
|
+
async function callChat(provider, systemPrompt, messages, abortSignal) {
|
|
49962
|
+
const apiMessages = [...FEW_SHOT_MESSAGES, ...buildApiMessages(messages)];
|
|
49963
|
+
let url;
|
|
49964
|
+
let headers;
|
|
49965
|
+
let body;
|
|
49966
|
+
if (provider.type === "anthropic") {
|
|
49967
|
+
url = "https://api.anthropic.com/v1/messages";
|
|
49968
|
+
headers = {
|
|
49969
|
+
"Content-Type": "application/json",
|
|
49970
|
+
"x-api-key": provider.apiKey ?? "",
|
|
49971
|
+
"anthropic-version": "2023-06-01"
|
|
49972
|
+
};
|
|
49973
|
+
body = {
|
|
49974
|
+
model: provider.model,
|
|
49975
|
+
max_tokens: 4096,
|
|
49976
|
+
system: systemPrompt,
|
|
49977
|
+
messages: apiMessages
|
|
49978
|
+
};
|
|
49979
|
+
} else {
|
|
49980
|
+
const base2 = provider.baseUrl ?? "https://api.openai.com/v1";
|
|
49981
|
+
url = `${base2}/chat/completions`;
|
|
49982
|
+
headers = {
|
|
49983
|
+
"Content-Type": "application/json",
|
|
49984
|
+
Authorization: `Bearer ${provider.apiKey}`
|
|
49985
|
+
};
|
|
49986
|
+
body = {
|
|
49987
|
+
model: provider.model,
|
|
49988
|
+
max_tokens: 4096,
|
|
49989
|
+
messages: [{ role: "system", content: systemPrompt }, ...apiMessages]
|
|
49990
|
+
};
|
|
49991
|
+
}
|
|
49992
|
+
const controller = new AbortController;
|
|
49993
|
+
const timer = setTimeout(() => controller.abort(), 60000);
|
|
49994
|
+
abortSignal?.addEventListener("abort", () => controller.abort());
|
|
49995
|
+
const res = await fetch(url, {
|
|
49996
|
+
method: "POST",
|
|
49997
|
+
headers,
|
|
49998
|
+
body: JSON.stringify(body),
|
|
49999
|
+
signal: controller.signal
|
|
50000
|
+
});
|
|
50001
|
+
clearTimeout(timer);
|
|
50002
|
+
if (!res.ok)
|
|
50003
|
+
throw new Error(`API error ${res.status}: ${await res.text()}`);
|
|
50004
|
+
const data = await res.json();
|
|
50005
|
+
if (provider.type === "anthropic") {
|
|
50006
|
+
const content = data.content;
|
|
50007
|
+
return content.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
50008
|
+
} else {
|
|
50009
|
+
const choices = data.choices;
|
|
50010
|
+
return choices[0]?.message.content ?? "";
|
|
49957
50011
|
}
|
|
49958
50012
|
}
|
|
49959
|
-
|
|
49960
|
-
|
|
49961
|
-
|
|
49962
|
-
|
|
49963
|
-
|
|
49964
|
-
|
|
49965
|
-
|
|
49966
|
-
|
|
49967
|
-
|
|
49968
|
-
|
|
49969
|
-
|
|
50013
|
+
|
|
50014
|
+
// src/utils/chatHistory.ts
|
|
50015
|
+
import {
|
|
50016
|
+
existsSync as existsSync12,
|
|
50017
|
+
mkdirSync as mkdirSync6,
|
|
50018
|
+
readFileSync as readFileSync11,
|
|
50019
|
+
readdirSync as readdirSync4,
|
|
50020
|
+
writeFileSync as writeFileSync8,
|
|
50021
|
+
unlinkSync
|
|
50022
|
+
} from "fs";
|
|
50023
|
+
import path16 from "path";
|
|
50024
|
+
import os7 from "os";
|
|
50025
|
+
var LENS_DIR = path16.join(os7.homedir(), ".lens");
|
|
50026
|
+
var CHATS_DIR = path16.join(LENS_DIR, "chats");
|
|
50027
|
+
function ensureChatsDir() {
|
|
50028
|
+
if (!existsSync12(CHATS_DIR))
|
|
50029
|
+
mkdirSync6(CHATS_DIR, { recursive: true });
|
|
49970
50030
|
}
|
|
49971
|
-
function
|
|
49972
|
-
const
|
|
49973
|
-
|
|
49974
|
-
if (!existsSync10(fullPath))
|
|
49975
|
-
return `Folder not found: ${fullPath}`;
|
|
49976
|
-
const { rmSync } = __require("fs");
|
|
49977
|
-
rmSync(fullPath, { recursive: true, force: true });
|
|
49978
|
-
return `Deleted folder: ${fullPath}`;
|
|
49979
|
-
} catch (err) {
|
|
49980
|
-
return `Error deleting folder: ${err instanceof Error ? err.message : String(err)}`;
|
|
49981
|
-
}
|
|
50031
|
+
function chatFilePath(name) {
|
|
50032
|
+
const safe = name.replace(/[^a-z0-9_-]/gi, "-").toLowerCase();
|
|
50033
|
+
return path16.join(CHATS_DIR, `${safe}.json`);
|
|
49982
50034
|
}
|
|
49983
|
-
function
|
|
50035
|
+
function saveChat(name, repoPath, messages) {
|
|
50036
|
+
ensureChatsDir();
|
|
50037
|
+
const data = {
|
|
50038
|
+
name,
|
|
50039
|
+
repoPath,
|
|
50040
|
+
messages,
|
|
50041
|
+
savedAt: new Date().toISOString(),
|
|
50042
|
+
userMessageCount: messages.filter((m) => m.role === "user").length
|
|
50043
|
+
};
|
|
50044
|
+
writeFileSync8(chatFilePath(name), JSON.stringify(data, null, 2), "utf-8");
|
|
50045
|
+
}
|
|
50046
|
+
function loadChat(name) {
|
|
50047
|
+
const filePath = chatFilePath(name);
|
|
50048
|
+
if (!existsSync12(filePath))
|
|
50049
|
+
return null;
|
|
49984
50050
|
try {
|
|
49985
|
-
|
|
49986
|
-
|
|
49987
|
-
|
|
49988
|
-
execSync2(`start "" "${url}"`, { stdio: "ignore" });
|
|
49989
|
-
} else if (platform2 === "darwin") {
|
|
49990
|
-
execSync2(`open "${url}"`, { stdio: "ignore" });
|
|
49991
|
-
} else {
|
|
49992
|
-
execSync2(`xdg-open "${url}"`, { stdio: "ignore" });
|
|
49993
|
-
}
|
|
49994
|
-
return `Opened: ${url}`;
|
|
49995
|
-
} catch (err) {
|
|
49996
|
-
return `Error opening URL: ${err instanceof Error ? err.message : String(err)}`;
|
|
50051
|
+
return JSON.parse(readFileSync11(filePath, "utf-8"));
|
|
50052
|
+
} catch {
|
|
50053
|
+
return null;
|
|
49997
50054
|
}
|
|
49998
50055
|
}
|
|
49999
|
-
function
|
|
50000
|
-
|
|
50001
|
-
|
|
50002
|
-
|
|
50003
|
-
|
|
50004
|
-
mkdirSync4(dir, { recursive: true });
|
|
50005
|
-
const escaped = content.replace(/\\/g, "\\\\").replace(/"""/g, "\\\"\\\"\\\"").replace(/\r/g, "");
|
|
50006
|
-
const script = `
|
|
50007
|
-
import sys
|
|
50008
|
-
try:
|
|
50009
|
-
from reportlab.lib.pagesizes import letter
|
|
50010
|
-
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, HRFlowable
|
|
50011
|
-
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
|
50012
|
-
from reportlab.lib.units import inch
|
|
50013
|
-
from reportlab.lib import colors
|
|
50014
|
-
except ImportError:
|
|
50015
|
-
import subprocess
|
|
50016
|
-
subprocess.check_call([sys.executable, "-m", "pip", "install", "reportlab", "--break-system-packages", "-q"])
|
|
50017
|
-
from reportlab.lib.pagesizes import letter
|
|
50018
|
-
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, HRFlowable
|
|
50019
|
-
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
|
50020
|
-
from reportlab.lib.units import inch
|
|
50021
|
-
from reportlab.lib import colors
|
|
50022
|
-
|
|
50023
|
-
doc = SimpleDocTemplate(
|
|
50024
|
-
r"""${fullPath}""",
|
|
50025
|
-
pagesize=letter,
|
|
50026
|
-
rightMargin=inch,
|
|
50027
|
-
leftMargin=inch,
|
|
50028
|
-
topMargin=inch,
|
|
50029
|
-
bottomMargin=inch,
|
|
50030
|
-
)
|
|
50031
|
-
|
|
50032
|
-
styles = getSampleStyleSheet()
|
|
50033
|
-
styles.add(ParagraphStyle(name="H1", parent=styles["Heading1"], fontSize=22, spaceAfter=10))
|
|
50034
|
-
styles.add(ParagraphStyle(name="H2", parent=styles["Heading2"], fontSize=16, spaceAfter=8))
|
|
50035
|
-
styles.add(ParagraphStyle(name="H3", parent=styles["Heading3"], fontSize=13, spaceAfter=6))
|
|
50036
|
-
styles.add(ParagraphStyle(name="Body", parent=styles["Normal"], fontSize=11, leading=16, spaceAfter=8))
|
|
50037
|
-
styles.add(ParagraphStyle(name="Bullet", parent=styles["Normal"], fontSize=11, leading=16, leftIndent=20, spaceAfter=4, bulletIndent=10))
|
|
50038
|
-
|
|
50039
|
-
raw = """${escaped}"""
|
|
50040
|
-
|
|
50041
|
-
story = []
|
|
50042
|
-
for line in raw.split("\\n"):
|
|
50043
|
-
s = line.rstrip()
|
|
50044
|
-
if s.startswith("### "):
|
|
50045
|
-
story.append(Paragraph(s[4:], styles["H3"]))
|
|
50046
|
-
elif s.startswith("## "):
|
|
50047
|
-
story.append(Spacer(1, 6))
|
|
50048
|
-
story.append(Paragraph(s[3:], styles["H2"]))
|
|
50049
|
-
story.append(HRFlowable(width="100%", thickness=0.5, color=colors.grey, spaceAfter=4))
|
|
50050
|
-
elif s.startswith("# "):
|
|
50051
|
-
story.append(Paragraph(s[2:], styles["H1"]))
|
|
50052
|
-
story.append(HRFlowable(width="100%", thickness=1, color=colors.black, spaceAfter=6))
|
|
50053
|
-
elif s.startswith("- ") or s.startswith("* "):
|
|
50054
|
-
story.append(Paragraph(u"\\u2022 " + s[2:], styles["Bullet"]))
|
|
50055
|
-
elif s.startswith("---"):
|
|
50056
|
-
story.append(HRFlowable(width="100%", thickness=0.5, color=colors.grey, spaceAfter=4))
|
|
50057
|
-
elif s == "":
|
|
50058
|
-
story.append(Spacer(1, 6))
|
|
50059
|
-
else:
|
|
50060
|
-
import re
|
|
50061
|
-
s = re.sub(r"\\*\\*(.+?)\\*\\*", r"<b>\\1</b>", s)
|
|
50062
|
-
s = re.sub(r"\\*(.+?)\\*", r"<i>\\1</i>", s)
|
|
50063
|
-
s = re.sub(r"\`(.+?)\`", r"<font name='Courier'>\\1</font>", s)
|
|
50064
|
-
story.append(Paragraph(s, styles["Body"]))
|
|
50065
|
-
|
|
50066
|
-
doc.build(story)
|
|
50067
|
-
print("OK")
|
|
50068
|
-
`.replace("${fullPath}", fullPath.replace(/\\/g, "/")).replace("${escaped}", escaped);
|
|
50069
|
-
const os6 = __require("os");
|
|
50070
|
-
const tmpFile = path14.join(os6.tmpdir(), `lens_pdf_${Date.now()}.py`);
|
|
50071
|
-
writeFileSync6(tmpFile, script, "utf-8");
|
|
50072
|
-
const { execSync: execSync2 } = __require("child_process");
|
|
50073
|
-
execSync2(`python "${tmpFile}"`, { stdio: "pipe" });
|
|
50056
|
+
function listChats(repoPath) {
|
|
50057
|
+
ensureChatsDir();
|
|
50058
|
+
const files = readdirSync4(CHATS_DIR).filter((f) => f.endsWith(".json"));
|
|
50059
|
+
const chats = [];
|
|
50060
|
+
for (const file of files) {
|
|
50074
50061
|
try {
|
|
50075
|
-
|
|
50062
|
+
const data = JSON.parse(readFileSync11(path16.join(CHATS_DIR, file), "utf-8"));
|
|
50063
|
+
if (!repoPath || data.repoPath === repoPath)
|
|
50064
|
+
chats.push(data);
|
|
50076
50065
|
} catch {}
|
|
50077
|
-
return `PDF generated: ${fullPath}`;
|
|
50078
|
-
} catch (err) {
|
|
50079
|
-
return `Error generating PDF: ${err instanceof Error ? err.message : String(err)}`;
|
|
50080
50066
|
}
|
|
50067
|
+
return chats.sort((a, b) => new Date(b.savedAt).getTime() - new Date(a.savedAt).getTime());
|
|
50068
|
+
}
|
|
50069
|
+
function deleteChat(name) {
|
|
50070
|
+
const filePath = chatFilePath(name);
|
|
50071
|
+
if (!existsSync12(filePath))
|
|
50072
|
+
return false;
|
|
50073
|
+
try {
|
|
50074
|
+
unlinkSync(filePath);
|
|
50075
|
+
return true;
|
|
50076
|
+
} catch {
|
|
50077
|
+
return false;
|
|
50078
|
+
}
|
|
50079
|
+
}
|
|
50080
|
+
function getChatNameSuggestions(messages) {
|
|
50081
|
+
const userMsgs = messages.filter((m) => m.role === "user").map((m) => m.content.toLowerCase().trim());
|
|
50082
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
50083
|
+
if (userMsgs.length === 0) {
|
|
50084
|
+
return [`chat-${date}`, `session-${date}`, `new-chat`];
|
|
50085
|
+
}
|
|
50086
|
+
const suggestions = [];
|
|
50087
|
+
const toSlug = (s) => s.replace(/[^a-z0-9\s]/g, "").split(/\s+/).filter(Boolean).slice(0, 4).join("-");
|
|
50088
|
+
const firstSlug = toSlug(userMsgs[0]);
|
|
50089
|
+
if (firstSlug)
|
|
50090
|
+
suggestions.push(firstSlug);
|
|
50091
|
+
if (userMsgs.length > 1) {
|
|
50092
|
+
const lastSlug = toSlug(userMsgs[userMsgs.length - 1]);
|
|
50093
|
+
if (lastSlug && lastSlug !== firstSlug)
|
|
50094
|
+
suggestions.push(lastSlug);
|
|
50095
|
+
}
|
|
50096
|
+
suggestions.push(`session-${date}`);
|
|
50097
|
+
return suggestions.slice(0, 3);
|
|
50081
50098
|
}
|
|
50082
50099
|
|
|
50083
50100
|
// src/components/chat/ChatMessage.tsx
|
|
@@ -50182,6 +50199,19 @@ function MessageBody({ content }) {
|
|
|
50182
50199
|
})
|
|
50183
50200
|
}, undefined, false, undefined, this);
|
|
50184
50201
|
}
|
|
50202
|
+
function summarizeToolContent(toolName, content) {
|
|
50203
|
+
if (toolName === "write-file" || toolName === "read-file") {
|
|
50204
|
+
const pathMatch = content.match(/"path"\s*:\s*"([^"]+)"/);
|
|
50205
|
+
if (pathMatch)
|
|
50206
|
+
return pathMatch[1];
|
|
50207
|
+
}
|
|
50208
|
+
if (content.includes('"summary"')) {
|
|
50209
|
+
const summaryMatch = content.match(/"summary"\s*:\s*"([^"]+)"/);
|
|
50210
|
+
if (summaryMatch)
|
|
50211
|
+
return summaryMatch[1];
|
|
50212
|
+
}
|
|
50213
|
+
return content.length > 120 ? content.slice(0, 120) + "…" : content;
|
|
50214
|
+
}
|
|
50185
50215
|
function StaticMessage({ msg }) {
|
|
50186
50216
|
if (msg.role === "user") {
|
|
50187
50217
|
return /* @__PURE__ */ jsx_dev_runtime19.jsxDEV(Box_default, {
|
|
@@ -50212,7 +50242,7 @@ function StaticMessage({ msg }) {
|
|
|
50212
50242
|
search: "?"
|
|
50213
50243
|
};
|
|
50214
50244
|
const icon = icons[msg.toolName] ?? "·";
|
|
50215
|
-
const label = msg.toolName === "shell" ? msg.content : msg.toolName === "search" ? `"${msg.content}"` : msg.content;
|
|
50245
|
+
const label = msg.toolName === "shell" ? msg.content : msg.toolName === "search" ? `"${msg.content}"` : summarizeToolContent(msg.toolName, msg.content);
|
|
50216
50246
|
return /* @__PURE__ */ jsx_dev_runtime19.jsxDEV(Box_default, {
|
|
50217
50247
|
flexDirection: "column",
|
|
50218
50248
|
marginBottom: 1,
|
|
@@ -50417,50 +50447,71 @@ function PermissionPrompt({
|
|
|
50417
50447
|
let icon;
|
|
50418
50448
|
let label;
|
|
50419
50449
|
let value;
|
|
50420
|
-
if (
|
|
50421
|
-
|
|
50422
|
-
|
|
50423
|
-
|
|
50424
|
-
|
|
50425
|
-
|
|
50426
|
-
|
|
50427
|
-
|
|
50428
|
-
|
|
50429
|
-
|
|
50430
|
-
|
|
50431
|
-
|
|
50432
|
-
|
|
50433
|
-
|
|
50434
|
-
|
|
50435
|
-
|
|
50436
|
-
|
|
50437
|
-
|
|
50438
|
-
|
|
50439
|
-
value = `${tool.pattern} ${tool.glob}`;
|
|
50440
|
-
} else if (tool.type === "delete-file") {
|
|
50441
|
-
icon = "x";
|
|
50442
|
-
label = "delete";
|
|
50443
|
-
value = tool.filePath;
|
|
50444
|
-
} else if (tool.type === "delete-folder") {
|
|
50445
|
-
icon = "X";
|
|
50446
|
-
label = "delete folder";
|
|
50447
|
-
value = tool.folderPath;
|
|
50448
|
-
} else if (tool.type === "open-url") {
|
|
50449
|
-
icon = "↗";
|
|
50450
|
-
label = "open";
|
|
50451
|
-
value = tool.url;
|
|
50452
|
-
} else if (tool.type === "generate-pdf") {
|
|
50453
|
-
icon = "P";
|
|
50454
|
-
label = "pdf";
|
|
50455
|
-
value = tool.filePath;
|
|
50456
|
-
} else if (tool.type === "write-file") {
|
|
50457
|
-
icon = "w";
|
|
50458
|
-
label = "write";
|
|
50459
|
-
value = `${tool.filePath} (${tool.fileContent.length} bytes)`;
|
|
50450
|
+
if ("_label" in tool) {
|
|
50451
|
+
const iconMap = {
|
|
50452
|
+
run: "$",
|
|
50453
|
+
fetch: "~>",
|
|
50454
|
+
read: "r",
|
|
50455
|
+
write: "w",
|
|
50456
|
+
delete: "x",
|
|
50457
|
+
"delete folder": "X",
|
|
50458
|
+
open: "↗",
|
|
50459
|
+
pdf: "P",
|
|
50460
|
+
search: "?",
|
|
50461
|
+
folder: "d",
|
|
50462
|
+
grep: "/",
|
|
50463
|
+
clone: "↓",
|
|
50464
|
+
query: "⌗"
|
|
50465
|
+
};
|
|
50466
|
+
icon = iconMap[tool._label] ?? "·";
|
|
50467
|
+
label = tool._label;
|
|
50468
|
+
value = tool._display;
|
|
50460
50469
|
} else {
|
|
50461
|
-
|
|
50462
|
-
|
|
50463
|
-
|
|
50470
|
+
if (tool.type === "shell") {
|
|
50471
|
+
icon = "$";
|
|
50472
|
+
label = "run";
|
|
50473
|
+
value = tool.command;
|
|
50474
|
+
} else if (tool.type === "fetch") {
|
|
50475
|
+
icon = "~>";
|
|
50476
|
+
label = "fetch";
|
|
50477
|
+
value = tool.url;
|
|
50478
|
+
} else if (tool.type === "read-file") {
|
|
50479
|
+
icon = "r";
|
|
50480
|
+
label = "read";
|
|
50481
|
+
value = tool.filePath;
|
|
50482
|
+
} else if (tool.type === "read-folder") {
|
|
50483
|
+
icon = "d";
|
|
50484
|
+
label = "folder";
|
|
50485
|
+
value = tool.folderPath;
|
|
50486
|
+
} else if (tool.type === "grep") {
|
|
50487
|
+
icon = "/";
|
|
50488
|
+
label = "grep";
|
|
50489
|
+
value = `${tool.pattern} ${tool.glob}`;
|
|
50490
|
+
} else if (tool.type === "delete-file") {
|
|
50491
|
+
icon = "x";
|
|
50492
|
+
label = "delete";
|
|
50493
|
+
value = tool.filePath;
|
|
50494
|
+
} else if (tool.type === "delete-folder") {
|
|
50495
|
+
icon = "X";
|
|
50496
|
+
label = "delete folder";
|
|
50497
|
+
value = tool.folderPath;
|
|
50498
|
+
} else if (tool.type === "open-url") {
|
|
50499
|
+
icon = "↗";
|
|
50500
|
+
label = "open";
|
|
50501
|
+
value = tool.url;
|
|
50502
|
+
} else if (tool.type === "generate-pdf") {
|
|
50503
|
+
icon = "P";
|
|
50504
|
+
label = "pdf";
|
|
50505
|
+
value = tool.filePath;
|
|
50506
|
+
} else if (tool.type === "write-file") {
|
|
50507
|
+
icon = "w";
|
|
50508
|
+
label = "write";
|
|
50509
|
+
value = `${tool.filePath} (${tool.fileContent.length} bytes)`;
|
|
50510
|
+
} else {
|
|
50511
|
+
icon = "?";
|
|
50512
|
+
label = "search";
|
|
50513
|
+
value = tool.query ?? "";
|
|
50514
|
+
}
|
|
50464
50515
|
}
|
|
50465
50516
|
return /* @__PURE__ */ jsx_dev_runtime20.jsxDEV(Box_default, {
|
|
50466
50517
|
flexDirection: "column",
|
|
@@ -50497,7 +50548,8 @@ function PermissionPrompt({
|
|
|
50497
50548
|
function InputBox({
|
|
50498
50549
|
value,
|
|
50499
50550
|
onChange,
|
|
50500
|
-
onSubmit
|
|
50551
|
+
onSubmit,
|
|
50552
|
+
inputKey
|
|
50501
50553
|
}) {
|
|
50502
50554
|
return /* @__PURE__ */ jsx_dev_runtime20.jsxDEV(Box_default, {
|
|
50503
50555
|
marginTop: 1,
|
|
@@ -50518,7 +50570,7 @@ function InputBox({
|
|
|
50518
50570
|
value,
|
|
50519
50571
|
onChange,
|
|
50520
50572
|
onSubmit
|
|
50521
|
-
},
|
|
50573
|
+
}, inputKey, false, undefined, this)
|
|
50522
50574
|
]
|
|
50523
50575
|
}, undefined, true, undefined, this)
|
|
50524
50576
|
}, undefined, false, undefined, this);
|
|
@@ -52283,53 +52335,96 @@ function TimelineRunner({
|
|
|
52283
52335
|
}, undefined, true, undefined, this);
|
|
52284
52336
|
}
|
|
52285
52337
|
|
|
52286
|
-
// src/utils/
|
|
52287
|
-
import { existsSync as
|
|
52288
|
-
import
|
|
52289
|
-
import
|
|
52290
|
-
var
|
|
52291
|
-
var
|
|
52292
|
-
function
|
|
52293
|
-
if (!
|
|
52294
|
-
return { entries: [] };
|
|
52338
|
+
// src/utils/memory.ts
|
|
52339
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "fs";
|
|
52340
|
+
import path17 from "path";
|
|
52341
|
+
import os8 from "os";
|
|
52342
|
+
var LENS_DIR2 = path17.join(os8.homedir(), ".lens");
|
|
52343
|
+
var MEMORY_PATH = path17.join(LENS_DIR2, "memory.json");
|
|
52344
|
+
function loadMemoryFile() {
|
|
52345
|
+
if (!existsSync13(MEMORY_PATH))
|
|
52346
|
+
return { entries: [], memories: [] };
|
|
52295
52347
|
try {
|
|
52296
|
-
|
|
52348
|
+
const data = JSON.parse(readFileSync12(MEMORY_PATH, "utf-8"));
|
|
52349
|
+
return {
|
|
52350
|
+
entries: data.entries ?? [],
|
|
52351
|
+
memories: data.memories ?? []
|
|
52352
|
+
};
|
|
52297
52353
|
} catch {
|
|
52298
|
-
return { entries: [] };
|
|
52354
|
+
return { entries: [], memories: [] };
|
|
52299
52355
|
}
|
|
52300
52356
|
}
|
|
52301
|
-
function
|
|
52302
|
-
if (!
|
|
52303
|
-
|
|
52304
|
-
|
|
52357
|
+
function saveMemoryFile(m) {
|
|
52358
|
+
if (!existsSync13(LENS_DIR2))
|
|
52359
|
+
mkdirSync7(LENS_DIR2, { recursive: true });
|
|
52360
|
+
writeFileSync9(MEMORY_PATH, JSON.stringify(m, null, 2), "utf-8");
|
|
52305
52361
|
}
|
|
52306
|
-
function
|
|
52307
|
-
const
|
|
52308
|
-
|
|
52309
|
-
if (
|
|
52310
|
-
|
|
52311
|
-
|
|
52362
|
+
function appendMemory(entry) {
|
|
52363
|
+
const m = loadMemoryFile();
|
|
52364
|
+
m.entries.push({ ...entry, timestamp: new Date().toISOString() });
|
|
52365
|
+
if (m.entries.length > 500)
|
|
52366
|
+
m.entries = m.entries.slice(-500);
|
|
52367
|
+
saveMemoryFile(m);
|
|
52312
52368
|
}
|
|
52313
|
-
function
|
|
52314
|
-
const
|
|
52315
|
-
const relevant =
|
|
52316
|
-
|
|
52317
|
-
|
|
52318
|
-
|
|
52319
|
-
|
|
52320
|
-
|
|
52321
|
-
|
|
52322
|
-
|
|
52369
|
+
function buildMemorySummary(repoPath) {
|
|
52370
|
+
const m = loadMemoryFile();
|
|
52371
|
+
const relevant = m.entries.filter((e) => e.repoPath === repoPath).slice(-50);
|
|
52372
|
+
const memories = m.memories.filter((mem) => mem.repoPath === repoPath);
|
|
52373
|
+
const parts = [];
|
|
52374
|
+
if (memories.length > 0) {
|
|
52375
|
+
parts.push(`## MEMORIES ABOUT THIS REPO
|
|
52376
|
+
|
|
52377
|
+
${memories.map((mem) => `- [${mem.id}] ${mem.content}`).join(`
|
|
52378
|
+
`)}`);
|
|
52379
|
+
}
|
|
52380
|
+
if (relevant.length > 0) {
|
|
52381
|
+
const lines = relevant.map((e) => {
|
|
52382
|
+
const ts = new Date(e.timestamp).toLocaleString();
|
|
52383
|
+
return `[${ts}] ${e.kind}: ${e.detail} — ${e.summary}`;
|
|
52384
|
+
});
|
|
52385
|
+
parts.push(`## WHAT YOU HAVE ALREADY DONE IN THIS REPO
|
|
52323
52386
|
|
|
52324
52387
|
The following actions have already been completed. Do NOT repeat them unless the user explicitly asks you to redo something:
|
|
52325
52388
|
|
|
52326
52389
|
${lines.join(`
|
|
52327
|
-
`)}
|
|
52390
|
+
`)}`);
|
|
52391
|
+
}
|
|
52392
|
+
return parts.join(`
|
|
52393
|
+
|
|
52394
|
+
`);
|
|
52395
|
+
}
|
|
52396
|
+
function clearRepoMemory(repoPath) {
|
|
52397
|
+
const m = loadMemoryFile();
|
|
52398
|
+
m.entries = m.entries.filter((e) => e.repoPath !== repoPath);
|
|
52399
|
+
m.memories = m.memories.filter((mem) => mem.repoPath !== repoPath);
|
|
52400
|
+
saveMemoryFile(m);
|
|
52328
52401
|
}
|
|
52329
|
-
function
|
|
52330
|
-
|
|
52331
|
-
|
|
52332
|
-
|
|
52402
|
+
function generateId() {
|
|
52403
|
+
return Math.random().toString(36).slice(2, 8);
|
|
52404
|
+
}
|
|
52405
|
+
function addMemory(content, repoPath) {
|
|
52406
|
+
const m = loadMemoryFile();
|
|
52407
|
+
const memory = {
|
|
52408
|
+
id: generateId(),
|
|
52409
|
+
content,
|
|
52410
|
+
timestamp: new Date().toISOString(),
|
|
52411
|
+
repoPath
|
|
52412
|
+
};
|
|
52413
|
+
m.memories.push(memory);
|
|
52414
|
+
saveMemoryFile(m);
|
|
52415
|
+
return memory;
|
|
52416
|
+
}
|
|
52417
|
+
function deleteMemory(id, repoPath) {
|
|
52418
|
+
const m = loadMemoryFile();
|
|
52419
|
+
const before2 = m.memories.length;
|
|
52420
|
+
m.memories = m.memories.filter((mem) => !(mem.id === id && mem.repoPath === repoPath));
|
|
52421
|
+
if (m.memories.length === before2)
|
|
52422
|
+
return false;
|
|
52423
|
+
saveMemoryFile(m);
|
|
52424
|
+
return true;
|
|
52425
|
+
}
|
|
52426
|
+
function listMemories(repoPath) {
|
|
52427
|
+
return loadMemoryFile().memories.filter((mem) => mem.repoPath === repoPath);
|
|
52333
52428
|
}
|
|
52334
52429
|
|
|
52335
52430
|
// src/components/chat/ChatRunner.tsx
|
|
@@ -52337,74 +52432,121 @@ var jsx_dev_runtime22 = __toESM(require_jsx_dev_runtime(), 1);
|
|
|
52337
52432
|
var COMMANDS = [
|
|
52338
52433
|
{ cmd: "/timeline", desc: "browse commit history" },
|
|
52339
52434
|
{ cmd: "/clear history", desc: "wipe session memory for this repo" },
|
|
52340
|
-
{ cmd: "/review", desc: "review current
|
|
52341
|
-
{ cmd: "/auto", desc: "toggle auto-approve for read/search tools" }
|
|
52435
|
+
{ cmd: "/review", desc: "review current codebase" },
|
|
52436
|
+
{ cmd: "/auto", desc: "toggle auto-approve for read/search tools" },
|
|
52437
|
+
{ cmd: "/chat", desc: "chat history commands" },
|
|
52438
|
+
{ cmd: "/chat list", desc: "list saved chats for this repo" },
|
|
52439
|
+
{ cmd: "/chat load", desc: "load a saved chat by name" },
|
|
52440
|
+
{ cmd: "/chat rename", desc: "rename the current chat" },
|
|
52441
|
+
{ cmd: "/chat delete", desc: "delete a saved chat by name" },
|
|
52442
|
+
{ cmd: "/memory", desc: "memory commands" },
|
|
52443
|
+
{ cmd: "/memory list", desc: "list all memories for this repo" },
|
|
52444
|
+
{ cmd: "/memory add", desc: "add a memory" },
|
|
52445
|
+
{ cmd: "/memory delete", desc: "delete a memory by id" },
|
|
52446
|
+
{ cmd: "/memory clear", desc: "clear all memories for this repo" }
|
|
52342
52447
|
];
|
|
52343
52448
|
function CommandPalette({
|
|
52344
52449
|
query,
|
|
52345
|
-
onSelect
|
|
52450
|
+
onSelect,
|
|
52451
|
+
recentChats
|
|
52346
52452
|
}) {
|
|
52347
52453
|
const q = query.toLowerCase();
|
|
52454
|
+
const isChatLoad = q.startsWith("/chat load") || q.startsWith("/chat delete");
|
|
52455
|
+
const chatFilter = isChatLoad ? q.startsWith("/chat load") ? q.slice("/chat load".length).trim() : q.slice("/chat delete".length).trim() : "";
|
|
52456
|
+
const filteredChats = chatFilter ? recentChats.filter((n) => n.toLowerCase().includes(chatFilter)) : recentChats;
|
|
52348
52457
|
const matches2 = COMMANDS.filter((c) => c.cmd.startsWith(q));
|
|
52349
|
-
if (!matches2.length)
|
|
52458
|
+
if (!matches2.length && !isChatLoad)
|
|
52459
|
+
return null;
|
|
52460
|
+
if (!matches2.length && isChatLoad && filteredChats.length === 0)
|
|
52350
52461
|
return null;
|
|
52351
52462
|
return /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(Box_default, {
|
|
52352
52463
|
flexDirection: "column",
|
|
52353
52464
|
marginBottom: 1,
|
|
52354
52465
|
marginLeft: 2,
|
|
52355
|
-
children:
|
|
52356
|
-
|
|
52357
|
-
|
|
52358
|
-
|
|
52466
|
+
children: [
|
|
52467
|
+
matches2.map((c, i) => {
|
|
52468
|
+
const isExact = c.cmd === query;
|
|
52469
|
+
return /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(Box_default, {
|
|
52470
|
+
gap: 2,
|
|
52471
|
+
children: [
|
|
52472
|
+
/* @__PURE__ */ jsx_dev_runtime22.jsxDEV(Text, {
|
|
52473
|
+
color: isExact ? ACCENT : "white",
|
|
52474
|
+
bold: isExact,
|
|
52475
|
+
children: c.cmd
|
|
52476
|
+
}, undefined, false, undefined, this),
|
|
52477
|
+
/* @__PURE__ */ jsx_dev_runtime22.jsxDEV(Text, {
|
|
52478
|
+
color: "gray",
|
|
52479
|
+
dimColor: true,
|
|
52480
|
+
children: c.desc
|
|
52481
|
+
}, undefined, false, undefined, this)
|
|
52482
|
+
]
|
|
52483
|
+
}, i, true, undefined, this);
|
|
52484
|
+
}),
|
|
52485
|
+
isChatLoad && filteredChats.length > 0 && /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(Box_default, {
|
|
52486
|
+
flexDirection: "column",
|
|
52487
|
+
marginTop: matches2.length ? 1 : 0,
|
|
52359
52488
|
children: [
|
|
52360
|
-
/* @__PURE__ */ jsx_dev_runtime22.jsxDEV(Text, {
|
|
52361
|
-
color: isExact ? ACCENT : "white",
|
|
52362
|
-
bold: isExact,
|
|
52363
|
-
children: c.cmd
|
|
52364
|
-
}, undefined, false, undefined, this),
|
|
52365
52489
|
/* @__PURE__ */ jsx_dev_runtime22.jsxDEV(Text, {
|
|
52366
52490
|
color: "gray",
|
|
52367
52491
|
dimColor: true,
|
|
52368
|
-
children:
|
|
52369
|
-
}, undefined, false, undefined, this)
|
|
52492
|
+
children: chatFilter ? `matching "${chatFilter}":` : "recent chats:"
|
|
52493
|
+
}, undefined, false, undefined, this),
|
|
52494
|
+
filteredChats.map((name, i) => /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(Box_default, {
|
|
52495
|
+
gap: 1,
|
|
52496
|
+
marginLeft: 2,
|
|
52497
|
+
children: [
|
|
52498
|
+
/* @__PURE__ */ jsx_dev_runtime22.jsxDEV(Text, {
|
|
52499
|
+
color: ACCENT,
|
|
52500
|
+
children: "·"
|
|
52501
|
+
}, undefined, false, undefined, this),
|
|
52502
|
+
/* @__PURE__ */ jsx_dev_runtime22.jsxDEV(Text, {
|
|
52503
|
+
color: "white",
|
|
52504
|
+
children: name
|
|
52505
|
+
}, undefined, false, undefined, this)
|
|
52506
|
+
]
|
|
52507
|
+
}, i, true, undefined, this))
|
|
52370
52508
|
]
|
|
52371
|
-
},
|
|
52372
|
-
|
|
52373
|
-
}, undefined,
|
|
52509
|
+
}, undefined, true, undefined, this)
|
|
52510
|
+
]
|
|
52511
|
+
}, undefined, true, undefined, this);
|
|
52374
52512
|
}
|
|
52375
52513
|
var ChatRunner = ({ repoPath }) => {
|
|
52376
|
-
const [stage, setStage] =
|
|
52377
|
-
const [committed, setCommitted] =
|
|
52378
|
-
const [provider, setProvider] =
|
|
52379
|
-
const [systemPrompt, setSystemPrompt] =
|
|
52380
|
-
const [inputValue, setInputValue] =
|
|
52381
|
-
const [pendingMsgIndex, setPendingMsgIndex] =
|
|
52382
|
-
const [allMessages, setAllMessages] =
|
|
52383
|
-
const [clonedUrls, setClonedUrls] =
|
|
52384
|
-
const [showTimeline, setShowTimeline] =
|
|
52385
|
-
const [showReview, setShowReview] =
|
|
52386
|
-
const [autoApprove, setAutoApprove] =
|
|
52387
|
-
const
|
|
52388
|
-
const
|
|
52389
|
-
const
|
|
52390
|
-
const
|
|
52391
|
-
const
|
|
52392
|
-
const
|
|
52393
|
-
|
|
52394
|
-
|
|
52395
|
-
|
|
52396
|
-
inputBuffer.current = "";
|
|
52397
|
-
setInputValue((v) => v + buf);
|
|
52398
|
-
};
|
|
52399
|
-
const scheduleFlush = () => {
|
|
52400
|
-
if (flushTimer.current !== null)
|
|
52401
|
-
return;
|
|
52402
|
-
flushTimer.current = setTimeout(() => {
|
|
52403
|
-
flushTimer.current = null;
|
|
52404
|
-
flushBuffer();
|
|
52405
|
-
}, 16);
|
|
52514
|
+
const [stage, setStage] = import_react49.useState({ type: "picking-provider" });
|
|
52515
|
+
const [committed, setCommitted] = import_react49.useState([]);
|
|
52516
|
+
const [provider, setProvider] = import_react49.useState(null);
|
|
52517
|
+
const [systemPrompt, setSystemPrompt] = import_react49.useState("");
|
|
52518
|
+
const [inputValue, setInputValue] = import_react49.useState("");
|
|
52519
|
+
const [pendingMsgIndex, setPendingMsgIndex] = import_react49.useState(null);
|
|
52520
|
+
const [allMessages, setAllMessages] = import_react49.useState([]);
|
|
52521
|
+
const [clonedUrls, setClonedUrls] = import_react49.useState(new Set);
|
|
52522
|
+
const [showTimeline, setShowTimeline] = import_react49.useState(false);
|
|
52523
|
+
const [showReview, setShowReview] = import_react49.useState(false);
|
|
52524
|
+
const [autoApprove, setAutoApprove] = import_react49.useState(false);
|
|
52525
|
+
const [chatName, setChatName] = import_react49.useState(null);
|
|
52526
|
+
const chatNameRef = import_react49.useRef(null);
|
|
52527
|
+
const [recentChats, setRecentChats] = import_react49.useState([]);
|
|
52528
|
+
const inputHistoryRef = import_react49.useRef([]);
|
|
52529
|
+
const historyIndexRef = import_react49.useRef(-1);
|
|
52530
|
+
const [inputKey, setInputKey] = import_react49.useState(0);
|
|
52531
|
+
const updateChatName = (name) => {
|
|
52532
|
+
chatNameRef.current = name;
|
|
52533
|
+
setChatName(name);
|
|
52406
52534
|
};
|
|
52535
|
+
const abortControllerRef = import_react49.useRef(null);
|
|
52536
|
+
const toolResultCache = import_react49.useRef(new Map);
|
|
52537
|
+
const batchApprovedRef = import_react49.useRef(false);
|
|
52538
|
+
const thinkingPhrase = useThinkingPhrase(stage.type === "thinking");
|
|
52539
|
+
import_react48.default.useEffect(() => {
|
|
52540
|
+
const chats = listChats(repoPath);
|
|
52541
|
+
setRecentChats(chats.slice(0, 10).map((c) => c.name));
|
|
52542
|
+
}, [repoPath]);
|
|
52543
|
+
import_react48.default.useEffect(() => {
|
|
52544
|
+
if (chatNameRef.current && allMessages.length > 1) {
|
|
52545
|
+
saveChat(chatNameRef.current, repoPath, allMessages);
|
|
52546
|
+
}
|
|
52547
|
+
}, [allMessages]);
|
|
52407
52548
|
const handleError = (currentAll) => (err) => {
|
|
52549
|
+
batchApprovedRef.current = false;
|
|
52408
52550
|
if (err instanceof Error && err.name === "AbortError") {
|
|
52409
52551
|
setStage({ type: "idle" });
|
|
52410
52552
|
return;
|
|
@@ -52420,180 +52562,63 @@ var ChatRunner = ({ repoPath }) => {
|
|
|
52420
52562
|
};
|
|
52421
52563
|
const processResponse = (raw, currentAll, signal) => {
|
|
52422
52564
|
if (signal.aborted) {
|
|
52565
|
+
batchApprovedRef.current = false;
|
|
52423
52566
|
setStage({ type: "idle" });
|
|
52424
52567
|
return;
|
|
52425
52568
|
}
|
|
52426
|
-
const
|
|
52569
|
+
const memAddMatches = [
|
|
52570
|
+
...raw.matchAll(/<memory-add>([\s\S]*?)<\/memory-add>/g)
|
|
52571
|
+
];
|
|
52572
|
+
const memDelMatches = [
|
|
52573
|
+
...raw.matchAll(/<memory-delete>([\s\S]*?)<\/memory-delete>/g)
|
|
52574
|
+
];
|
|
52575
|
+
for (const match of memAddMatches) {
|
|
52576
|
+
const content = match[1].trim();
|
|
52577
|
+
if (content)
|
|
52578
|
+
addMemory(content, repoPath);
|
|
52579
|
+
}
|
|
52580
|
+
for (const match of memDelMatches) {
|
|
52581
|
+
const id = match[1].trim();
|
|
52582
|
+
if (id)
|
|
52583
|
+
deleteMemory(id, repoPath);
|
|
52584
|
+
}
|
|
52585
|
+
const cleanRaw = raw.replace(/<memory-add>[\s\S]*?<\/memory-add>/g, "").replace(/<memory-delete>[\s\S]*?<\/memory-delete>/g, "").trim();
|
|
52586
|
+
const parsed = parseResponse(cleanRaw);
|
|
52427
52587
|
if (parsed.kind === "changes") {
|
|
52588
|
+
batchApprovedRef.current = false;
|
|
52428
52589
|
if (parsed.patches.length === 0) {
|
|
52429
|
-
const
|
|
52430
|
-
role: "assistant",
|
|
52431
|
-
content: parsed.content,
|
|
52432
|
-
type: "text"
|
|
52433
|
-
};
|
|
52434
|
-
setAllMessages([...currentAll, msg2]);
|
|
52435
|
-
setCommitted((prev) => [...prev, msg2]);
|
|
52436
|
-
setStage({ type: "idle" });
|
|
52437
|
-
return;
|
|
52438
|
-
}
|
|
52439
|
-
const assistantMsg = {
|
|
52440
|
-
role: "assistant",
|
|
52441
|
-
content: parsed.content,
|
|
52442
|
-
type: "plan",
|
|
52443
|
-
patches: parsed.patches,
|
|
52444
|
-
applied: false
|
|
52445
|
-
};
|
|
52446
|
-
const withAssistant = [...currentAll, assistantMsg];
|
|
52447
|
-
setAllMessages(withAssistant);
|
|
52448
|
-
setPendingMsgIndex(withAssistant.length - 1);
|
|
52449
|
-
const diffLines = buildDiffs(repoPath, parsed.patches);
|
|
52450
|
-
setStage({
|
|
52451
|
-
type: "preview",
|
|
52452
|
-
patches: parsed.patches,
|
|
52453
|
-
diffLines,
|
|
52454
|
-
scrollOffset: 0,
|
|
52455
|
-
pendingMessages: currentAll
|
|
52456
|
-
});
|
|
52457
|
-
return;
|
|
52458
|
-
}
|
|
52459
|
-
if (parsed.kind === "shell" || parsed.kind === "fetch" || parsed.kind === "read-file" || parsed.kind === "read-folder" || parsed.kind === "grep" || parsed.kind === "write-file" || parsed.kind === "delete-file" || parsed.kind === "delete-folder" || parsed.kind === "open-url" || parsed.kind === "generate-pdf" || parsed.kind === "search") {
|
|
52460
|
-
let tool;
|
|
52461
|
-
if (parsed.kind === "shell") {
|
|
52462
|
-
tool = { type: "shell", command: parsed.command };
|
|
52463
|
-
} else if (parsed.kind === "fetch") {
|
|
52464
|
-
tool = { type: "fetch", url: parsed.url };
|
|
52465
|
-
} else if (parsed.kind === "read-file") {
|
|
52466
|
-
tool = { type: "read-file", filePath: parsed.filePath };
|
|
52467
|
-
} else if (parsed.kind === "read-folder") {
|
|
52468
|
-
tool = { type: "read-folder", folderPath: parsed.folderPath };
|
|
52469
|
-
} else if (parsed.kind === "grep") {
|
|
52470
|
-
tool = { type: "grep", pattern: parsed.pattern, glob: parsed.glob };
|
|
52471
|
-
} else if (parsed.kind === "delete-file") {
|
|
52472
|
-
tool = { type: "delete-file", filePath: parsed.filePath };
|
|
52473
|
-
} else if (parsed.kind === "delete-folder") {
|
|
52474
|
-
tool = { type: "delete-folder", folderPath: parsed.folderPath };
|
|
52475
|
-
} else if (parsed.kind === "open-url") {
|
|
52476
|
-
tool = { type: "open-url", url: parsed.url };
|
|
52477
|
-
} else if (parsed.kind === "generate-pdf") {
|
|
52478
|
-
tool = {
|
|
52479
|
-
type: "generate-pdf",
|
|
52480
|
-
filePath: parsed.filePath,
|
|
52481
|
-
content: parsed.pdfContent
|
|
52482
|
-
};
|
|
52483
|
-
} else if (parsed.kind === "search") {
|
|
52484
|
-
tool = { type: "search", query: parsed.query };
|
|
52485
|
-
} else {
|
|
52486
|
-
tool = {
|
|
52487
|
-
type: "write-file",
|
|
52488
|
-
filePath: parsed.filePath,
|
|
52489
|
-
fileContent: parsed.fileContent
|
|
52490
|
-
};
|
|
52491
|
-
}
|
|
52492
|
-
if (parsed.content) {
|
|
52493
|
-
const preambleMsg = {
|
|
52590
|
+
const msg = {
|
|
52494
52591
|
role: "assistant",
|
|
52495
52592
|
content: parsed.content,
|
|
52496
52593
|
type: "text"
|
|
52497
52594
|
};
|
|
52498
|
-
setAllMessages([...currentAll,
|
|
52499
|
-
setCommitted((prev) => [...prev,
|
|
52500
|
-
|
|
52501
|
-
const isSafeTool = parsed.kind === "read-file" || parsed.kind === "read-folder" || parsed.kind === "grep" || parsed.kind === "fetch" || parsed.kind === "open-url" || parsed.kind === "search";
|
|
52502
|
-
const executeAndContinue = async (approved) => {
|
|
52503
|
-
let result2 = "(denied by user)";
|
|
52504
|
-
if (approved) {
|
|
52505
|
-
const cacheKey = parsed.kind === "read-file" ? `read-file:${parsed.filePath}` : parsed.kind === "read-folder" ? `read-folder:${parsed.folderPath}` : parsed.kind === "grep" ? `grep:${parsed.pattern}:${parsed.glob}` : null;
|
|
52506
|
-
if (cacheKey && toolResultCache.current.has(cacheKey)) {
|
|
52507
|
-
result2 = toolResultCache.current.get(cacheKey) + `
|
|
52508
|
-
|
|
52509
|
-
[NOTE: This result was already retrieved earlier. Do not request it again.]`;
|
|
52510
|
-
} else {
|
|
52511
|
-
try {
|
|
52512
|
-
setStage({ type: "thinking" });
|
|
52513
|
-
if (parsed.kind === "shell") {
|
|
52514
|
-
result2 = await runShell(parsed.command, repoPath);
|
|
52515
|
-
} else if (parsed.kind === "fetch") {
|
|
52516
|
-
result2 = await fetchUrl(parsed.url);
|
|
52517
|
-
} else if (parsed.kind === "read-file") {
|
|
52518
|
-
result2 = readFile(parsed.filePath, repoPath);
|
|
52519
|
-
} else if (parsed.kind === "read-folder") {
|
|
52520
|
-
result2 = readFolder(parsed.folderPath, repoPath);
|
|
52521
|
-
} else if (parsed.kind === "grep") {
|
|
52522
|
-
result2 = grepFiles(parsed.pattern, parsed.glob, repoPath);
|
|
52523
|
-
} else if (parsed.kind === "delete-file") {
|
|
52524
|
-
result2 = deleteFile(parsed.filePath, repoPath);
|
|
52525
|
-
} else if (parsed.kind === "delete-folder") {
|
|
52526
|
-
result2 = deleteFolder(parsed.folderPath, repoPath);
|
|
52527
|
-
} else if (parsed.kind === "open-url") {
|
|
52528
|
-
result2 = openUrl(parsed.url);
|
|
52529
|
-
} else if (parsed.kind === "generate-pdf") {
|
|
52530
|
-
result2 = generatePdf(parsed.filePath, parsed.pdfContent, repoPath);
|
|
52531
|
-
} else if (parsed.kind === "write-file") {
|
|
52532
|
-
result2 = writeFile(parsed.filePath, parsed.fileContent, repoPath);
|
|
52533
|
-
} else if (parsed.kind === "search") {
|
|
52534
|
-
result2 = await searchWeb(parsed.query);
|
|
52535
|
-
}
|
|
52536
|
-
if (cacheKey) {
|
|
52537
|
-
toolResultCache.current.set(cacheKey, result2);
|
|
52538
|
-
}
|
|
52539
|
-
} catch (err) {
|
|
52540
|
-
result2 = `Error: ${err instanceof Error ? err.message : "failed"}`;
|
|
52541
|
-
}
|
|
52542
|
-
}
|
|
52543
|
-
}
|
|
52544
|
-
if (approved && !result2.startsWith("Error:")) {
|
|
52545
|
-
const kindMap2 = {
|
|
52546
|
-
shell: "shell-run",
|
|
52547
|
-
fetch: "url-fetched",
|
|
52548
|
-
"read-file": "file-read",
|
|
52549
|
-
"read-folder": "file-read",
|
|
52550
|
-
grep: "file-read",
|
|
52551
|
-
"delete-file": "file-written",
|
|
52552
|
-
"delete-folder": "file-written",
|
|
52553
|
-
"open-url": "url-fetched",
|
|
52554
|
-
"generate-pdf": "file-written",
|
|
52555
|
-
"write-file": "file-written",
|
|
52556
|
-
search: "url-fetched"
|
|
52557
|
-
};
|
|
52558
|
-
appendHistory({
|
|
52559
|
-
kind: kindMap2[parsed.kind] ?? "shell-run",
|
|
52560
|
-
detail: parsed.kind === "shell" ? parsed.command : parsed.kind === "fetch" ? parsed.url : parsed.kind === "search" ? parsed.query : parsed.kind === "read-folder" ? parsed.folderPath : parsed.kind === "grep" ? `${parsed.pattern} ${parsed.glob}` : parsed.kind === "delete-file" ? parsed.filePath : parsed.kind === "delete-folder" ? parsed.folderPath : parsed.kind === "open-url" ? parsed.url : parsed.kind === "generate-pdf" ? parsed.filePath : parsed.filePath,
|
|
52561
|
-
summary: result2.split(`
|
|
52562
|
-
`)[0]?.slice(0, 120) ?? "",
|
|
52563
|
-
repoPath
|
|
52564
|
-
});
|
|
52565
|
-
}
|
|
52566
|
-
const toolName = parsed.kind === "shell" ? "shell" : parsed.kind === "fetch" ? "fetch" : parsed.kind === "read-file" ? "read-file" : parsed.kind === "read-folder" ? "read-folder" : parsed.kind === "grep" ? "grep" : parsed.kind === "delete-file" ? "delete-file" : parsed.kind === "delete-folder" ? "delete-folder" : parsed.kind === "open-url" ? "open-url" : parsed.kind === "generate-pdf" ? "generate-pdf" : parsed.kind === "search" ? "search" : "write-file";
|
|
52567
|
-
const toolContent = parsed.kind === "shell" ? parsed.command : parsed.kind === "fetch" ? parsed.url : parsed.kind === "search" ? parsed.query : parsed.kind === "read-folder" ? parsed.folderPath : parsed.kind === "grep" ? `${parsed.pattern} — ${parsed.glob}` : parsed.kind === "delete-file" ? parsed.filePath : parsed.kind === "delete-folder" ? parsed.folderPath : parsed.kind === "open-url" ? parsed.url : parsed.kind === "generate-pdf" ? parsed.filePath : parsed.filePath;
|
|
52568
|
-
const toolMsg = {
|
|
52569
|
-
role: "assistant",
|
|
52570
|
-
type: "tool",
|
|
52571
|
-
toolName,
|
|
52572
|
-
content: toolContent,
|
|
52573
|
-
result: result2,
|
|
52574
|
-
approved
|
|
52575
|
-
};
|
|
52576
|
-
const withTool = [...currentAll, toolMsg];
|
|
52577
|
-
setAllMessages(withTool);
|
|
52578
|
-
setCommitted((prev) => [...prev, toolMsg]);
|
|
52579
|
-
const nextAbort = new AbortController;
|
|
52580
|
-
abortControllerRef.current = nextAbort;
|
|
52581
|
-
setStage({ type: "thinking" });
|
|
52582
|
-
callChat(provider, systemPrompt, withTool, nextAbort.signal).then((r) => processResponse(r, withTool, nextAbort.signal)).catch(handleError(withTool));
|
|
52583
|
-
};
|
|
52584
|
-
if (autoApprove && isSafeTool) {
|
|
52585
|
-
executeAndContinue(true);
|
|
52595
|
+
setAllMessages([...currentAll, msg]);
|
|
52596
|
+
setCommitted((prev) => [...prev, msg]);
|
|
52597
|
+
setStage({ type: "idle" });
|
|
52586
52598
|
return;
|
|
52587
52599
|
}
|
|
52600
|
+
const assistantMsg = {
|
|
52601
|
+
role: "assistant",
|
|
52602
|
+
content: parsed.content,
|
|
52603
|
+
type: "plan",
|
|
52604
|
+
patches: parsed.patches,
|
|
52605
|
+
applied: false
|
|
52606
|
+
};
|
|
52607
|
+
const withAssistant = [...currentAll, assistantMsg];
|
|
52608
|
+
setAllMessages(withAssistant);
|
|
52609
|
+
setPendingMsgIndex(withAssistant.length - 1);
|
|
52610
|
+
const diffLines = buildDiffs(repoPath, parsed.patches);
|
|
52588
52611
|
setStage({
|
|
52589
|
-
type: "
|
|
52590
|
-
|
|
52591
|
-
|
|
52592
|
-
|
|
52612
|
+
type: "preview",
|
|
52613
|
+
patches: parsed.patches,
|
|
52614
|
+
diffLines,
|
|
52615
|
+
scrollOffset: 0,
|
|
52616
|
+
pendingMessages: currentAll
|
|
52593
52617
|
});
|
|
52594
52618
|
return;
|
|
52595
52619
|
}
|
|
52596
52620
|
if (parsed.kind === "clone") {
|
|
52621
|
+
batchApprovedRef.current = false;
|
|
52597
52622
|
if (parsed.content) {
|
|
52598
52623
|
const preambleMsg = {
|
|
52599
52624
|
role: "assistant",
|
|
@@ -52610,23 +52635,116 @@ var ChatRunner = ({ repoPath }) => {
|
|
|
52610
52635
|
});
|
|
52611
52636
|
return;
|
|
52612
52637
|
}
|
|
52613
|
-
|
|
52614
|
-
|
|
52615
|
-
|
|
52616
|
-
|
|
52617
|
-
|
|
52618
|
-
|
|
52619
|
-
|
|
52620
|
-
|
|
52621
|
-
|
|
52622
|
-
|
|
52623
|
-
|
|
52624
|
-
|
|
52625
|
-
|
|
52626
|
-
|
|
52627
|
-
|
|
52638
|
+
if (parsed.kind === "text") {
|
|
52639
|
+
batchApprovedRef.current = false;
|
|
52640
|
+
const msg = {
|
|
52641
|
+
role: "assistant",
|
|
52642
|
+
content: parsed.content,
|
|
52643
|
+
type: "text"
|
|
52644
|
+
};
|
|
52645
|
+
const withMsg = [...currentAll, msg];
|
|
52646
|
+
setAllMessages(withMsg);
|
|
52647
|
+
setCommitted((prev) => [...prev, msg]);
|
|
52648
|
+
const lastUserMsg = [...currentAll].reverse().find((m) => m.role === "user");
|
|
52649
|
+
const githubUrl = lastUserMsg ? extractGithubUrl(lastUserMsg.content) : null;
|
|
52650
|
+
if (githubUrl && !clonedUrls.has(githubUrl)) {
|
|
52651
|
+
setTimeout(() => setStage({ type: "clone-offer", repoUrl: githubUrl }), 80);
|
|
52652
|
+
} else {
|
|
52653
|
+
setStage({ type: "idle" });
|
|
52654
|
+
}
|
|
52655
|
+
return;
|
|
52656
|
+
}
|
|
52657
|
+
const tool = registry.get(parsed.toolName);
|
|
52658
|
+
if (!tool) {
|
|
52659
|
+
batchApprovedRef.current = false;
|
|
52628
52660
|
setStage({ type: "idle" });
|
|
52661
|
+
return;
|
|
52662
|
+
}
|
|
52663
|
+
if (parsed.content) {
|
|
52664
|
+
const preambleMsg = {
|
|
52665
|
+
role: "assistant",
|
|
52666
|
+
content: parsed.content,
|
|
52667
|
+
type: "text"
|
|
52668
|
+
};
|
|
52669
|
+
setAllMessages([...currentAll, preambleMsg]);
|
|
52670
|
+
setCommitted((prev) => [...prev, preambleMsg]);
|
|
52671
|
+
}
|
|
52672
|
+
const remainder = parsed.remainder;
|
|
52673
|
+
const isSafe = tool.safe ?? false;
|
|
52674
|
+
const executeAndContinue = async (approved) => {
|
|
52675
|
+
if (approved && remainder) {
|
|
52676
|
+
batchApprovedRef.current = true;
|
|
52677
|
+
}
|
|
52678
|
+
let result2 = "(denied by user)";
|
|
52679
|
+
if (approved) {
|
|
52680
|
+
const cacheKey = isSafe ? `${parsed.toolName}:${parsed.rawInput}` : null;
|
|
52681
|
+
if (cacheKey && toolResultCache.current.has(cacheKey)) {
|
|
52682
|
+
result2 = toolResultCache.current.get(cacheKey) + `
|
|
52683
|
+
|
|
52684
|
+
[NOTE: This result was already retrieved earlier. Do not request it again.]`;
|
|
52685
|
+
} else {
|
|
52686
|
+
try {
|
|
52687
|
+
setStage({ type: "thinking" });
|
|
52688
|
+
const toolResult = await tool.execute(parsed.input, {
|
|
52689
|
+
repoPath,
|
|
52690
|
+
messages: currentAll
|
|
52691
|
+
});
|
|
52692
|
+
result2 = toolResult.value;
|
|
52693
|
+
if (cacheKey && toolResult.kind === "text") {
|
|
52694
|
+
toolResultCache.current.set(cacheKey, result2);
|
|
52695
|
+
}
|
|
52696
|
+
} catch (err) {
|
|
52697
|
+
result2 = `Error: ${err instanceof Error ? err.message : "failed"}`;
|
|
52698
|
+
}
|
|
52699
|
+
}
|
|
52700
|
+
}
|
|
52701
|
+
if (approved && !result2.startsWith("Error:")) {
|
|
52702
|
+
appendMemory({
|
|
52703
|
+
kind: "shell-run",
|
|
52704
|
+
detail: tool.summariseInput ? String(tool.summariseInput(parsed.input)) : parsed.rawInput,
|
|
52705
|
+
summary: result2.split(`
|
|
52706
|
+
`)[0]?.slice(0, 120) ?? "",
|
|
52707
|
+
repoPath
|
|
52708
|
+
});
|
|
52709
|
+
}
|
|
52710
|
+
const displayContent = tool.summariseInput ? String(tool.summariseInput(parsed.input)) : parsed.rawInput;
|
|
52711
|
+
const toolMsg = {
|
|
52712
|
+
role: "assistant",
|
|
52713
|
+
type: "tool",
|
|
52714
|
+
toolName: parsed.toolName,
|
|
52715
|
+
content: displayContent,
|
|
52716
|
+
result: result2,
|
|
52717
|
+
approved
|
|
52718
|
+
};
|
|
52719
|
+
const withTool = [...currentAll, toolMsg];
|
|
52720
|
+
setAllMessages(withTool);
|
|
52721
|
+
setCommitted((prev) => [...prev, toolMsg]);
|
|
52722
|
+
if (approved && remainder && remainder.length > 0) {
|
|
52723
|
+
processResponse(remainder, withTool, signal);
|
|
52724
|
+
return;
|
|
52725
|
+
}
|
|
52726
|
+
batchApprovedRef.current = false;
|
|
52727
|
+
const nextAbort = new AbortController;
|
|
52728
|
+
abortControllerRef.current = nextAbort;
|
|
52729
|
+
setStage({ type: "thinking" });
|
|
52730
|
+
callChat(provider, systemPrompt, withTool, nextAbort.signal).then((r) => processResponse(r, withTool, nextAbort.signal)).catch(handleError(withTool));
|
|
52731
|
+
};
|
|
52732
|
+
if (autoApprove && isSafe || batchApprovedRef.current) {
|
|
52733
|
+
executeAndContinue(true);
|
|
52734
|
+
return;
|
|
52629
52735
|
}
|
|
52736
|
+
const permLabel = tool.permissionLabel ?? tool.name;
|
|
52737
|
+
const permValue = tool.summariseInput ? String(tool.summariseInput(parsed.input)) : parsed.rawInput;
|
|
52738
|
+
setStage({
|
|
52739
|
+
type: "permission",
|
|
52740
|
+
tool: {
|
|
52741
|
+
type: parsed.toolName,
|
|
52742
|
+
_display: permValue,
|
|
52743
|
+
_label: permLabel
|
|
52744
|
+
},
|
|
52745
|
+
pendingMessages: currentAll,
|
|
52746
|
+
resolve: executeAndContinue
|
|
52747
|
+
});
|
|
52630
52748
|
};
|
|
52631
52749
|
const sendMessage = (text) => {
|
|
52632
52750
|
if (!provider)
|
|
@@ -52644,7 +52762,7 @@ var ChatRunner = ({ repoPath }) => {
|
|
|
52644
52762
|
setAutoApprove(next);
|
|
52645
52763
|
const msg = {
|
|
52646
52764
|
role: "assistant",
|
|
52647
|
-
content: next ? "Auto-approve ON — read, search,
|
|
52765
|
+
content: next ? "Auto-approve ON — safe tools (read, search, fetch) will run without asking." : "Auto-approve OFF — all tools will ask for permission.",
|
|
52648
52766
|
type: "text"
|
|
52649
52767
|
};
|
|
52650
52768
|
setCommitted((prev) => [...prev, msg]);
|
|
@@ -52652,14 +52770,204 @@ var ChatRunner = ({ repoPath }) => {
|
|
|
52652
52770
|
return;
|
|
52653
52771
|
}
|
|
52654
52772
|
if (text.trim().toLowerCase() === "/clear history") {
|
|
52655
|
-
|
|
52656
|
-
const
|
|
52773
|
+
clearRepoMemory(repoPath);
|
|
52774
|
+
const msg = {
|
|
52657
52775
|
role: "assistant",
|
|
52658
52776
|
content: "History cleared for this repo.",
|
|
52659
52777
|
type: "text"
|
|
52660
52778
|
};
|
|
52661
|
-
setCommitted((prev) => [...prev,
|
|
52662
|
-
setAllMessages((prev) => [...prev,
|
|
52779
|
+
setCommitted((prev) => [...prev, msg]);
|
|
52780
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
52781
|
+
return;
|
|
52782
|
+
}
|
|
52783
|
+
if (text.trim().toLowerCase() === "/chat") {
|
|
52784
|
+
const msg = {
|
|
52785
|
+
role: "assistant",
|
|
52786
|
+
content: "Chat commands: `/chat list` · `/chat load <n>` · `/chat rename <n>` · `/chat delete <n>`",
|
|
52787
|
+
type: "text"
|
|
52788
|
+
};
|
|
52789
|
+
setCommitted((prev) => [...prev, msg]);
|
|
52790
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
52791
|
+
return;
|
|
52792
|
+
}
|
|
52793
|
+
if (text.trim().toLowerCase().startsWith("/chat rename")) {
|
|
52794
|
+
const parts = text.trim().split(/\s+/);
|
|
52795
|
+
const newName = parts.slice(2).join("-");
|
|
52796
|
+
if (!newName) {
|
|
52797
|
+
const msg2 = {
|
|
52798
|
+
role: "assistant",
|
|
52799
|
+
content: "Usage: `/chat rename <new-name>`",
|
|
52800
|
+
type: "text"
|
|
52801
|
+
};
|
|
52802
|
+
setCommitted((prev) => [...prev, msg2]);
|
|
52803
|
+
setAllMessages((prev) => [...prev, msg2]);
|
|
52804
|
+
return;
|
|
52805
|
+
}
|
|
52806
|
+
const oldName = chatNameRef.current;
|
|
52807
|
+
if (oldName)
|
|
52808
|
+
deleteChat(oldName);
|
|
52809
|
+
updateChatName(newName);
|
|
52810
|
+
saveChat(newName, repoPath, allMessages);
|
|
52811
|
+
setRecentChats((prev) => [newName, ...prev.filter((n) => n !== newName && n !== oldName)].slice(0, 10));
|
|
52812
|
+
const msg = {
|
|
52813
|
+
role: "assistant",
|
|
52814
|
+
content: `Chat renamed to **${newName}**.`,
|
|
52815
|
+
type: "text"
|
|
52816
|
+
};
|
|
52817
|
+
setCommitted((prev) => [...prev, msg]);
|
|
52818
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
52819
|
+
return;
|
|
52820
|
+
}
|
|
52821
|
+
if (text.trim().toLowerCase().startsWith("/chat delete")) {
|
|
52822
|
+
const parts = text.trim().split(/\s+/);
|
|
52823
|
+
const name = parts.slice(2).join("-");
|
|
52824
|
+
if (!name) {
|
|
52825
|
+
const msg2 = {
|
|
52826
|
+
role: "assistant",
|
|
52827
|
+
content: "Usage: `/chat delete <n>`",
|
|
52828
|
+
type: "text"
|
|
52829
|
+
};
|
|
52830
|
+
setCommitted((prev) => [...prev, msg2]);
|
|
52831
|
+
setAllMessages((prev) => [...prev, msg2]);
|
|
52832
|
+
return;
|
|
52833
|
+
}
|
|
52834
|
+
const deleted = deleteChat(name);
|
|
52835
|
+
if (!deleted) {
|
|
52836
|
+
const msg2 = {
|
|
52837
|
+
role: "assistant",
|
|
52838
|
+
content: `Chat **${name}** not found.`,
|
|
52839
|
+
type: "text"
|
|
52840
|
+
};
|
|
52841
|
+
setCommitted((prev) => [...prev, msg2]);
|
|
52842
|
+
setAllMessages((prev) => [...prev, msg2]);
|
|
52843
|
+
return;
|
|
52844
|
+
}
|
|
52845
|
+
if (chatNameRef.current === name) {
|
|
52846
|
+
chatNameRef.current = null;
|
|
52847
|
+
setChatName(null);
|
|
52848
|
+
}
|
|
52849
|
+
setRecentChats((prev) => prev.filter((n) => n !== name));
|
|
52850
|
+
const msg = {
|
|
52851
|
+
role: "assistant",
|
|
52852
|
+
content: `Chat **${name}** deleted.`,
|
|
52853
|
+
type: "text"
|
|
52854
|
+
};
|
|
52855
|
+
setCommitted((prev) => [...prev, msg]);
|
|
52856
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
52857
|
+
return;
|
|
52858
|
+
}
|
|
52859
|
+
if (text.trim().toLowerCase() === "/chat list") {
|
|
52860
|
+
const chats = listChats(repoPath);
|
|
52861
|
+
const content = chats.length === 0 ? "No saved chats for this repo yet." : `Saved chats:
|
|
52862
|
+
|
|
52863
|
+
${chats.map((c) => `- **${c.name}** · ${c.userMessageCount} messages · ${new Date(c.savedAt).toLocaleString()}`).join(`
|
|
52864
|
+
`)}`;
|
|
52865
|
+
const msg = { role: "assistant", content, type: "text" };
|
|
52866
|
+
setCommitted((prev) => [...prev, msg]);
|
|
52867
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
52868
|
+
return;
|
|
52869
|
+
}
|
|
52870
|
+
if (text.trim().toLowerCase().startsWith("/chat load")) {
|
|
52871
|
+
const parts = text.trim().split(/\s+/);
|
|
52872
|
+
const name = parts.slice(2).join("-");
|
|
52873
|
+
if (!name) {
|
|
52874
|
+
const chats = listChats(repoPath);
|
|
52875
|
+
const content = chats.length === 0 ? "No saved chats found." : `Specify a chat name. Recent chats:
|
|
52876
|
+
|
|
52877
|
+
${chats.slice(0, 10).map((c) => `- **${c.name}**`).join(`
|
|
52878
|
+
`)}`;
|
|
52879
|
+
const msg = { role: "assistant", content, type: "text" };
|
|
52880
|
+
setCommitted((prev) => [...prev, msg]);
|
|
52881
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
52882
|
+
return;
|
|
52883
|
+
}
|
|
52884
|
+
const saved = loadChat(name);
|
|
52885
|
+
if (!saved) {
|
|
52886
|
+
const msg = {
|
|
52887
|
+
role: "assistant",
|
|
52888
|
+
content: `Chat **${name}** not found. Use \`/chat list\` to see saved chats.`,
|
|
52889
|
+
type: "text"
|
|
52890
|
+
};
|
|
52891
|
+
setCommitted((prev) => [...prev, msg]);
|
|
52892
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
52893
|
+
return;
|
|
52894
|
+
}
|
|
52895
|
+
updateChatName(name);
|
|
52896
|
+
setAllMessages(saved.messages);
|
|
52897
|
+
setCommitted(saved.messages);
|
|
52898
|
+
const notice = {
|
|
52899
|
+
role: "assistant",
|
|
52900
|
+
content: `Loaded chat **${name}** · ${saved.userMessageCount} messages · saved ${new Date(saved.savedAt).toLocaleString()}`,
|
|
52901
|
+
type: "text"
|
|
52902
|
+
};
|
|
52903
|
+
setCommitted((prev) => [...prev, notice]);
|
|
52904
|
+
setAllMessages((prev) => [...prev, notice]);
|
|
52905
|
+
return;
|
|
52906
|
+
}
|
|
52907
|
+
if (text.trim().toLowerCase() === "/memory list" || text.trim().toLowerCase() === "/memory") {
|
|
52908
|
+
const mems = listMemories(repoPath);
|
|
52909
|
+
const content = mems.length === 0 ? "No memories stored for this repo yet." : `Memories for this repo:
|
|
52910
|
+
|
|
52911
|
+
${mems.map((m) => `- [${m.id}] ${m.content}`).join(`
|
|
52912
|
+
`)}`;
|
|
52913
|
+
const msg = { role: "assistant", content, type: "text" };
|
|
52914
|
+
setCommitted((prev) => [...prev, msg]);
|
|
52915
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
52916
|
+
return;
|
|
52917
|
+
}
|
|
52918
|
+
if (text.trim().toLowerCase().startsWith("/memory add")) {
|
|
52919
|
+
const content = text.trim().slice("/memory add".length).trim();
|
|
52920
|
+
if (!content) {
|
|
52921
|
+
const msg2 = {
|
|
52922
|
+
role: "assistant",
|
|
52923
|
+
content: "Usage: `/memory add <content>`",
|
|
52924
|
+
type: "text"
|
|
52925
|
+
};
|
|
52926
|
+
setCommitted((prev) => [...prev, msg2]);
|
|
52927
|
+
setAllMessages((prev) => [...prev, msg2]);
|
|
52928
|
+
return;
|
|
52929
|
+
}
|
|
52930
|
+
const mem = addMemory(content, repoPath);
|
|
52931
|
+
const msg = {
|
|
52932
|
+
role: "assistant",
|
|
52933
|
+
content: `Memory saved **[${mem.id}]**: ${mem.content}`,
|
|
52934
|
+
type: "text"
|
|
52935
|
+
};
|
|
52936
|
+
setCommitted((prev) => [...prev, msg]);
|
|
52937
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
52938
|
+
return;
|
|
52939
|
+
}
|
|
52940
|
+
if (text.trim().toLowerCase().startsWith("/memory delete")) {
|
|
52941
|
+
const id = text.trim().split(/\s+/)[2];
|
|
52942
|
+
if (!id) {
|
|
52943
|
+
const msg2 = {
|
|
52944
|
+
role: "assistant",
|
|
52945
|
+
content: "Usage: `/memory delete <id>`",
|
|
52946
|
+
type: "text"
|
|
52947
|
+
};
|
|
52948
|
+
setCommitted((prev) => [...prev, msg2]);
|
|
52949
|
+
setAllMessages((prev) => [...prev, msg2]);
|
|
52950
|
+
return;
|
|
52951
|
+
}
|
|
52952
|
+
const deleted = deleteMemory(id, repoPath);
|
|
52953
|
+
const msg = {
|
|
52954
|
+
role: "assistant",
|
|
52955
|
+
content: deleted ? `Memory **[${id}]** deleted.` : `Memory **[${id}]** not found.`,
|
|
52956
|
+
type: "text"
|
|
52957
|
+
};
|
|
52958
|
+
setCommitted((prev) => [...prev, msg]);
|
|
52959
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
52960
|
+
return;
|
|
52961
|
+
}
|
|
52962
|
+
if (text.trim().toLowerCase() === "/memory clear") {
|
|
52963
|
+
clearRepoMemory(repoPath);
|
|
52964
|
+
const msg = {
|
|
52965
|
+
role: "assistant",
|
|
52966
|
+
content: "All memories cleared for this repo.",
|
|
52967
|
+
type: "text"
|
|
52968
|
+
};
|
|
52969
|
+
setCommitted((prev) => [...prev, msg]);
|
|
52970
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
52663
52971
|
return;
|
|
52664
52972
|
}
|
|
52665
52973
|
const userMsg = { role: "user", content: text, type: "text" };
|
|
@@ -52667,6 +52975,18 @@ var ChatRunner = ({ repoPath }) => {
|
|
|
52667
52975
|
setCommitted((prev) => [...prev, userMsg]);
|
|
52668
52976
|
setAllMessages(nextAll);
|
|
52669
52977
|
toolResultCache.current.clear();
|
|
52978
|
+
batchApprovedRef.current = false;
|
|
52979
|
+
inputHistoryRef.current = [
|
|
52980
|
+
text,
|
|
52981
|
+
...inputHistoryRef.current.filter((m) => m !== text)
|
|
52982
|
+
].slice(0, 50);
|
|
52983
|
+
historyIndexRef.current = -1;
|
|
52984
|
+
if (!chatName) {
|
|
52985
|
+
const name = getChatNameSuggestions(nextAll)[0] ?? `chat-${new Date().toISOString().slice(0, 10)}`;
|
|
52986
|
+
updateChatName(name);
|
|
52987
|
+
setRecentChats((prev) => [name, ...prev.filter((n) => n !== name)].slice(0, 10));
|
|
52988
|
+
saveChat(name, repoPath, nextAll);
|
|
52989
|
+
}
|
|
52670
52990
|
const abort = new AbortController;
|
|
52671
52991
|
abortControllerRef.current = abort;
|
|
52672
52992
|
setStage({ type: "thinking" });
|
|
@@ -52678,6 +52998,7 @@ var ChatRunner = ({ repoPath }) => {
|
|
|
52678
52998
|
if (stage.type === "thinking" && key.escape) {
|
|
52679
52999
|
abortControllerRef.current?.abort();
|
|
52680
53000
|
abortControllerRef.current = null;
|
|
53001
|
+
batchApprovedRef.current = false;
|
|
52681
53002
|
setStage({ type: "idle" });
|
|
52682
53003
|
return;
|
|
52683
53004
|
}
|
|
@@ -52686,6 +53007,20 @@ var ChatRunner = ({ repoPath }) => {
|
|
|
52686
53007
|
process.exit(0);
|
|
52687
53008
|
return;
|
|
52688
53009
|
}
|
|
53010
|
+
if (key.upArrow && inputHistoryRef.current.length > 0) {
|
|
53011
|
+
const next = Math.min(historyIndexRef.current + 1, inputHistoryRef.current.length - 1);
|
|
53012
|
+
historyIndexRef.current = next;
|
|
53013
|
+
setInputValue(inputHistoryRef.current[next]);
|
|
53014
|
+
setInputKey((k) => k + 1);
|
|
53015
|
+
return;
|
|
53016
|
+
}
|
|
53017
|
+
if (key.downArrow) {
|
|
53018
|
+
const next = historyIndexRef.current - 1;
|
|
53019
|
+
historyIndexRef.current = next;
|
|
53020
|
+
setInputValue(next < 0 ? "" : inputHistoryRef.current[next]);
|
|
53021
|
+
setInputKey((k) => k + 1);
|
|
53022
|
+
return;
|
|
53023
|
+
}
|
|
52689
53024
|
if (key.tab && inputValue.startsWith("/")) {
|
|
52690
53025
|
const q = inputValue.toLowerCase();
|
|
52691
53026
|
const match = COMMANDS.find((c) => c.cmd.startsWith(q));
|
|
@@ -52704,9 +53039,9 @@ var ChatRunner = ({ repoPath }) => {
|
|
|
52704
53039
|
startCloneRepo(cloneUrl).then((result2) => {
|
|
52705
53040
|
if (result2.done) {
|
|
52706
53041
|
const repoName = cloneUrl.split("/").pop()?.replace(/\.git$/, "") ?? "repo";
|
|
52707
|
-
const destPath =
|
|
53042
|
+
const destPath = path18.join(os9.tmpdir(), repoName);
|
|
52708
53043
|
const fileCount = walkDir3(destPath).length;
|
|
52709
|
-
|
|
53044
|
+
appendMemory({
|
|
52710
53045
|
kind: "url-fetched",
|
|
52711
53046
|
detail: repoUrl,
|
|
52712
53047
|
summary: `Cloned ${repoName} — ${fileCount} files`,
|
|
@@ -52742,16 +53077,14 @@ var ChatRunner = ({ repoPath }) => {
|
|
|
52742
53077
|
if (stage.type === "clone-exists") {
|
|
52743
53078
|
if (input === "y" || input === "Y") {
|
|
52744
53079
|
const { repoUrl, repoPath: existingPath } = stage;
|
|
52745
|
-
const cloneUrl = toCloneUrl(repoUrl);
|
|
52746
53080
|
setStage({ type: "cloning", repoUrl });
|
|
52747
|
-
startCloneRepo(
|
|
53081
|
+
startCloneRepo(toCloneUrl(repoUrl), { forceReclone: true }).then((result2) => {
|
|
52748
53082
|
if (result2.done) {
|
|
52749
|
-
const fileCount = walkDir3(existingPath).length;
|
|
52750
53083
|
setStage({
|
|
52751
53084
|
type: "clone-done",
|
|
52752
53085
|
repoUrl,
|
|
52753
53086
|
destPath: existingPath,
|
|
52754
|
-
fileCount
|
|
53087
|
+
fileCount: walkDir3(existingPath).length
|
|
52755
53088
|
});
|
|
52756
53089
|
} else {
|
|
52757
53090
|
setStage({
|
|
@@ -52764,12 +53097,11 @@ var ChatRunner = ({ repoPath }) => {
|
|
|
52764
53097
|
}
|
|
52765
53098
|
if (input === "n" || input === "N") {
|
|
52766
53099
|
const { repoUrl, repoPath: existingPath } = stage;
|
|
52767
|
-
const fileCount = walkDir3(existingPath).length;
|
|
52768
53100
|
setStage({
|
|
52769
53101
|
type: "clone-done",
|
|
52770
53102
|
repoUrl,
|
|
52771
53103
|
destPath: existingPath,
|
|
52772
|
-
fileCount
|
|
53104
|
+
fileCount: walkDir3(existingPath).length
|
|
52773
53105
|
});
|
|
52774
53106
|
return;
|
|
52775
53107
|
}
|
|
@@ -52791,7 +53123,7 @@ Ask me anything about it — I can read files, explain how it works, or suggest
|
|
|
52791
53123
|
type: "tool",
|
|
52792
53124
|
toolName: "fetch",
|
|
52793
53125
|
content: stage.repoUrl,
|
|
52794
|
-
result: `Clone complete. Repo: ${repoName}. Local path: ${stage.destPath}. ${stage.fileCount} files
|
|
53126
|
+
result: `Clone complete. Repo: ${repoName}. Local path: ${stage.destPath}. ${stage.fileCount} files.`,
|
|
52795
53127
|
approved: true
|
|
52796
53128
|
};
|
|
52797
53129
|
const withClone = [...allMessages, contextMsg, summaryMsg];
|
|
@@ -52812,6 +53144,7 @@ Ask me anything about it — I can read files, explain how it works, or suggest
|
|
|
52812
53144
|
return;
|
|
52813
53145
|
}
|
|
52814
53146
|
if (input === "n" || input === "N" || key.escape) {
|
|
53147
|
+
batchApprovedRef.current = false;
|
|
52815
53148
|
stage.resolve(false);
|
|
52816
53149
|
return;
|
|
52817
53150
|
}
|
|
@@ -52834,7 +53167,7 @@ Ask me anything about it — I can read files, explain how it works, or suggest
|
|
|
52834
53167
|
const msg = allMessages[pendingMsgIndex];
|
|
52835
53168
|
if (msg?.type === "plan") {
|
|
52836
53169
|
setCommitted((prev) => [...prev, { ...msg, applied: false }]);
|
|
52837
|
-
|
|
53170
|
+
appendMemory({
|
|
52838
53171
|
kind: "code-skipped",
|
|
52839
53172
|
detail: msg.patches.map((p) => p.path).join(", "),
|
|
52840
53173
|
summary: `Skipped changes to ${msg.patches.length} file(s)`,
|
|
@@ -52849,7 +53182,7 @@ Ask me anything about it — I can read files, explain how it works, or suggest
|
|
|
52849
53182
|
if (key.return || input === "a" || input === "A") {
|
|
52850
53183
|
try {
|
|
52851
53184
|
applyPatches3(repoPath, stage.patches);
|
|
52852
|
-
|
|
53185
|
+
appendMemory({
|
|
52853
53186
|
kind: "code-applied",
|
|
52854
53187
|
detail: stage.patches.map((p) => p.path).join(", "),
|
|
52855
53188
|
summary: `Applied changes to ${stage.patches.length} file(s)`,
|
|
@@ -52892,7 +53225,7 @@ Ask me anything about it — I can read files, explain how it works, or suggest
|
|
|
52892
53225
|
setStage({ type: "loading" });
|
|
52893
53226
|
fetchFileTree(repoPath).catch(() => walkDir3(repoPath)).then((fileTree) => {
|
|
52894
53227
|
const importantFiles = readImportantFiles(repoPath, fileTree);
|
|
52895
|
-
const historySummary =
|
|
53228
|
+
const historySummary = buildMemorySummary(repoPath);
|
|
52896
53229
|
const lensFile = readLensFile(repoPath);
|
|
52897
53230
|
const lensContext = lensFile ? `
|
|
52898
53231
|
|
|
@@ -52901,17 +53234,16 @@ ${lensFile.overview}
|
|
|
52901
53234
|
|
|
52902
53235
|
Important folders: ${lensFile.importantFolders.join(", ")}
|
|
52903
53236
|
Suggestions: ${lensFile.suggestions.slice(0, 3).join("; ")}` : "";
|
|
52904
|
-
|
|
52905
|
-
|
|
52906
|
-
|
|
52907
|
-
I have memory of previous actions in this repo.` : "";
|
|
52908
|
-
const lensGreetNote = lensFile ? `
|
|
52909
|
-
|
|
52910
|
-
Found LENS.md — I have context from a previous analysis of this repo.` : "";
|
|
53237
|
+
const toolsSection = registry.buildSystemPromptSection();
|
|
53238
|
+
setSystemPrompt(buildSystemPrompt(importantFiles, historySummary, toolsSection) + lensContext);
|
|
52911
53239
|
const greeting = {
|
|
52912
53240
|
role: "assistant",
|
|
52913
|
-
content: `Welcome to Lens
|
|
52914
|
-
Codebase loaded — ${importantFiles.length} files indexed.${
|
|
53241
|
+
content: `Welcome to Lens
|
|
53242
|
+
Codebase loaded — ${importantFiles.length} files indexed.${historySummary ? `
|
|
53243
|
+
|
|
53244
|
+
I have memory of previous actions in this repo.` : ""}${lensFile ? `
|
|
53245
|
+
|
|
53246
|
+
Found LENS.md — I have context from a previous analysis of this repo.` : ""}
|
|
52915
53247
|
Ask me anything, tell me what to build, share a URL, or ask me to read/write files.
|
|
52916
53248
|
|
|
52917
53249
|
Tip: type /timeline to browse commit history.`,
|
|
@@ -52926,7 +53258,7 @@ Tip: type /timeline to browse commit history.`,
|
|
|
52926
53258
|
return /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(ProviderPicker, {
|
|
52927
53259
|
onDone: handleProviderDone
|
|
52928
53260
|
}, undefined, false, undefined, this);
|
|
52929
|
-
if (stage.type === "loading")
|
|
53261
|
+
if (stage.type === "loading")
|
|
52930
53262
|
return /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(Box_default, {
|
|
52931
53263
|
gap: 1,
|
|
52932
53264
|
marginTop: 1,
|
|
@@ -52946,19 +53278,16 @@ Tip: type /timeline to browse commit history.`,
|
|
|
52946
53278
|
}, undefined, false, undefined, this)
|
|
52947
53279
|
]
|
|
52948
53280
|
}, undefined, true, undefined, this);
|
|
52949
|
-
|
|
52950
|
-
if (showTimeline) {
|
|
53281
|
+
if (showTimeline)
|
|
52951
53282
|
return /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(TimelineRunner, {
|
|
52952
53283
|
repoPath,
|
|
52953
53284
|
onExit: () => setShowTimeline(false)
|
|
52954
53285
|
}, undefined, false, undefined, this);
|
|
52955
|
-
|
|
52956
|
-
if (showReview) {
|
|
53286
|
+
if (showReview)
|
|
52957
53287
|
return /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(ReviewCommand, {
|
|
52958
53288
|
path: repoPath,
|
|
52959
53289
|
onExit: () => setShowReview(false)
|
|
52960
53290
|
}, undefined, false, undefined, this);
|
|
52961
|
-
}
|
|
52962
53291
|
if (stage.type === "clone-offer")
|
|
52963
53292
|
return /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(CloneOfferView, {
|
|
52964
53293
|
stage,
|
|
@@ -53029,18 +53358,21 @@ Tip: type /timeline to browse commit history.`,
|
|
|
53029
53358
|
children: [
|
|
53030
53359
|
inputValue.startsWith("/") && /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(CommandPalette, {
|
|
53031
53360
|
query: inputValue,
|
|
53032
|
-
onSelect: (cmd) =>
|
|
53033
|
-
|
|
53034
|
-
}
|
|
53361
|
+
onSelect: (cmd) => setInputValue(cmd),
|
|
53362
|
+
recentChats
|
|
53035
53363
|
}, undefined, false, undefined, this),
|
|
53036
53364
|
/* @__PURE__ */ jsx_dev_runtime22.jsxDEV(InputBox, {
|
|
53037
53365
|
value: inputValue,
|
|
53038
|
-
onChange:
|
|
53366
|
+
onChange: (v) => {
|
|
53367
|
+
historyIndexRef.current = -1;
|
|
53368
|
+
setInputValue(v);
|
|
53369
|
+
},
|
|
53039
53370
|
onSubmit: (val) => {
|
|
53040
53371
|
if (val.trim())
|
|
53041
53372
|
sendMessage(val.trim());
|
|
53042
53373
|
setInputValue("");
|
|
53043
|
-
}
|
|
53374
|
+
},
|
|
53375
|
+
inputKey
|
|
53044
53376
|
}, undefined, false, undefined, this),
|
|
53045
53377
|
/* @__PURE__ */ jsx_dev_runtime22.jsxDEV(ShortcutBar, {
|
|
53046
53378
|
autoApprove
|
|
@@ -53054,8 +53386,8 @@ Tip: type /timeline to browse commit history.`,
|
|
|
53054
53386
|
// src/commands/chat.tsx
|
|
53055
53387
|
var jsx_dev_runtime23 = __toESM(require_jsx_dev_runtime(), 1);
|
|
53056
53388
|
var ChatCommand = ({ path: inputPath }) => {
|
|
53057
|
-
const resolvedPath =
|
|
53058
|
-
if (!
|
|
53389
|
+
const resolvedPath = path19.resolve(inputPath);
|
|
53390
|
+
if (!existsSync14(resolvedPath)) {
|
|
53059
53391
|
return /* @__PURE__ */ jsx_dev_runtime23.jsxDEV(Box_default, {
|
|
53060
53392
|
marginTop: 1,
|
|
53061
53393
|
children: /* @__PURE__ */ jsx_dev_runtime23.jsxDEV(Text, {
|
|
@@ -53074,12 +53406,12 @@ var ChatCommand = ({ path: inputPath }) => {
|
|
|
53074
53406
|
};
|
|
53075
53407
|
|
|
53076
53408
|
// src/commands/timeline.tsx
|
|
53077
|
-
import { existsSync as
|
|
53078
|
-
import
|
|
53409
|
+
import { existsSync as existsSync15 } from "fs";
|
|
53410
|
+
import path20 from "path";
|
|
53079
53411
|
var jsx_dev_runtime24 = __toESM(require_jsx_dev_runtime(), 1);
|
|
53080
53412
|
var TimelineCommand = ({ path: inputPath }) => {
|
|
53081
|
-
const resolvedPath =
|
|
53082
|
-
if (!
|
|
53413
|
+
const resolvedPath = path20.resolve(inputPath);
|
|
53414
|
+
if (!existsSync15(resolvedPath)) {
|
|
53083
53415
|
return /* @__PURE__ */ jsx_dev_runtime24.jsxDEV(Box_default, {
|
|
53084
53416
|
marginTop: 1,
|
|
53085
53417
|
children: /* @__PURE__ */ jsx_dev_runtime24.jsxDEV(Text, {
|
|
@@ -53096,9 +53428,259 @@ var TimelineCommand = ({ path: inputPath }) => {
|
|
|
53096
53428
|
repoPath: resolvedPath
|
|
53097
53429
|
}, undefined, false, undefined, this);
|
|
53098
53430
|
};
|
|
53431
|
+
// src/utils/tools/builtins.ts
|
|
53432
|
+
var fetchTool = {
|
|
53433
|
+
name: "fetch",
|
|
53434
|
+
description: "load a URL",
|
|
53435
|
+
safe: true,
|
|
53436
|
+
permissionLabel: "fetch",
|
|
53437
|
+
systemPromptEntry: (i) => `### ${i}. fetch — load a URL
|
|
53438
|
+
<fetch>https://example.com</fetch>`,
|
|
53439
|
+
parseInput: (body) => body.replace(/^<|>$/g, "").trim() || null,
|
|
53440
|
+
summariseInput: (url) => url,
|
|
53441
|
+
execute: async (url) => {
|
|
53442
|
+
try {
|
|
53443
|
+
const value = await fetchUrl(url);
|
|
53444
|
+
return { kind: "text", value };
|
|
53445
|
+
} catch (err) {
|
|
53446
|
+
return {
|
|
53447
|
+
kind: "error",
|
|
53448
|
+
value: `Fetch failed: ${err instanceof Error ? err.message : String(err)}`
|
|
53449
|
+
};
|
|
53450
|
+
}
|
|
53451
|
+
}
|
|
53452
|
+
};
|
|
53453
|
+
var shellTool = {
|
|
53454
|
+
name: "shell",
|
|
53455
|
+
description: "run a terminal command",
|
|
53456
|
+
safe: false,
|
|
53457
|
+
permissionLabel: "run",
|
|
53458
|
+
systemPromptEntry: (i) => `### ${i}. shell — run a terminal command
|
|
53459
|
+
<shell>node -v</shell>`,
|
|
53460
|
+
parseInput: (body) => body || null,
|
|
53461
|
+
summariseInput: (cmd) => cmd,
|
|
53462
|
+
execute: async (cmd, ctx) => {
|
|
53463
|
+
const value = await runShell(cmd, ctx.repoPath);
|
|
53464
|
+
return { kind: "text", value };
|
|
53465
|
+
}
|
|
53466
|
+
};
|
|
53467
|
+
var readFileTool = {
|
|
53468
|
+
name: "read-file",
|
|
53469
|
+
description: "read a file from the repo",
|
|
53470
|
+
safe: true,
|
|
53471
|
+
permissionLabel: "read",
|
|
53472
|
+
systemPromptEntry: (i) => `### ${i}. read-file — read a file from the repo
|
|
53473
|
+
<read-file>src/foo.ts</read-file>`,
|
|
53474
|
+
parseInput: (body) => body || null,
|
|
53475
|
+
summariseInput: (p) => p,
|
|
53476
|
+
execute: (filePath, ctx) => ({
|
|
53477
|
+
kind: "text",
|
|
53478
|
+
value: readFile(filePath, ctx.repoPath)
|
|
53479
|
+
})
|
|
53480
|
+
};
|
|
53481
|
+
var readFolderTool = {
|
|
53482
|
+
name: "read-folder",
|
|
53483
|
+
description: "list contents of a folder (files + subfolders, one level deep)",
|
|
53484
|
+
safe: true,
|
|
53485
|
+
permissionLabel: "folder",
|
|
53486
|
+
systemPromptEntry: (i) => `### ${i}. read-folder — list contents of a folder (files + subfolders, one level deep)
|
|
53487
|
+
<read-folder>src/components</read-folder>`,
|
|
53488
|
+
parseInput: (body) => body || null,
|
|
53489
|
+
summariseInput: (p) => p,
|
|
53490
|
+
execute: (folderPath, ctx) => ({
|
|
53491
|
+
kind: "text",
|
|
53492
|
+
value: readFolder(folderPath, ctx.repoPath)
|
|
53493
|
+
})
|
|
53494
|
+
};
|
|
53495
|
+
var grepTool = {
|
|
53496
|
+
name: "grep",
|
|
53497
|
+
description: "search for a pattern across files in the repo",
|
|
53498
|
+
safe: true,
|
|
53499
|
+
permissionLabel: "grep",
|
|
53500
|
+
systemPromptEntry: (i) => `### ${i}. grep — search for a pattern across files in the repo (cross-platform, no shell needed)
|
|
53501
|
+
<grep>
|
|
53502
|
+
{"pattern": "ChatRunner", "glob": "src/**/*.tsx"}
|
|
53503
|
+
</grep>`,
|
|
53504
|
+
parseInput: (body) => {
|
|
53505
|
+
try {
|
|
53506
|
+
const parsed = JSON.parse(body);
|
|
53507
|
+
return { pattern: parsed.pattern, glob: parsed.glob ?? "**/*" };
|
|
53508
|
+
} catch {
|
|
53509
|
+
return { pattern: body, glob: "**/*" };
|
|
53510
|
+
}
|
|
53511
|
+
},
|
|
53512
|
+
summariseInput: ({ pattern, glob }) => `${pattern} — ${glob}`,
|
|
53513
|
+
execute: ({ pattern, glob }, ctx) => ({
|
|
53514
|
+
kind: "text",
|
|
53515
|
+
value: grepFiles(pattern, glob, ctx.repoPath)
|
|
53516
|
+
})
|
|
53517
|
+
};
|
|
53518
|
+
var writeFileTool = {
|
|
53519
|
+
name: "write-file",
|
|
53520
|
+
description: "create or overwrite a file",
|
|
53521
|
+
safe: false,
|
|
53522
|
+
permissionLabel: "write",
|
|
53523
|
+
systemPromptEntry: (i) => `### ${i}. write-file — create or overwrite a file
|
|
53524
|
+
<write-file>
|
|
53525
|
+
{"path": "data/output.csv", "content": "col1,col2\\nval1,val2"}
|
|
53526
|
+
</write-file>`,
|
|
53527
|
+
parseInput: (body) => {
|
|
53528
|
+
try {
|
|
53529
|
+
const parsed = JSON.parse(body);
|
|
53530
|
+
if (!parsed.path)
|
|
53531
|
+
return null;
|
|
53532
|
+
return parsed;
|
|
53533
|
+
} catch {
|
|
53534
|
+
return null;
|
|
53535
|
+
}
|
|
53536
|
+
},
|
|
53537
|
+
summariseInput: ({ path: path21, content }) => `${path21} (${content.length} bytes)`,
|
|
53538
|
+
execute: ({ path: filePath, content }, ctx) => ({
|
|
53539
|
+
kind: "text",
|
|
53540
|
+
value: writeFile(filePath, content, ctx.repoPath)
|
|
53541
|
+
})
|
|
53542
|
+
};
|
|
53543
|
+
var deleteFileTool = {
|
|
53544
|
+
name: "delete-file",
|
|
53545
|
+
description: "permanently delete a single file",
|
|
53546
|
+
safe: false,
|
|
53547
|
+
permissionLabel: "delete",
|
|
53548
|
+
systemPromptEntry: (i) => `### ${i}. delete-file — permanently delete a single file
|
|
53549
|
+
<delete-file>src/old-component.tsx</delete-file>`,
|
|
53550
|
+
parseInput: (body) => body || null,
|
|
53551
|
+
summariseInput: (p) => p,
|
|
53552
|
+
execute: (filePath, ctx) => ({
|
|
53553
|
+
kind: "text",
|
|
53554
|
+
value: deleteFile(filePath, ctx.repoPath)
|
|
53555
|
+
})
|
|
53556
|
+
};
|
|
53557
|
+
var deleteFolderTool = {
|
|
53558
|
+
name: "delete-folder",
|
|
53559
|
+
description: "permanently delete a folder and all its contents",
|
|
53560
|
+
safe: false,
|
|
53561
|
+
permissionLabel: "delete folder",
|
|
53562
|
+
systemPromptEntry: (i) => `### ${i}. delete-folder — permanently delete a folder and all its contents
|
|
53563
|
+
<delete-folder>src/legacy</delete-folder>`,
|
|
53564
|
+
parseInput: (body) => body || null,
|
|
53565
|
+
summariseInput: (p) => p,
|
|
53566
|
+
execute: (folderPath, ctx) => ({
|
|
53567
|
+
kind: "text",
|
|
53568
|
+
value: deleteFolder(folderPath, ctx.repoPath)
|
|
53569
|
+
})
|
|
53570
|
+
};
|
|
53571
|
+
var openUrlTool = {
|
|
53572
|
+
name: "open-url",
|
|
53573
|
+
description: "open a URL in the user's default browser",
|
|
53574
|
+
safe: true,
|
|
53575
|
+
permissionLabel: "open",
|
|
53576
|
+
systemPromptEntry: (i) => `### ${i}. open-url — open a URL in the user's default browser
|
|
53577
|
+
<open-url>https://github.com/owner/repo</open-url>`,
|
|
53578
|
+
parseInput: (body) => body.replace(/^<|>$/g, "").trim() || null,
|
|
53579
|
+
summariseInput: (url) => url,
|
|
53580
|
+
execute: (url) => ({ kind: "text", value: openUrl(url) })
|
|
53581
|
+
};
|
|
53582
|
+
var generatePdfTool = {
|
|
53583
|
+
name: "generate-pdf",
|
|
53584
|
+
description: "generate a PDF file from markdown-style content",
|
|
53585
|
+
safe: false,
|
|
53586
|
+
permissionLabel: "pdf",
|
|
53587
|
+
systemPromptEntry: (i) => `### ${i}. generate-pdf — generate a PDF file from markdown-style content
|
|
53588
|
+
<generate-pdf>
|
|
53589
|
+
{"path": "output/report.pdf", "content": "# Title\\n\\nSome body text."}
|
|
53590
|
+
</generate-pdf>`,
|
|
53591
|
+
parseInput: (body) => {
|
|
53592
|
+
try {
|
|
53593
|
+
const parsed = JSON.parse(body);
|
|
53594
|
+
return {
|
|
53595
|
+
filePath: parsed.path ?? parsed.filePath ?? "output.pdf",
|
|
53596
|
+
content: parsed.content ?? ""
|
|
53597
|
+
};
|
|
53598
|
+
} catch {
|
|
53599
|
+
return null;
|
|
53600
|
+
}
|
|
53601
|
+
},
|
|
53602
|
+
summariseInput: ({ filePath }) => filePath,
|
|
53603
|
+
execute: ({ filePath, content }, ctx) => ({
|
|
53604
|
+
kind: "text",
|
|
53605
|
+
value: generatePdf(filePath, content, ctx.repoPath)
|
|
53606
|
+
})
|
|
53607
|
+
};
|
|
53608
|
+
var searchTool = {
|
|
53609
|
+
name: "search",
|
|
53610
|
+
description: "search the internet for anything you are unsure about",
|
|
53611
|
+
safe: true,
|
|
53612
|
+
permissionLabel: "search",
|
|
53613
|
+
systemPromptEntry: (i) => `### ${i}. search — search the internet for anything you are unsure about
|
|
53614
|
+
<search>how to use React useEffect cleanup function</search>`,
|
|
53615
|
+
parseInput: (body) => body || null,
|
|
53616
|
+
summariseInput: (q) => `"${q}"`,
|
|
53617
|
+
execute: async (query) => {
|
|
53618
|
+
try {
|
|
53619
|
+
const value = await searchWeb(query);
|
|
53620
|
+
return { kind: "text", value };
|
|
53621
|
+
} catch (err) {
|
|
53622
|
+
return {
|
|
53623
|
+
kind: "error",
|
|
53624
|
+
value: `Search failed: ${err instanceof Error ? err.message : String(err)}`
|
|
53625
|
+
};
|
|
53626
|
+
}
|
|
53627
|
+
}
|
|
53628
|
+
};
|
|
53629
|
+
var cloneTool = {
|
|
53630
|
+
name: "clone",
|
|
53631
|
+
description: "clone a GitHub repo so you can explore and discuss it",
|
|
53632
|
+
safe: false,
|
|
53633
|
+
permissionLabel: "clone",
|
|
53634
|
+
systemPromptEntry: (i) => `### ${i}. clone — clone a GitHub repo so you can explore and discuss it
|
|
53635
|
+
<clone>https://github.com/owner/repo</clone>`,
|
|
53636
|
+
parseInput: (body) => body.replace(/^<|>$/g, "").trim() || null,
|
|
53637
|
+
summariseInput: (url) => url,
|
|
53638
|
+
execute: (repoUrl) => ({
|
|
53639
|
+
kind: "text",
|
|
53640
|
+
value: `Clone of ${repoUrl} was handled by the UI.`
|
|
53641
|
+
})
|
|
53642
|
+
};
|
|
53643
|
+
var changesTool = {
|
|
53644
|
+
name: "changes",
|
|
53645
|
+
description: "propose code edits (shown as a diff for user approval)",
|
|
53646
|
+
safe: false,
|
|
53647
|
+
permissionLabel: "changes",
|
|
53648
|
+
systemPromptEntry: (i) => `### ${i}. changes — propose code edits (shown as a diff for user approval)
|
|
53649
|
+
<changes>
|
|
53650
|
+
{"summary": "what changed and why", "patches": [{"path": "src/foo.ts", "content": "COMPLETE file content", "isNew": false}]}
|
|
53651
|
+
</changes>`,
|
|
53652
|
+
parseInput: (body) => {
|
|
53653
|
+
try {
|
|
53654
|
+
return JSON.parse(body);
|
|
53655
|
+
} catch {
|
|
53656
|
+
return null;
|
|
53657
|
+
}
|
|
53658
|
+
},
|
|
53659
|
+
summariseInput: ({ summary }) => summary,
|
|
53660
|
+
execute: ({ summary }) => ({
|
|
53661
|
+
kind: "text",
|
|
53662
|
+
value: `Changes proposed: ${summary}`
|
|
53663
|
+
})
|
|
53664
|
+
};
|
|
53665
|
+
function registerBuiltins() {
|
|
53666
|
+
registry.register(fetchTool);
|
|
53667
|
+
registry.register(shellTool);
|
|
53668
|
+
registry.register(readFileTool);
|
|
53669
|
+
registry.register(readFolderTool);
|
|
53670
|
+
registry.register(grepTool);
|
|
53671
|
+
registry.register(writeFileTool);
|
|
53672
|
+
registry.register(deleteFileTool);
|
|
53673
|
+
registry.register(deleteFolderTool);
|
|
53674
|
+
registry.register(openUrlTool);
|
|
53675
|
+
registry.register(generatePdfTool);
|
|
53676
|
+
registry.register(searchTool);
|
|
53677
|
+
registry.register(cloneTool);
|
|
53678
|
+
registry.register(changesTool);
|
|
53679
|
+
}
|
|
53099
53680
|
|
|
53100
53681
|
// src/index.tsx
|
|
53101
53682
|
var jsx_dev_runtime25 = __toESM(require_jsx_dev_runtime(), 1);
|
|
53683
|
+
registerBuiltins();
|
|
53102
53684
|
var program2 = new Command;
|
|
53103
53685
|
program2.command("repo <url>").description("Analyze a remote repository").action((url) => {
|
|
53104
53686
|
render_default(/* @__PURE__ */ jsx_dev_runtime25.jsxDEV(RepoCommand, {
|