@ridit/lens 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +1553 -1139
- package/package.json +1 -1
- package/src/components/chat/ChatMessage.tsx +15 -1
- package/src/components/chat/ChatOverlays.tsx +8 -1
- package/src/components/chat/ChatRunner.tsx +381 -31
- package/src/prompts/fewshot.ts +377 -0
- package/src/prompts/index.ts +2 -0
- package/src/prompts/system.ts +190 -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 +40 -1308
- package/src/utils/chatHistory.ts +121 -0
- package/src/utils/files.ts +1 -0
- package/src/utils/memory.ts +137 -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,443 +48651,912 @@ 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>
|
|
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)
|
|
48709
48711
|
|
|
48710
|
-
|
|
48711
|
-
<clone>https://github.com/owner/repo</clone>
|
|
48712
|
+
${content.slice(0, 8000)}${content.length > 8000 ? `
|
|
48712
48713
|
|
|
48713
|
-
|
|
48714
|
-
|
|
48715
|
-
|
|
48716
|
-
|
|
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)
|
|
48717
48838
|
|
|
48718
|
-
|
|
48839
|
+
${results.join(`
|
|
48719
48840
|
|
|
48720
|
-
|
|
48721
|
-
|
|
48722
|
-
|
|
48723
|
-
|
|
48724
|
-
|
|
48725
|
-
|
|
48726
|
-
|
|
48727
|
-
|
|
48728
|
-
|
|
48729
|
-
|
|
48730
|
-
|
|
48731
|
-
|
|
48732
|
-
|
|
48733
|
-
|
|
48734
|
-
|
|
48735
|
-
|
|
48736
|
-
|
|
48737
|
-
|
|
48738
|
-
|
|
48739
|
-
|
|
48740
|
-
|
|
48741
|
-
|
|
48742
|
-
|
|
48743
|
-
|
|
48744
|
-
|
|
48745
|
-
|
|
48746
|
-
|
|
48747
|
-
|
|
48748
|
-
|
|
48749
|
-
|
|
48750
|
-
|
|
48751
|
-
|
|
48752
|
-
|
|
48753
|
-
|
|
48754
|
-
|
|
48755
|
-
|
|
48756
|
-
|
|
48757
|
-
|
|
48758
|
-
|
|
48759
|
-
|
|
48760
|
-
|
|
48761
|
-
|
|
48762
|
-
|
|
48763
|
-
|
|
48764
|
-
|
|
48765
|
-
|
|
48766
|
-
|
|
48767
|
-
|
|
48768
|
-
|
|
48769
|
-
|
|
48770
|
-
|
|
48771
|
-
|
|
48772
|
-
|
|
48773
|
-
|
|
48774
|
-
|
|
48775
|
-
|
|
48776
|
-
|
|
48777
|
-
|
|
48778
|
-
|
|
48779
|
-
5
|
|
48780
|
-
|
|
48781
|
-
|
|
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}) ===
|
|
48782
48973
|
|
|
48783
|
-
|
|
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
|
|
48974
|
+
${tables.join(`
|
|
48789
48975
|
|
|
48790
|
-
|
|
48976
|
+
---
|
|
48791
48977
|
|
|
48792
|
-
|
|
48793
|
-
|
|
48794
|
-
|
|
48795
|
-
|
|
48796
|
-
|
|
48797
|
-
|
|
48798
|
-
|
|
48799
|
-
|
|
48800
|
-
|
|
48801
|
-
|
|
48802
|
-
|
|
48803
|
-
|
|
48804
|
-
|
|
48805
|
-
|
|
48806
|
-
|
|
48807
|
-
|
|
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 ===
|
|
48808
48998
|
|
|
48809
|
-
|
|
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
|
-
|
|
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(`
|
|
48834
49070
|
|
|
48835
|
-
|
|
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(`
|
|
48836
49116
|
|
|
48837
|
-
|
|
48838
|
-
}
|
|
48839
|
-
|
|
48840
|
-
|
|
48841
|
-
|
|
48842
|
-
|
|
48843
|
-
|
|
48844
|
-
|
|
48845
|
-
|
|
48846
|
-
|
|
48847
|
-
|
|
48848
|
-
|
|
48849
|
-
|
|
48850
|
-
|
|
48851
|
-
|
|
48852
|
-
|
|
48853
|
-
|
|
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
|
|
48854
49149
|
|
|
48855
|
-
|
|
49150
|
+
doc = SimpleDocTemplate(
|
|
49151
|
+
r"""${fullPath}""",
|
|
49152
|
+
pagesize=letter,
|
|
49153
|
+
rightMargin=inch,
|
|
49154
|
+
leftMargin=inch,
|
|
49155
|
+
topMargin=inch,
|
|
49156
|
+
bottomMargin=inch,
|
|
49157
|
+
)
|
|
48856
49158
|
|
|
48857
|
-
|
|
48858
|
-
|
|
48859
|
-
|
|
48860
|
-
|
|
48861
|
-
|
|
48862
|
-
|
|
48863
|
-
{
|
|
48864
|
-
role: "user",
|
|
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:
|
|
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))
|
|
48874
49165
|
|
|
48875
|
-
|
|
49166
|
+
raw = """${escaped}"""
|
|
48876
49167
|
|
|
48877
|
-
|
|
48878
|
-
|
|
48879
|
-
|
|
48880
|
-
|
|
48881
|
-
|
|
48882
|
-
|
|
48883
|
-
|
|
48884
|
-
|
|
48885
|
-
|
|
48886
|
-
|
|
48887
|
-
|
|
48888
|
-
|
|
48889
|
-
|
|
48890
|
-
|
|
48891
|
-
|
|
48892
|
-
|
|
48893
|
-
|
|
48894
|
-
|
|
48895
|
-
|
|
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"]))
|
|
48896
49192
|
|
|
48897
|
-
|
|
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 = "") {
|
|
49210
|
+
const fileList = files.map((f) => `### ${f.path}
|
|
49211
|
+
\`\`\`
|
|
49212
|
+
${f.content.slice(0, 2000)}
|
|
49213
|
+
\`\`\``).join(`
|
|
48898
49214
|
|
|
48899
|
-
|
|
48900
|
-
|
|
48901
|
-
{
|
|
48902
|
-
role: "assistant",
|
|
48903
|
-
content: "Done — the PDF report has been saved to `docs/report.pdf`."
|
|
48904
|
-
},
|
|
48905
|
-
{
|
|
48906
|
-
role: "user",
|
|
48907
|
-
content: 'grep -R "ChatRunner" -n src'
|
|
48908
|
-
},
|
|
48909
|
-
{
|
|
48910
|
-
role: "assistant",
|
|
48911
|
-
content: `<grep>
|
|
48912
|
-
{"pattern": "ChatRunner", "glob": "src/**/*"}
|
|
48913
|
-
</grep>`
|
|
48914
|
-
},
|
|
48915
|
-
{
|
|
48916
|
-
role: "user",
|
|
48917
|
-
content: `Here is the output from grep for "ChatRunner":
|
|
49215
|
+
`);
|
|
49216
|
+
return `You are an expert software engineer assistant with access to the user's codebase and tools.
|
|
48918
49217
|
|
|
48919
|
-
|
|
49218
|
+
## TOOLS
|
|
48920
49219
|
|
|
48921
|
-
|
|
48922
|
-
12: import { ChatRunner } from "./components/chat/ChatRunner";
|
|
49220
|
+
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.
|
|
48923
49221
|
|
|
48924
|
-
|
|
48925
|
-
|
|
49222
|
+
### 1. fetch — load a URL
|
|
49223
|
+
<fetch>https://example.com</fetch>
|
|
48926
49224
|
|
|
48927
|
-
|
|
48928
|
-
|
|
48929
|
-
|
|
48930
|
-
|
|
48931
|
-
|
|
48932
|
-
|
|
49225
|
+
### 2. shell — run a terminal command
|
|
49226
|
+
<shell>node -v</shell>
|
|
49227
|
+
|
|
49228
|
+
### 3. read-file — read a file from the repo
|
|
49229
|
+
<read-file>src/foo.ts</read-file>
|
|
49230
|
+
|
|
49231
|
+
### 4. read-folder — list contents of a folder (files + subfolders, one level deep)
|
|
49232
|
+
<read-folder>src/components</read-folder>
|
|
49233
|
+
|
|
49234
|
+
### 5. grep — search for a pattern across files in the repo (cross-platform, no shell needed)
|
|
49235
|
+
<grep>
|
|
49236
|
+
{"pattern": "ChatRunner", "glob": "src/**/*.tsx"}
|
|
49237
|
+
</grep>
|
|
49238
|
+
|
|
49239
|
+
### 6. write-file — create or overwrite a file
|
|
49240
|
+
<write-file>
|
|
49241
|
+
{"path": "data/output.csv", "content": "col1,col2
|
|
49242
|
+
val1,val2"}
|
|
49243
|
+
</write-file>
|
|
49244
|
+
|
|
49245
|
+
### 7. delete-file — permanently delete a single file
|
|
49246
|
+
<delete-file>src/old-component.tsx</delete-file>
|
|
49247
|
+
|
|
49248
|
+
### 8. delete-folder — permanently delete a folder and all its contents
|
|
49249
|
+
<delete-folder>src/legacy</delete-folder>
|
|
49250
|
+
|
|
49251
|
+
### 9. open-url — open a URL in the user's default browser
|
|
49252
|
+
<open-url>https://github.com/owner/repo</open-url>
|
|
49253
|
+
|
|
49254
|
+
### 10. generate-pdf — generate a PDF file from markdown-style content
|
|
49255
|
+
<generate-pdf>
|
|
49256
|
+
{"path": "output/report.pdf", "content": "# Title
|
|
49257
|
+
|
|
49258
|
+
Some body text.
|
|
49259
|
+
|
|
49260
|
+
## Section
|
|
49261
|
+
|
|
49262
|
+
More content."}
|
|
49263
|
+
</generate-pdf>
|
|
49264
|
+
|
|
49265
|
+
### 11. search — search the internet for anything you are unsure about
|
|
49266
|
+
<search>how to use React useEffect cleanup function</search>
|
|
49267
|
+
|
|
49268
|
+
### 12. clone — clone a GitHub repo so you can explore and discuss it
|
|
49269
|
+
<clone>https://github.com/owner/repo</clone>
|
|
49270
|
+
|
|
49271
|
+
### 13. changes — propose code edits (shown as a diff for user approval)
|
|
49272
|
+
<changes>
|
|
49273
|
+
{"summary": "what changed and why", "patches": [{"path": "src/foo.ts", "content": "COMPLETE file content", "isNew": false}]}
|
|
49274
|
+
</changes>
|
|
49275
|
+
|
|
49276
|
+
## MEMORY OPERATIONS
|
|
49277
|
+
|
|
49278
|
+
You can save and delete memories at any time by emitting these tags alongside your normal response.
|
|
49279
|
+
They are stripped before display — the user will not see the raw tags.
|
|
49280
|
+
|
|
49281
|
+
### memory-add — save something important to long-term memory for this repo
|
|
49282
|
+
<memory-add>User prefers TypeScript strict mode in all new files</memory-add>
|
|
49283
|
+
|
|
49284
|
+
### memory-delete — delete a memory by its ID (shown in brackets like [abc123])
|
|
49285
|
+
<memory-delete>abc123</memory-delete>
|
|
49286
|
+
|
|
49287
|
+
Use memory-add when:
|
|
49288
|
+
- The user explicitly asks you to remember something ("remember that...", "don't forget...")
|
|
49289
|
+
- You learn something project-specific that would be useful in future sessions
|
|
49290
|
+
(e.g. preferred patterns, architecture decisions, known gotchas, user preferences)
|
|
49291
|
+
|
|
49292
|
+
Use memory-delete when:
|
|
49293
|
+
- The user asks you to forget something
|
|
49294
|
+
- A memory is outdated or wrong and you are replacing it with a new one
|
|
49295
|
+
|
|
49296
|
+
You may emit multiple memory operations in a single response alongside normal content.
|
|
49297
|
+
|
|
49298
|
+
## RULES
|
|
49299
|
+
|
|
49300
|
+
1. When you need to use a tool, output ONLY the XML tag — nothing before or after it in that response
|
|
49301
|
+
2. ONE tool per response — emit the tag, then stop completely
|
|
49302
|
+
3. After the user approves and you get the result, continue your analysis in the next response
|
|
49303
|
+
4. NEVER print a URL, command, filename, or JSON blob as plain text when you should be using a tool
|
|
49304
|
+
5. NEVER say "I'll fetch" / "run this command" / "here's the write-file" — just emit the tag
|
|
49305
|
+
6. NEVER use shell to run git clone — always use the clone tag instead
|
|
49306
|
+
7. NEVER use shell to list files or folders (no ls, dir, find, git ls-files, tree) — ALWAYS use read-folder instead
|
|
49307
|
+
8. NEVER use shell to read a file (no cat, type, Get-Content) — ALWAYS use read-file instead
|
|
49308
|
+
9. NEVER use shell grep, findstr, or Select-String to search file contents — ALWAYS use grep instead
|
|
49309
|
+
10. shell is ONLY for running code, installing packages, building, testing — not for filesystem inspection
|
|
49310
|
+
11. write-file content field must be the COMPLETE file content, never empty or placeholder
|
|
49311
|
+
12. After a write-file succeeds, do NOT repeat it — trust the result and move on
|
|
49312
|
+
13. After a write-file succeeds, tell the user it is done immediately — do NOT auto-read the file back to verify
|
|
49313
|
+
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
|
|
49314
|
+
15. NEVER say "I made a mistake" and repeat the same tool — one attempt is enough, trust the output
|
|
49315
|
+
16. NEVER second-guess yourself mid-response — commit to your answer
|
|
49316
|
+
17. If a read-folder or read-file returns "not found", accept it and move on — do NOT retry the same path
|
|
49317
|
+
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
|
|
49318
|
+
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\`
|
|
49319
|
+
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\`
|
|
49320
|
+
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\`
|
|
49321
|
+
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.
|
|
49322
|
+
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
|
|
49323
|
+
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
|
|
49324
|
+
25. NEVER chain tool calls unless the user's request explicitly requires multiple steps
|
|
49325
|
+
26. NEVER read files, list folders, or run tools that were not asked for in the current user message
|
|
49326
|
+
27. 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.
|
|
49327
|
+
|
|
49328
|
+
## CRITICAL: READ BEFORE YOU WRITE
|
|
49329
|
+
|
|
49330
|
+
These rules are mandatory whenever you plan to edit or create a file:
|
|
49331
|
+
|
|
49332
|
+
### Before modifying ANY existing file:
|
|
49333
|
+
1. ALWAYS use read-file on the exact file you plan to change FIRST
|
|
49334
|
+
2. Study the full current content — understand every import, every export, every type, every existing feature
|
|
49335
|
+
3. Your changes patch MUST preserve ALL existing functionality — do not remove or rewrite things that were not part of the request
|
|
49336
|
+
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
|
|
49337
|
+
|
|
49338
|
+
### Before adding a feature that touches multiple files:
|
|
49339
|
+
1. Use read-folder on the relevant directory to see what files exist
|
|
49340
|
+
2. Use read-file on each file you plan to touch
|
|
49341
|
+
3. Only then emit a changes tag — with patches that are surgical additions, not wholesale rewrites
|
|
49342
|
+
|
|
49343
|
+
### The golden rule for write-file and changes:
|
|
49344
|
+
- The output file must contain EVERYTHING the original had, PLUS your new additions
|
|
49345
|
+
- NEVER produce a file that is shorter than the original unless you are explicitly asked to delete things
|
|
49346
|
+
- If you catch yourself rewriting a file from scratch, STOP — go back and read the original first
|
|
49347
|
+
|
|
49348
|
+
## WHEN TO USE read-folder:
|
|
49349
|
+
- Before editing files in an unfamiliar directory — list it first to understand the structure
|
|
49350
|
+
- When a feature spans multiple files and you are not sure what exists
|
|
49351
|
+
- When the user asks you to explore or explain a part of the codebase
|
|
49352
|
+
|
|
49353
|
+
## SCAFFOLDING A NEW PROJECT (follow this exactly)
|
|
49354
|
+
|
|
49355
|
+
When the user asks to create a new CLI/app in a subfolder (e.g. "make a todo app called list"):
|
|
49356
|
+
1. Create all files first using write-file with paths like \`list/package.json\`, \`list/src/index.tsx\`
|
|
49357
|
+
2. Then run \`cd list && bun install\` (or npm/pnpm) in one shell command
|
|
49358
|
+
3. Then run the project with \`cd list && bun run index.ts\` or whatever the entry point is
|
|
49359
|
+
4. NEVER run \`bun init\` — it is interactive and will hang. Create package.json manually with write-file instead
|
|
49360
|
+
5. TSX files need either tsconfig.json with \`"jsx": "react-jsx"\` or \`/** @jsxImportSource react */\` at the top
|
|
49361
|
+
|
|
49362
|
+
## FETCH → WRITE FLOW (follow this exactly when saving fetched data)
|
|
49363
|
+
|
|
49364
|
+
1. fetch the URL
|
|
49365
|
+
2. Analyze the result — count the rows, identify columns, check completeness
|
|
49366
|
+
3. Tell the user what you found: "Found X rows with columns: A, B, C. Writing now."
|
|
49367
|
+
4. emit write-file with correctly structured, complete content
|
|
49368
|
+
5. After write-file confirms success, emit read-file to verify
|
|
49369
|
+
6. Only after read-file confirms content is correct, tell the user it is done
|
|
49370
|
+
|
|
49371
|
+
## WHEN TO USE TOOLS
|
|
49372
|
+
|
|
49373
|
+
- User shares any URL → fetch it immediately
|
|
49374
|
+
- User asks to run anything → shell it immediately
|
|
49375
|
+
- User asks to open a link, open a URL, or visit a website → open-url it immediately, do NOT use fetch
|
|
49376
|
+
- User asks to delete a file → delete-file it immediately (requires approval)
|
|
49377
|
+
- User asks to delete a folder or directory → delete-folder it immediately (requires approval)
|
|
49378
|
+
- 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
|
|
49379
|
+
- User asks to read a file → read-file it immediately, NEVER use shell cat/type
|
|
49380
|
+
- 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
|
|
49381
|
+
- User asks to explore a folder or directory → read-folder it immediately
|
|
49382
|
+
- User asks to save/create/write a file → write-file it immediately, then read-file to verify
|
|
49383
|
+
- User asks to modify/edit/add to an existing file → read-file it FIRST, then emit changes
|
|
49384
|
+
- User shares a GitHub URL and wants to clone/explore/discuss it → use clone immediately, NEVER use shell git clone
|
|
49385
|
+
- 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.
|
|
49386
|
+
- You are unsure about an API, library, error, concept, or piece of code → search it immediately
|
|
49387
|
+
- User asks about something recent or that you might not know → search it immediately
|
|
49388
|
+
- You are about to say "I'm not sure" or "I don't know" → search instead of guessing
|
|
49389
|
+
|
|
49390
|
+
## shell IS ONLY FOR:
|
|
49391
|
+
- Running code: \`node script.js\`, \`bun run dev\`, \`python main.py\`
|
|
49392
|
+
- Installing packages: \`npm install\`, \`pip install\`
|
|
49393
|
+
- Building/testing: \`npm run build\`, \`bun test\`
|
|
49394
|
+
- Git operations other than clone: \`git status\`, \`git log\`, \`git diff\`
|
|
49395
|
+
- Anything that EXECUTES — not reads or lists
|
|
49396
|
+
|
|
49397
|
+
## CODEBASE
|
|
49398
|
+
|
|
49399
|
+
${fileList.length > 0 ? fileList : "(no files indexed)"}
|
|
49400
|
+
|
|
49401
|
+
${memorySummary}`;
|
|
49402
|
+
}
|
|
49403
|
+
// src/prompts/fewshot.ts
|
|
49404
|
+
var FEW_SHOT_MESSAGES = [
|
|
48933
49405
|
{
|
|
48934
49406
|
role: "user",
|
|
48935
|
-
content: "
|
|
49407
|
+
content: "delete src/old-component.tsx"
|
|
48936
49408
|
},
|
|
48937
49409
|
{
|
|
48938
49410
|
role: "assistant",
|
|
48939
|
-
content:
|
|
48940
|
-
{"pattern": "useInput", "glob": "src/**/*.tsx"}
|
|
48941
|
-
</grep>`
|
|
49411
|
+
content: "<delete-file>src/old-component.tsx</delete-file>"
|
|
48942
49412
|
},
|
|
48943
49413
|
{
|
|
48944
49414
|
role: "user",
|
|
48945
|
-
content: `Here is the output from
|
|
48946
|
-
|
|
48947
|
-
grep /useInput/ src/**/*.tsx — 2 match(es) in 1 file(s)
|
|
49415
|
+
content: `Here is the output from delete-file of src/old-component.tsx:
|
|
48948
49416
|
|
|
48949
|
-
|
|
48950
|
-
5: import { useInput } from "ink";
|
|
48951
|
-
210: useInput((input, key) => {
|
|
49417
|
+
Deleted: /repo/src/old-component.tsx
|
|
48952
49418
|
|
|
48953
49419
|
Please continue your response based on this output.`
|
|
48954
49420
|
},
|
|
48955
49421
|
{
|
|
48956
49422
|
role: "assistant",
|
|
48957
|
-
content: "
|
|
49423
|
+
content: "Done — `src/old-component.tsx` has been deleted."
|
|
48958
49424
|
},
|
|
48959
49425
|
{
|
|
48960
49426
|
role: "user",
|
|
48961
|
-
content: "
|
|
49427
|
+
content: "delete the legacy folder"
|
|
48962
49428
|
},
|
|
48963
49429
|
{
|
|
48964
49430
|
role: "assistant",
|
|
48965
|
-
content: "
|
|
49431
|
+
content: "<delete-folder>src/legacy</delete-folder>"
|
|
48966
49432
|
},
|
|
48967
49433
|
{
|
|
48968
49434
|
role: "user",
|
|
48969
|
-
content:
|
|
49435
|
+
content: `Here is the output from delete-folder of src/legacy:
|
|
49436
|
+
|
|
49437
|
+
Deleted folder: /repo/src/legacy
|
|
49438
|
+
|
|
49439
|
+
Please continue your response based on this output.`
|
|
48970
49440
|
},
|
|
48971
49441
|
{
|
|
48972
49442
|
role: "assistant",
|
|
48973
|
-
content: "
|
|
49443
|
+
content: "Done — the `src/legacy` folder and all its contents have been deleted."
|
|
48974
49444
|
},
|
|
48975
49445
|
{
|
|
48976
49446
|
role: "user",
|
|
48977
|
-
content: "
|
|
49447
|
+
content: "open https://github.com/microsoft/typescript"
|
|
48978
49448
|
},
|
|
48979
49449
|
{
|
|
48980
49450
|
role: "assistant",
|
|
48981
|
-
content: "<
|
|
49451
|
+
content: "<open-url>https://github.com/microsoft/typescript</open-url>"
|
|
48982
49452
|
},
|
|
48983
49453
|
{
|
|
48984
49454
|
role: "user",
|
|
48985
|
-
content: `Here is the output from
|
|
48986
|
-
|
|
48987
|
-
Folder: src (4 entries)
|
|
48988
|
-
|
|
48989
|
-
Files:
|
|
48990
|
-
index.ts
|
|
48991
|
-
App.tsx
|
|
49455
|
+
content: `Here is the output from open-url https://github.com/microsoft/typescript:
|
|
48992
49456
|
|
|
48993
|
-
|
|
48994
|
-
components/
|
|
48995
|
-
utils/
|
|
49457
|
+
Opened: https://github.com/microsoft/typescript
|
|
48996
49458
|
|
|
48997
49459
|
Please continue your response based on this output.`
|
|
48998
49460
|
},
|
|
48999
49461
|
{
|
|
49000
49462
|
role: "assistant",
|
|
49001
|
-
content: "
|
|
49463
|
+
content: "Opened the TypeScript GitHub page in your browser."
|
|
49002
49464
|
},
|
|
49003
49465
|
{
|
|
49004
49466
|
role: "user",
|
|
49005
|
-
content: "
|
|
49467
|
+
content: "generate a PDF report about the project and save it to docs/report.pdf"
|
|
49006
49468
|
},
|
|
49007
49469
|
{
|
|
49008
49470
|
role: "assistant",
|
|
49009
|
-
content:
|
|
49471
|
+
content: `<generate-pdf>
|
|
49472
|
+
{"path": "docs/report.pdf", "content": "# Project Report\\n\\n## Overview\\n\\nThis document summarizes the project.\\n\\n## Details\\n\\nMore content here."}
|
|
49473
|
+
</generate-pdf>`
|
|
49010
49474
|
},
|
|
49011
49475
|
{
|
|
49012
49476
|
role: "user",
|
|
49013
|
-
content: `Here is the output from
|
|
49014
|
-
|
|
49015
|
-
Folder: src (4 entries)
|
|
49016
|
-
|
|
49017
|
-
Files:
|
|
49018
|
-
index.ts
|
|
49019
|
-
App.tsx
|
|
49477
|
+
content: `Here is the output from generate-pdf to docs/report.pdf:
|
|
49020
49478
|
|
|
49021
|
-
|
|
49022
|
-
components/
|
|
49023
|
-
utils/
|
|
49479
|
+
PDF generated: /repo/docs/report.pdf
|
|
49024
49480
|
|
|
49025
49481
|
Please continue your response based on this output.`
|
|
49026
49482
|
},
|
|
49027
49483
|
{
|
|
49028
49484
|
role: "assistant",
|
|
49029
|
-
content: "
|
|
49485
|
+
content: "Done — the PDF report has been saved to `docs/report.pdf`."
|
|
49030
49486
|
},
|
|
49031
49487
|
{
|
|
49032
49488
|
role: "user",
|
|
49033
|
-
content:
|
|
49489
|
+
content: 'grep -R "ChatRunner" -n src'
|
|
49034
49490
|
},
|
|
49035
49491
|
{
|
|
49036
49492
|
role: "assistant",
|
|
49037
|
-
content:
|
|
49493
|
+
content: `<grep>
|
|
49494
|
+
{"pattern": "ChatRunner", "glob": "src/**/*"}
|
|
49495
|
+
</grep>`
|
|
49038
49496
|
},
|
|
49039
49497
|
{
|
|
49040
49498
|
role: "user",
|
|
49041
|
-
content: `Here is the output from
|
|
49499
|
+
content: `Here is the output from grep for "ChatRunner":
|
|
49042
49500
|
|
|
49043
|
-
|
|
49501
|
+
grep /ChatRunner/ src/**/* — 3 match(es) in 2 file(s)
|
|
49044
49502
|
|
|
49045
|
-
|
|
49046
|
-
|
|
49047
|
-
Footer.tsx
|
|
49048
|
-
Button.tsx
|
|
49503
|
+
src/index.tsx
|
|
49504
|
+
12: import { ChatRunner } from "./components/chat/ChatRunner";
|
|
49049
49505
|
|
|
49050
|
-
|
|
49051
|
-
|
|
49052
|
-
forms/
|
|
49506
|
+
src/components/chat/ChatRunner.tsx
|
|
49507
|
+
1: export const ChatRunner = ...
|
|
49053
49508
|
|
|
49054
49509
|
Please continue your response based on this output.`
|
|
49055
49510
|
},
|
|
49056
49511
|
{
|
|
49057
49512
|
role: "assistant",
|
|
49058
|
-
content: "
|
|
49513
|
+
content: "`ChatRunner` is defined in `src/components/chat/ChatRunner.tsx` and imported in `src/index.tsx`."
|
|
49059
49514
|
},
|
|
49060
49515
|
{
|
|
49061
49516
|
role: "user",
|
|
49062
|
-
content: "
|
|
49517
|
+
content: "find all usages of useInput in the codebase"
|
|
49063
49518
|
},
|
|
49064
49519
|
{
|
|
49065
49520
|
role: "assistant",
|
|
49066
|
-
content:
|
|
49521
|
+
content: `<grep>
|
|
49522
|
+
{"pattern": "useInput", "glob": "src/**/*.tsx"}
|
|
49523
|
+
</grep>`
|
|
49067
49524
|
},
|
|
49068
49525
|
{
|
|
49069
49526
|
role: "user",
|
|
49070
|
-
content: `Here is the output from
|
|
49071
|
-
|
|
49072
|
-
Folder: src/utils (3 entries)
|
|
49527
|
+
content: `Here is the output from grep for "useInput":
|
|
49073
49528
|
|
|
49074
|
-
|
|
49075
|
-
|
|
49076
|
-
|
|
49077
|
-
|
|
49529
|
+
grep /useInput/ src/**/*.tsx — 2 match(es) in 1 file(s)
|
|
49530
|
+
|
|
49531
|
+
src/components/chat/ChatRunner.tsx
|
|
49532
|
+
5: import { useInput } from "ink";
|
|
49533
|
+
210: useInput((input, key) => {
|
|
49078
49534
|
|
|
49079
49535
|
Please continue your response based on this output.`
|
|
49080
49536
|
},
|
|
49081
49537
|
{
|
|
49082
49538
|
role: "assistant",
|
|
49083
|
-
content: "
|
|
49539
|
+
content: "`useInput` is used in `src/components/chat/ChatRunner.tsx` — imported on line 5 and called on line 210."
|
|
49540
|
+
},
|
|
49541
|
+
{
|
|
49542
|
+
role: "user",
|
|
49543
|
+
content: "show me how to use the read-file tool"
|
|
49544
|
+
},
|
|
49545
|
+
{
|
|
49546
|
+
role: "assistant",
|
|
49547
|
+
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."
|
|
49548
|
+
},
|
|
49549
|
+
{
|
|
49550
|
+
role: "user",
|
|
49551
|
+
content: "show me an example of the shell tool"
|
|
49552
|
+
},
|
|
49553
|
+
{
|
|
49554
|
+
role: "assistant",
|
|
49555
|
+
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."
|
|
49084
49556
|
},
|
|
49085
49557
|
{
|
|
49086
49558
|
role: "user",
|
|
49087
|
-
content: "
|
|
49559
|
+
content: "read src folder"
|
|
49088
49560
|
},
|
|
49089
49561
|
{
|
|
49090
49562
|
role: "assistant",
|
|
@@ -49108,40 +49580,40 @@ Please continue your response based on this output.`
|
|
|
49108
49580
|
},
|
|
49109
49581
|
{
|
|
49110
49582
|
role: "assistant",
|
|
49111
|
-
content: "The `src`
|
|
49583
|
+
content: "The `src` folder contains `index.ts`, `App.tsx`, plus subfolders `components/` and `utils/`."
|
|
49112
49584
|
},
|
|
49113
49585
|
{
|
|
49114
49586
|
role: "user",
|
|
49115
|
-
content: "
|
|
49587
|
+
content: "what files are in src/components?"
|
|
49116
49588
|
},
|
|
49117
49589
|
{
|
|
49118
49590
|
role: "assistant",
|
|
49119
|
-
content: "<read-folder
|
|
49591
|
+
content: "<read-folder>src/components</read-folder>"
|
|
49120
49592
|
},
|
|
49121
49593
|
{
|
|
49122
49594
|
role: "user",
|
|
49123
|
-
content: `Here is the output from read-folder of
|
|
49595
|
+
content: `Here is the output from read-folder of src/components:
|
|
49124
49596
|
|
|
49125
|
-
Folder:
|
|
49597
|
+
Folder: src/components (5 entries)
|
|
49126
49598
|
|
|
49127
49599
|
Files:
|
|
49128
|
-
|
|
49129
|
-
|
|
49130
|
-
|
|
49600
|
+
Header.tsx
|
|
49601
|
+
Footer.tsx
|
|
49602
|
+
Button.tsx
|
|
49131
49603
|
|
|
49132
49604
|
Subfolders:
|
|
49133
|
-
|
|
49134
|
-
|
|
49605
|
+
ui/
|
|
49606
|
+
forms/
|
|
49135
49607
|
|
|
49136
49608
|
Please continue your response based on this output.`
|
|
49137
49609
|
},
|
|
49138
49610
|
{
|
|
49139
49611
|
role: "assistant",
|
|
49140
|
-
content: "The
|
|
49612
|
+
content: "The `src/components` folder has 3 files — `Header.tsx`, `Footer.tsx`, `Button.tsx` — plus two subfolders: `ui/` and `forms/`."
|
|
49141
49613
|
},
|
|
49142
49614
|
{
|
|
49143
49615
|
role: "user",
|
|
49144
|
-
content: "
|
|
49616
|
+
content: "show me the project structure"
|
|
49145
49617
|
},
|
|
49146
49618
|
{
|
|
49147
49619
|
role: "assistant",
|
|
@@ -49151,21 +49623,22 @@ Please continue your response based on this output.`
|
|
|
49151
49623
|
role: "user",
|
|
49152
49624
|
content: `Here is the output from read-folder of .:
|
|
49153
49625
|
|
|
49154
|
-
Folder: . (
|
|
49626
|
+
Folder: . (5 entries)
|
|
49155
49627
|
|
|
49156
49628
|
Files:
|
|
49157
49629
|
package.json
|
|
49630
|
+
tsconfig.json
|
|
49158
49631
|
README.md
|
|
49159
49632
|
|
|
49160
49633
|
Subfolders:
|
|
49161
49634
|
src/
|
|
49162
|
-
|
|
49635
|
+
node_modules/
|
|
49163
49636
|
|
|
49164
49637
|
Please continue your response based on this output.`
|
|
49165
49638
|
},
|
|
49166
49639
|
{
|
|
49167
49640
|
role: "assistant",
|
|
49168
|
-
content: "The
|
|
49641
|
+
content: "The project root has 3 files (`package.json`, `tsconfig.json`, `README.md`) and the `src/` folder for source code."
|
|
49169
49642
|
},
|
|
49170
49643
|
{
|
|
49171
49644
|
role: "user",
|
|
@@ -49314,13 +49787,62 @@ Please continue your response based on this output.`
|
|
|
49314
49787
|
},
|
|
49315
49788
|
{
|
|
49316
49789
|
role: "user",
|
|
49317
|
-
content: "
|
|
49790
|
+
content: "scaffold a new project called myapp with package.json and src/index.ts"
|
|
49791
|
+
},
|
|
49792
|
+
{
|
|
49793
|
+
role: "assistant",
|
|
49794
|
+
content: `<write-file>
|
|
49795
|
+
{"path": "myapp/package.json", "content": "{"name":"myapp","version":"1.0.0"}"}
|
|
49796
|
+
</write-file>`
|
|
49797
|
+
},
|
|
49798
|
+
{
|
|
49799
|
+
role: "user",
|
|
49800
|
+
content: `Here is the output from write-file to myapp/package.json:
|
|
49801
|
+
|
|
49802
|
+
Written: /repo/myapp/package.json (1 lines, 38 bytes)
|
|
49803
|
+
|
|
49804
|
+
Please continue your response based on this output.`
|
|
49805
|
+
},
|
|
49806
|
+
{
|
|
49807
|
+
role: "assistant",
|
|
49808
|
+
content: `<write-file>
|
|
49809
|
+
{"path": "myapp/src/index.ts", "content": "console.log('hello')"}
|
|
49810
|
+
</write-file>`
|
|
49811
|
+
},
|
|
49812
|
+
{
|
|
49813
|
+
role: "user",
|
|
49814
|
+
content: `Here is the output from write-file to myapp/src/index.ts:
|
|
49815
|
+
|
|
49816
|
+
Written: /repo/myapp/src/index.ts (1 lines, 20 bytes)
|
|
49817
|
+
|
|
49818
|
+
Please continue your response based on this output.`
|
|
49819
|
+
},
|
|
49820
|
+
{
|
|
49821
|
+
role: "assistant",
|
|
49822
|
+
content: "Done — scaffolded `myapp/package.json` and `myapp/src/index.ts`. Run `cd myapp && npm install` to get started."
|
|
49823
|
+
},
|
|
49824
|
+
{
|
|
49825
|
+
role: "user",
|
|
49826
|
+
content: "remember that we always use bun in this project"
|
|
49827
|
+
},
|
|
49828
|
+
{
|
|
49829
|
+
role: "assistant",
|
|
49830
|
+
content: `<memory-add>Project uses bun as the package manager and runtime, not npm or node</memory-add>
|
|
49831
|
+
|
|
49832
|
+
Got it — I'll always use bun for this project.`
|
|
49833
|
+
},
|
|
49834
|
+
{
|
|
49835
|
+
role: "user",
|
|
49836
|
+
content: "forget the bun preference"
|
|
49318
49837
|
},
|
|
49319
49838
|
{
|
|
49320
49839
|
role: "assistant",
|
|
49321
|
-
content:
|
|
49840
|
+
content: `<memory-delete>MEMORY_ID</memory-delete>
|
|
49841
|
+
|
|
49842
|
+
Done — removed that memory.`
|
|
49322
49843
|
}
|
|
49323
49844
|
];
|
|
49845
|
+
// src/utils/chat.ts
|
|
49324
49846
|
function parseResponse(text) {
|
|
49325
49847
|
const scanText = text.replace(/```[\s\S]*?```/g, (m) => " ".repeat(m.length));
|
|
49326
49848
|
const candidates = [];
|
|
@@ -49357,728 +49879,265 @@ function parseResponse(text) {
|
|
|
49357
49879
|
const originalMatch = originalRe.exec(text.slice(m.index));
|
|
49358
49880
|
if (originalMatch) {
|
|
49359
49881
|
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}:
|
|
49466
|
-
|
|
49467
|
-
${m.result}
|
|
49468
|
-
|
|
49469
|
-
Please continue your response based on this output.`
|
|
49470
|
-
};
|
|
49471
|
-
}
|
|
49472
|
-
return { role: m.role, content: m.content };
|
|
49473
|
-
});
|
|
49474
|
-
}
|
|
49475
|
-
async function callChat(provider, systemPrompt, messages, abortSignal) {
|
|
49476
|
-
const apiMessages = [...FEW_SHOT_MESSAGES, ...buildApiMessages(messages)];
|
|
49477
|
-
let url;
|
|
49478
|
-
let headers;
|
|
49479
|
-
let body;
|
|
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(`
|
|
49792
|
-
|
|
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)
|
|
49807
|
-
|
|
49808
|
-
${content.slice(0, 8000)}${content.length > 8000 ? `
|
|
49809
|
-
|
|
49810
|
-
… (truncated)` : ""}`;
|
|
49811
|
-
} catch (err) {
|
|
49812
|
-
return `Error reading file: ${err instanceof Error ? err.message : String(err)}`;
|
|
49813
|
-
}
|
|
49814
|
-
}
|
|
49815
|
-
}
|
|
49816
|
-
return `File not found: ${filePath}. If reading from a cloned repo, use the full absolute path e.g. C:\\Users\\...\\repo\\file.ts`;
|
|
49817
|
-
}
|
|
49818
|
-
function readFolder(folderPath, repoPath) {
|
|
49819
|
-
const sanitized = folderPath.replace(/^(ls|dir|find|tree|cat|read|ls -la?|ls -al?)\s+/i, "").trim();
|
|
49820
|
-
const candidates = path14.isAbsolute(sanitized) ? [sanitized] : [sanitized, path14.join(repoPath, sanitized)];
|
|
49821
|
-
for (const candidate of candidates) {
|
|
49822
|
-
if (!existsSync10(candidate))
|
|
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}`));
|
|
49864
|
-
}
|
|
49865
|
-
if (total === 0) {
|
|
49866
|
-
lines.push("(empty folder)");
|
|
49867
|
-
}
|
|
49868
|
-
return lines.join(`
|
|
49869
|
-
`);
|
|
49870
|
-
}
|
|
49871
|
-
return `Folder not found: ${sanitized}`;
|
|
49872
|
-
}
|
|
49873
|
-
function grepFiles(pattern, glob, repoPath) {
|
|
49874
|
-
let regex2;
|
|
49875
|
-
try {
|
|
49876
|
-
regex2 = new RegExp(pattern, "i");
|
|
49877
|
-
} catch {
|
|
49878
|
-
regex2 = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i");
|
|
49879
|
-
}
|
|
49880
|
-
const globToFilter = (g) => {
|
|
49881
|
-
const cleaned = g.replace(/^\*\*\//, "");
|
|
49882
|
-
const parts = cleaned.split("/");
|
|
49883
|
-
const ext = parts[parts.length - 1];
|
|
49884
|
-
const prefix = parts.slice(0, -1).join("/");
|
|
49885
|
-
return (rel) => {
|
|
49886
|
-
if (ext?.startsWith("*.")) {
|
|
49887
|
-
const extSuffix = ext.slice(1);
|
|
49888
|
-
if (!rel.endsWith(extSuffix))
|
|
49889
|
-
return false;
|
|
49890
|
-
} else if (ext && !ext.includes("*")) {
|
|
49891
|
-
if (!rel.endsWith(ext))
|
|
49892
|
-
return false;
|
|
49893
|
-
}
|
|
49894
|
-
if (prefix && !prefix.includes("*")) {
|
|
49895
|
-
if (!rel.startsWith(prefix))
|
|
49896
|
-
return false;
|
|
49882
|
+
text.slice(m.index, m.index + originalMatch[0].length),
|
|
49883
|
+
originalMatch[1]
|
|
49884
|
+
], { index: m.index, input: text, groups: undefined });
|
|
49885
|
+
candidates.push({ index: m.index, kind: kind2, match: fakeMatch });
|
|
49897
49886
|
}
|
|
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}`;
|
|
49887
|
+
}
|
|
49906
49888
|
}
|
|
49907
|
-
|
|
49908
|
-
|
|
49909
|
-
|
|
49910
|
-
|
|
49911
|
-
|
|
49889
|
+
if (candidates.length === 0)
|
|
49890
|
+
return { kind: "text", content: text.trim() };
|
|
49891
|
+
candidates.sort((a, b) => a.index - b.index);
|
|
49892
|
+
const { kind, match } = candidates[0];
|
|
49893
|
+
const before2 = text.slice(0, match.index).replace(/<(fetch|shell|read-file|read-folder|write-file|search|clone|changes)[^>]*>[\s\S]*?<\/\1>/g, "").trim();
|
|
49894
|
+
const body = (match[1] ?? "").trim();
|
|
49895
|
+
if (kind === "changes") {
|
|
49912
49896
|
try {
|
|
49913
|
-
|
|
49914
|
-
|
|
49915
|
-
|
|
49916
|
-
}
|
|
49917
|
-
const lines = content.split(`
|
|
49897
|
+
const parsed = JSON.parse(body);
|
|
49898
|
+
const display = [before2, parsed.summary].filter(Boolean).join(`
|
|
49899
|
+
|
|
49918
49900
|
`);
|
|
49919
|
-
|
|
49920
|
-
|
|
49921
|
-
if (regex2.test(line)) {
|
|
49922
|
-
fileMatches.push(` ${i + 1}: ${line.trimEnd()}`);
|
|
49923
|
-
totalMatches++;
|
|
49924
|
-
}
|
|
49925
|
-
});
|
|
49926
|
-
if (fileMatches.length > 0) {
|
|
49927
|
-
results.push(`${relPath}
|
|
49928
|
-
${fileMatches.join(`
|
|
49929
|
-
`)}`);
|
|
49930
|
-
}
|
|
49931
|
-
if (totalMatches >= 200) {
|
|
49932
|
-
results.push("(truncated — too many matches)");
|
|
49933
|
-
break;
|
|
49934
|
-
}
|
|
49901
|
+
return { kind: "changes", content: display, patches: parsed.patches };
|
|
49902
|
+
} catch {}
|
|
49935
49903
|
}
|
|
49936
|
-
if (
|
|
49937
|
-
return
|
|
49904
|
+
if (kind === "shell")
|
|
49905
|
+
return { kind: "shell", content: before2, command: body };
|
|
49906
|
+
if (kind === "fetch")
|
|
49907
|
+
return {
|
|
49908
|
+
kind: "fetch",
|
|
49909
|
+
content: before2,
|
|
49910
|
+
url: body.replace(/^<|>$/g, "").trim()
|
|
49911
|
+
};
|
|
49912
|
+
if (kind === "read-file")
|
|
49913
|
+
return { kind: "read-file", content: before2, filePath: body };
|
|
49914
|
+
if (kind === "read-folder")
|
|
49915
|
+
return { kind: "read-folder", content: before2, folderPath: body };
|
|
49916
|
+
if (kind === "delete-file")
|
|
49917
|
+
return { kind: "delete-file", content: before2, filePath: body };
|
|
49918
|
+
if (kind === "delete-folder")
|
|
49919
|
+
return { kind: "delete-folder", content: before2, folderPath: body };
|
|
49920
|
+
if (kind === "open-url")
|
|
49921
|
+
return {
|
|
49922
|
+
kind: "open-url",
|
|
49923
|
+
content: before2,
|
|
49924
|
+
url: body.replace(/^<|>$/g, "").trim()
|
|
49925
|
+
};
|
|
49926
|
+
if (kind === "search")
|
|
49927
|
+
return { kind: "search", content: before2, query: body };
|
|
49928
|
+
if (kind === "clone")
|
|
49929
|
+
return {
|
|
49930
|
+
kind: "clone",
|
|
49931
|
+
content: before2,
|
|
49932
|
+
repoUrl: body.replace(/^<|>$/g, "").trim()
|
|
49933
|
+
};
|
|
49934
|
+
if (kind === "generate-pdf") {
|
|
49935
|
+
try {
|
|
49936
|
+
const parsed = JSON.parse(body);
|
|
49937
|
+
return {
|
|
49938
|
+
kind: "generate-pdf",
|
|
49939
|
+
content: before2,
|
|
49940
|
+
filePath: parsed.path ?? parsed.filePath ?? "output.pdf",
|
|
49941
|
+
pdfContent: parsed.content ?? ""
|
|
49942
|
+
};
|
|
49943
|
+
} catch {
|
|
49944
|
+
return { kind: "text", content: text };
|
|
49945
|
+
}
|
|
49938
49946
|
}
|
|
49939
|
-
|
|
49940
|
-
|
|
49941
|
-
|
|
49942
|
-
|
|
49943
|
-
|
|
49944
|
-
|
|
49945
|
-
|
|
49946
|
-
|
|
49947
|
-
|
|
49948
|
-
|
|
49949
|
-
|
|
49950
|
-
|
|
49951
|
-
writeFileSync6(fullPath, content, "utf-8");
|
|
49952
|
-
const lines = content.split(`
|
|
49953
|
-
`).length;
|
|
49954
|
-
return `Written: ${fullPath} (${lines} lines, ${content.length} bytes)`;
|
|
49955
|
-
} catch (err) {
|
|
49956
|
-
return `Error writing file: ${err instanceof Error ? err.message : String(err)}`;
|
|
49947
|
+
if (kind === "grep") {
|
|
49948
|
+
try {
|
|
49949
|
+
const parsed = JSON.parse(body);
|
|
49950
|
+
return {
|
|
49951
|
+
kind: "grep",
|
|
49952
|
+
content: before2,
|
|
49953
|
+
pattern: parsed.pattern,
|
|
49954
|
+
glob: parsed.glob ?? "**/*"
|
|
49955
|
+
};
|
|
49956
|
+
} catch {
|
|
49957
|
+
return { kind: "grep", content: before2, pattern: body, glob: "**/*" };
|
|
49958
|
+
}
|
|
49957
49959
|
}
|
|
49958
|
-
|
|
49959
|
-
|
|
49960
|
-
|
|
49961
|
-
|
|
49962
|
-
|
|
49963
|
-
|
|
49964
|
-
|
|
49965
|
-
|
|
49966
|
-
|
|
49967
|
-
|
|
49968
|
-
return `Error deleting file: ${err instanceof Error ? err.message : String(err)}`;
|
|
49960
|
+
if (kind === "write-file") {
|
|
49961
|
+
try {
|
|
49962
|
+
const parsed = JSON.parse(body);
|
|
49963
|
+
return {
|
|
49964
|
+
kind: "write-file",
|
|
49965
|
+
content: before2,
|
|
49966
|
+
filePath: parsed.path,
|
|
49967
|
+
fileContent: parsed.content
|
|
49968
|
+
};
|
|
49969
|
+
} catch {}
|
|
49969
49970
|
}
|
|
49971
|
+
return { kind: "text", content: text.trim() };
|
|
49970
49972
|
}
|
|
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
|
-
}
|
|
49973
|
+
function extractGithubUrl(text) {
|
|
49974
|
+
const match = text.match(/https?:\/\/github\.com\/[\w.-]+\/[\w.-]+/);
|
|
49975
|
+
return match ? match[0] : null;
|
|
49982
49976
|
}
|
|
49983
|
-
function
|
|
49984
|
-
|
|
49985
|
-
|
|
49986
|
-
const platform2 = process.platform;
|
|
49987
|
-
if (platform2 === "win32") {
|
|
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)}`;
|
|
49997
|
-
}
|
|
49977
|
+
function toCloneUrl(url) {
|
|
49978
|
+
const clean = url.replace(/\/+$/, "");
|
|
49979
|
+
return clean.endsWith(".git") ? clean : `${clean}.git`;
|
|
49998
49980
|
}
|
|
49999
|
-
function
|
|
50000
|
-
|
|
50001
|
-
|
|
50002
|
-
|
|
50003
|
-
|
|
50004
|
-
|
|
50005
|
-
|
|
50006
|
-
|
|
50007
|
-
|
|
50008
|
-
|
|
50009
|
-
|
|
50010
|
-
|
|
50011
|
-
|
|
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))
|
|
49981
|
+
function buildApiMessages(messages) {
|
|
49982
|
+
return messages.map((m) => {
|
|
49983
|
+
if (m.type === "tool") {
|
|
49984
|
+
if (!m.approved) {
|
|
49985
|
+
return {
|
|
49986
|
+
role: "user",
|
|
49987
|
+
content: "The tool call was denied by the user. Please respond without using that tool."
|
|
49988
|
+
};
|
|
49989
|
+
}
|
|
49990
|
+
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}`;
|
|
49991
|
+
return {
|
|
49992
|
+
role: "user",
|
|
49993
|
+
content: `Here is the output from the ${label}:
|
|
50038
49994
|
|
|
50039
|
-
|
|
49995
|
+
${m.result}
|
|
50040
49996
|
|
|
50041
|
-
|
|
50042
|
-
|
|
50043
|
-
|
|
50044
|
-
|
|
50045
|
-
|
|
50046
|
-
|
|
50047
|
-
|
|
50048
|
-
|
|
50049
|
-
|
|
50050
|
-
|
|
50051
|
-
|
|
50052
|
-
|
|
50053
|
-
|
|
50054
|
-
|
|
50055
|
-
|
|
50056
|
-
|
|
50057
|
-
|
|
50058
|
-
|
|
50059
|
-
|
|
50060
|
-
|
|
50061
|
-
|
|
50062
|
-
|
|
50063
|
-
|
|
50064
|
-
|
|
49997
|
+
Please continue your response based on this output.`
|
|
49998
|
+
};
|
|
49999
|
+
}
|
|
50000
|
+
return { role: m.role, content: m.content };
|
|
50001
|
+
});
|
|
50002
|
+
}
|
|
50003
|
+
async function callChat(provider, systemPrompt, messages, abortSignal) {
|
|
50004
|
+
const apiMessages = [...FEW_SHOT_MESSAGES, ...buildApiMessages(messages)];
|
|
50005
|
+
let url;
|
|
50006
|
+
let headers;
|
|
50007
|
+
let body;
|
|
50008
|
+
if (provider.type === "anthropic") {
|
|
50009
|
+
url = "https://api.anthropic.com/v1/messages";
|
|
50010
|
+
headers = {
|
|
50011
|
+
"Content-Type": "application/json",
|
|
50012
|
+
"x-api-key": provider.apiKey ?? "",
|
|
50013
|
+
"anthropic-version": "2023-06-01"
|
|
50014
|
+
};
|
|
50015
|
+
body = {
|
|
50016
|
+
model: provider.model,
|
|
50017
|
+
max_tokens: 4096,
|
|
50018
|
+
system: systemPrompt,
|
|
50019
|
+
messages: apiMessages
|
|
50020
|
+
};
|
|
50021
|
+
} else {
|
|
50022
|
+
const base2 = provider.baseUrl ?? "https://api.openai.com/v1";
|
|
50023
|
+
url = `${base2}/chat/completions`;
|
|
50024
|
+
headers = {
|
|
50025
|
+
"Content-Type": "application/json",
|
|
50026
|
+
Authorization: `Bearer ${provider.apiKey}`
|
|
50027
|
+
};
|
|
50028
|
+
body = {
|
|
50029
|
+
model: provider.model,
|
|
50030
|
+
max_tokens: 4096,
|
|
50031
|
+
messages: [{ role: "system", content: systemPrompt }, ...apiMessages]
|
|
50032
|
+
};
|
|
50033
|
+
}
|
|
50034
|
+
const controller = new AbortController;
|
|
50035
|
+
const timer = setTimeout(() => controller.abort(), 60000);
|
|
50036
|
+
abortSignal?.addEventListener("abort", () => controller.abort());
|
|
50037
|
+
const res = await fetch(url, {
|
|
50038
|
+
method: "POST",
|
|
50039
|
+
headers,
|
|
50040
|
+
body: JSON.stringify(body),
|
|
50041
|
+
signal: controller.signal
|
|
50042
|
+
});
|
|
50043
|
+
clearTimeout(timer);
|
|
50044
|
+
if (!res.ok)
|
|
50045
|
+
throw new Error(`API error ${res.status}: ${await res.text()}`);
|
|
50046
|
+
const data = await res.json();
|
|
50047
|
+
if (provider.type === "anthropic") {
|
|
50048
|
+
const content = data.content;
|
|
50049
|
+
return content.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
50050
|
+
} else {
|
|
50051
|
+
const choices = data.choices;
|
|
50052
|
+
return choices[0]?.message.content ?? "";
|
|
50053
|
+
}
|
|
50054
|
+
}
|
|
50065
50055
|
|
|
50066
|
-
|
|
50067
|
-
|
|
50068
|
-
|
|
50069
|
-
|
|
50070
|
-
|
|
50071
|
-
|
|
50072
|
-
|
|
50073
|
-
|
|
50056
|
+
// src/utils/chatHistory.ts
|
|
50057
|
+
import {
|
|
50058
|
+
existsSync as existsSync12,
|
|
50059
|
+
mkdirSync as mkdirSync6,
|
|
50060
|
+
readFileSync as readFileSync11,
|
|
50061
|
+
readdirSync as readdirSync4,
|
|
50062
|
+
writeFileSync as writeFileSync8,
|
|
50063
|
+
unlinkSync
|
|
50064
|
+
} from "fs";
|
|
50065
|
+
import path16 from "path";
|
|
50066
|
+
import os7 from "os";
|
|
50067
|
+
var LENS_DIR = path16.join(os7.homedir(), ".lens");
|
|
50068
|
+
var CHATS_DIR = path16.join(LENS_DIR, "chats");
|
|
50069
|
+
function ensureChatsDir() {
|
|
50070
|
+
if (!existsSync12(CHATS_DIR))
|
|
50071
|
+
mkdirSync6(CHATS_DIR, { recursive: true });
|
|
50072
|
+
}
|
|
50073
|
+
function chatFilePath(name) {
|
|
50074
|
+
const safe = name.replace(/[^a-z0-9_-]/gi, "-").toLowerCase();
|
|
50075
|
+
return path16.join(CHATS_DIR, `${safe}.json`);
|
|
50076
|
+
}
|
|
50077
|
+
function saveChat(name, repoPath, messages) {
|
|
50078
|
+
ensureChatsDir();
|
|
50079
|
+
const data = {
|
|
50080
|
+
name,
|
|
50081
|
+
repoPath,
|
|
50082
|
+
messages,
|
|
50083
|
+
savedAt: new Date().toISOString(),
|
|
50084
|
+
userMessageCount: messages.filter((m) => m.role === "user").length
|
|
50085
|
+
};
|
|
50086
|
+
writeFileSync8(chatFilePath(name), JSON.stringify(data, null, 2), "utf-8");
|
|
50087
|
+
}
|
|
50088
|
+
function loadChat(name) {
|
|
50089
|
+
const filePath = chatFilePath(name);
|
|
50090
|
+
if (!existsSync12(filePath))
|
|
50091
|
+
return null;
|
|
50092
|
+
try {
|
|
50093
|
+
return JSON.parse(readFileSync11(filePath, "utf-8"));
|
|
50094
|
+
} catch {
|
|
50095
|
+
return null;
|
|
50096
|
+
}
|
|
50097
|
+
}
|
|
50098
|
+
function listChats(repoPath) {
|
|
50099
|
+
ensureChatsDir();
|
|
50100
|
+
const files = readdirSync4(CHATS_DIR).filter((f) => f.endsWith(".json"));
|
|
50101
|
+
const chats = [];
|
|
50102
|
+
for (const file of files) {
|
|
50074
50103
|
try {
|
|
50075
|
-
|
|
50104
|
+
const data = JSON.parse(readFileSync11(path16.join(CHATS_DIR, file), "utf-8"));
|
|
50105
|
+
if (!repoPath || data.repoPath === repoPath)
|
|
50106
|
+
chats.push(data);
|
|
50076
50107
|
} catch {}
|
|
50077
|
-
|
|
50078
|
-
|
|
50079
|
-
|
|
50108
|
+
}
|
|
50109
|
+
return chats.sort((a, b) => new Date(b.savedAt).getTime() - new Date(a.savedAt).getTime());
|
|
50110
|
+
}
|
|
50111
|
+
function deleteChat(name) {
|
|
50112
|
+
const filePath = chatFilePath(name);
|
|
50113
|
+
if (!existsSync12(filePath))
|
|
50114
|
+
return false;
|
|
50115
|
+
try {
|
|
50116
|
+
unlinkSync(filePath);
|
|
50117
|
+
return true;
|
|
50118
|
+
} catch {
|
|
50119
|
+
return false;
|
|
50080
50120
|
}
|
|
50081
50121
|
}
|
|
50122
|
+
function getChatNameSuggestions(messages) {
|
|
50123
|
+
const userMsgs = messages.filter((m) => m.role === "user").map((m) => m.content.toLowerCase().trim());
|
|
50124
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
50125
|
+
if (userMsgs.length === 0) {
|
|
50126
|
+
return [`chat-${date}`, `session-${date}`, `new-chat`];
|
|
50127
|
+
}
|
|
50128
|
+
const suggestions = [];
|
|
50129
|
+
const toSlug = (s) => s.replace(/[^a-z0-9\s]/g, "").split(/\s+/).filter(Boolean).slice(0, 4).join("-");
|
|
50130
|
+
const firstSlug = toSlug(userMsgs[0]);
|
|
50131
|
+
if (firstSlug)
|
|
50132
|
+
suggestions.push(firstSlug);
|
|
50133
|
+
if (userMsgs.length > 1) {
|
|
50134
|
+
const lastSlug = toSlug(userMsgs[userMsgs.length - 1]);
|
|
50135
|
+
if (lastSlug && lastSlug !== firstSlug)
|
|
50136
|
+
suggestions.push(lastSlug);
|
|
50137
|
+
}
|
|
50138
|
+
suggestions.push(`session-${date}`);
|
|
50139
|
+
return suggestions.slice(0, 3);
|
|
50140
|
+
}
|
|
50082
50141
|
|
|
50083
50142
|
// src/components/chat/ChatMessage.tsx
|
|
50084
50143
|
var jsx_dev_runtime19 = __toESM(require_jsx_dev_runtime(), 1);
|
|
@@ -50182,6 +50241,19 @@ function MessageBody({ content }) {
|
|
|
50182
50241
|
})
|
|
50183
50242
|
}, undefined, false, undefined, this);
|
|
50184
50243
|
}
|
|
50244
|
+
function summarizeToolContent(toolName, content) {
|
|
50245
|
+
if (toolName === "write-file" || toolName === "read-file") {
|
|
50246
|
+
const pathMatch = content.match(/"path"\s*:\s*"([^"]+)"/);
|
|
50247
|
+
if (pathMatch)
|
|
50248
|
+
return pathMatch[1];
|
|
50249
|
+
}
|
|
50250
|
+
if (content.includes('"summary"')) {
|
|
50251
|
+
const summaryMatch = content.match(/"summary"\s*:\s*"([^"]+)"/);
|
|
50252
|
+
if (summaryMatch)
|
|
50253
|
+
return summaryMatch[1];
|
|
50254
|
+
}
|
|
50255
|
+
return content.length > 120 ? content.slice(0, 120) + "…" : content;
|
|
50256
|
+
}
|
|
50185
50257
|
function StaticMessage({ msg }) {
|
|
50186
50258
|
if (msg.role === "user") {
|
|
50187
50259
|
return /* @__PURE__ */ jsx_dev_runtime19.jsxDEV(Box_default, {
|
|
@@ -50212,7 +50284,7 @@ function StaticMessage({ msg }) {
|
|
|
50212
50284
|
search: "?"
|
|
50213
50285
|
};
|
|
50214
50286
|
const icon = icons[msg.toolName] ?? "·";
|
|
50215
|
-
const label = msg.toolName === "shell" ? msg.content : msg.toolName === "search" ? `"${msg.content}"` : msg.content;
|
|
50287
|
+
const label = msg.toolName === "shell" ? msg.content : msg.toolName === "search" ? `"${msg.content}"` : summarizeToolContent(msg.toolName, msg.content);
|
|
50216
50288
|
return /* @__PURE__ */ jsx_dev_runtime19.jsxDEV(Box_default, {
|
|
50217
50289
|
flexDirection: "column",
|
|
50218
50290
|
marginBottom: 1,
|
|
@@ -50497,7 +50569,8 @@ function PermissionPrompt({
|
|
|
50497
50569
|
function InputBox({
|
|
50498
50570
|
value,
|
|
50499
50571
|
onChange,
|
|
50500
|
-
onSubmit
|
|
50572
|
+
onSubmit,
|
|
50573
|
+
inputKey
|
|
50501
50574
|
}) {
|
|
50502
50575
|
return /* @__PURE__ */ jsx_dev_runtime20.jsxDEV(Box_default, {
|
|
50503
50576
|
marginTop: 1,
|
|
@@ -50518,7 +50591,7 @@ function InputBox({
|
|
|
50518
50591
|
value,
|
|
50519
50592
|
onChange,
|
|
50520
50593
|
onSubmit
|
|
50521
|
-
},
|
|
50594
|
+
}, inputKey, false, undefined, this)
|
|
50522
50595
|
]
|
|
50523
50596
|
}, undefined, true, undefined, this)
|
|
50524
50597
|
}, undefined, false, undefined, this);
|
|
@@ -52283,53 +52356,96 @@ function TimelineRunner({
|
|
|
52283
52356
|
}, undefined, true, undefined, this);
|
|
52284
52357
|
}
|
|
52285
52358
|
|
|
52286
|
-
// src/utils/
|
|
52287
|
-
import { existsSync as
|
|
52288
|
-
import
|
|
52289
|
-
import
|
|
52290
|
-
var
|
|
52291
|
-
var
|
|
52292
|
-
function
|
|
52293
|
-
if (!
|
|
52294
|
-
return { entries: [] };
|
|
52359
|
+
// src/utils/memory.ts
|
|
52360
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "fs";
|
|
52361
|
+
import path17 from "path";
|
|
52362
|
+
import os8 from "os";
|
|
52363
|
+
var LENS_DIR2 = path17.join(os8.homedir(), ".lens");
|
|
52364
|
+
var MEMORY_PATH = path17.join(LENS_DIR2, "memory.json");
|
|
52365
|
+
function loadMemoryFile() {
|
|
52366
|
+
if (!existsSync13(MEMORY_PATH))
|
|
52367
|
+
return { entries: [], memories: [] };
|
|
52295
52368
|
try {
|
|
52296
|
-
|
|
52369
|
+
const data = JSON.parse(readFileSync12(MEMORY_PATH, "utf-8"));
|
|
52370
|
+
return {
|
|
52371
|
+
entries: data.entries ?? [],
|
|
52372
|
+
memories: data.memories ?? []
|
|
52373
|
+
};
|
|
52297
52374
|
} catch {
|
|
52298
|
-
return { entries: [] };
|
|
52375
|
+
return { entries: [], memories: [] };
|
|
52299
52376
|
}
|
|
52300
52377
|
}
|
|
52301
|
-
function
|
|
52302
|
-
if (!
|
|
52303
|
-
|
|
52304
|
-
|
|
52378
|
+
function saveMemoryFile(m) {
|
|
52379
|
+
if (!existsSync13(LENS_DIR2))
|
|
52380
|
+
mkdirSync7(LENS_DIR2, { recursive: true });
|
|
52381
|
+
writeFileSync9(MEMORY_PATH, JSON.stringify(m, null, 2), "utf-8");
|
|
52305
52382
|
}
|
|
52306
|
-
function
|
|
52307
|
-
const
|
|
52308
|
-
|
|
52309
|
-
if (
|
|
52310
|
-
|
|
52311
|
-
|
|
52383
|
+
function appendMemory(entry) {
|
|
52384
|
+
const m = loadMemoryFile();
|
|
52385
|
+
m.entries.push({ ...entry, timestamp: new Date().toISOString() });
|
|
52386
|
+
if (m.entries.length > 500)
|
|
52387
|
+
m.entries = m.entries.slice(-500);
|
|
52388
|
+
saveMemoryFile(m);
|
|
52312
52389
|
}
|
|
52313
|
-
function
|
|
52314
|
-
const
|
|
52315
|
-
const relevant =
|
|
52316
|
-
|
|
52317
|
-
|
|
52318
|
-
|
|
52319
|
-
|
|
52320
|
-
|
|
52321
|
-
|
|
52322
|
-
|
|
52390
|
+
function buildMemorySummary(repoPath) {
|
|
52391
|
+
const m = loadMemoryFile();
|
|
52392
|
+
const relevant = m.entries.filter((e) => e.repoPath === repoPath).slice(-50);
|
|
52393
|
+
const memories = m.memories.filter((mem) => mem.repoPath === repoPath);
|
|
52394
|
+
const parts = [];
|
|
52395
|
+
if (memories.length > 0) {
|
|
52396
|
+
parts.push(`## MEMORIES ABOUT THIS REPO
|
|
52397
|
+
|
|
52398
|
+
${memories.map((mem) => `- [${mem.id}] ${mem.content}`).join(`
|
|
52399
|
+
`)}`);
|
|
52400
|
+
}
|
|
52401
|
+
if (relevant.length > 0) {
|
|
52402
|
+
const lines = relevant.map((e) => {
|
|
52403
|
+
const ts = new Date(e.timestamp).toLocaleString();
|
|
52404
|
+
return `[${ts}] ${e.kind}: ${e.detail} — ${e.summary}`;
|
|
52405
|
+
});
|
|
52406
|
+
parts.push(`## WHAT YOU HAVE ALREADY DONE IN THIS REPO
|
|
52323
52407
|
|
|
52324
52408
|
The following actions have already been completed. Do NOT repeat them unless the user explicitly asks you to redo something:
|
|
52325
52409
|
|
|
52326
52410
|
${lines.join(`
|
|
52327
|
-
`)}
|
|
52411
|
+
`)}`);
|
|
52412
|
+
}
|
|
52413
|
+
return parts.join(`
|
|
52414
|
+
|
|
52415
|
+
`);
|
|
52416
|
+
}
|
|
52417
|
+
function clearRepoMemory(repoPath) {
|
|
52418
|
+
const m = loadMemoryFile();
|
|
52419
|
+
m.entries = m.entries.filter((e) => e.repoPath !== repoPath);
|
|
52420
|
+
m.memories = m.memories.filter((mem) => mem.repoPath !== repoPath);
|
|
52421
|
+
saveMemoryFile(m);
|
|
52422
|
+
}
|
|
52423
|
+
function generateId() {
|
|
52424
|
+
return Math.random().toString(36).slice(2, 8);
|
|
52425
|
+
}
|
|
52426
|
+
function addMemory(content, repoPath) {
|
|
52427
|
+
const m = loadMemoryFile();
|
|
52428
|
+
const memory = {
|
|
52429
|
+
id: generateId(),
|
|
52430
|
+
content,
|
|
52431
|
+
timestamp: new Date().toISOString(),
|
|
52432
|
+
repoPath
|
|
52433
|
+
};
|
|
52434
|
+
m.memories.push(memory);
|
|
52435
|
+
saveMemoryFile(m);
|
|
52436
|
+
return memory;
|
|
52328
52437
|
}
|
|
52329
|
-
function
|
|
52330
|
-
const
|
|
52331
|
-
|
|
52332
|
-
|
|
52438
|
+
function deleteMemory(id, repoPath) {
|
|
52439
|
+
const m = loadMemoryFile();
|
|
52440
|
+
const before2 = m.memories.length;
|
|
52441
|
+
m.memories = m.memories.filter((mem) => !(mem.id === id && mem.repoPath === repoPath));
|
|
52442
|
+
if (m.memories.length === before2)
|
|
52443
|
+
return false;
|
|
52444
|
+
saveMemoryFile(m);
|
|
52445
|
+
return true;
|
|
52446
|
+
}
|
|
52447
|
+
function listMemories(repoPath) {
|
|
52448
|
+
return loadMemoryFile().memories.filter((mem) => mem.repoPath === repoPath);
|
|
52333
52449
|
}
|
|
52334
52450
|
|
|
52335
52451
|
// src/components/chat/ChatRunner.tsx
|
|
@@ -52337,58 +52453,120 @@ var jsx_dev_runtime22 = __toESM(require_jsx_dev_runtime(), 1);
|
|
|
52337
52453
|
var COMMANDS = [
|
|
52338
52454
|
{ cmd: "/timeline", desc: "browse commit history" },
|
|
52339
52455
|
{ 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" }
|
|
52456
|
+
{ cmd: "/review", desc: "review current codebase" },
|
|
52457
|
+
{ cmd: "/auto", desc: "toggle auto-approve for read/search tools" },
|
|
52458
|
+
{ cmd: "/chat", desc: "chat history commands" },
|
|
52459
|
+
{ cmd: "/chat list", desc: "list saved chats for this repo" },
|
|
52460
|
+
{ cmd: "/chat load", desc: "load a saved chat by name" },
|
|
52461
|
+
{ cmd: "/chat rename", desc: "rename the current chat" },
|
|
52462
|
+
{ cmd: "/chat delete", desc: "delete a saved chat by name" },
|
|
52463
|
+
{ cmd: "/memory", desc: "memory commands" },
|
|
52464
|
+
{ cmd: "/memory list", desc: "list all memories for this repo" },
|
|
52465
|
+
{ cmd: "/memory add", desc: "add a memory" },
|
|
52466
|
+
{ cmd: "/memory delete", desc: "delete a memory by id" },
|
|
52467
|
+
{ cmd: "/memory clear", desc: "clear all memories for this repo" }
|
|
52342
52468
|
];
|
|
52343
52469
|
function CommandPalette({
|
|
52344
52470
|
query,
|
|
52345
|
-
onSelect
|
|
52471
|
+
onSelect,
|
|
52472
|
+
recentChats
|
|
52346
52473
|
}) {
|
|
52347
52474
|
const q = query.toLowerCase();
|
|
52475
|
+
const isChatLoad = q.startsWith("/chat load") || q.startsWith("/chat delete");
|
|
52476
|
+
const chatFilter = isChatLoad ? q.startsWith("/chat load") ? q.slice("/chat load".length).trim() : q.slice("/chat delete".length).trim() : "";
|
|
52477
|
+
const filteredChats = chatFilter ? recentChats.filter((n) => n.toLowerCase().includes(chatFilter)) : recentChats;
|
|
52348
52478
|
const matches2 = COMMANDS.filter((c) => c.cmd.startsWith(q));
|
|
52349
|
-
if (!matches2.length)
|
|
52479
|
+
if (!matches2.length && !isChatLoad)
|
|
52480
|
+
return null;
|
|
52481
|
+
if (!matches2.length && isChatLoad && filteredChats.length === 0)
|
|
52350
52482
|
return null;
|
|
52351
52483
|
return /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(Box_default, {
|
|
52352
52484
|
flexDirection: "column",
|
|
52353
52485
|
marginBottom: 1,
|
|
52354
52486
|
marginLeft: 2,
|
|
52355
|
-
children:
|
|
52356
|
-
|
|
52357
|
-
|
|
52358
|
-
|
|
52487
|
+
children: [
|
|
52488
|
+
matches2.map((c, i) => {
|
|
52489
|
+
const isExact = c.cmd === query;
|
|
52490
|
+
return /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(Box_default, {
|
|
52491
|
+
gap: 2,
|
|
52492
|
+
children: [
|
|
52493
|
+
/* @__PURE__ */ jsx_dev_runtime22.jsxDEV(Text, {
|
|
52494
|
+
color: isExact ? ACCENT : "white",
|
|
52495
|
+
bold: isExact,
|
|
52496
|
+
children: c.cmd
|
|
52497
|
+
}, undefined, false, undefined, this),
|
|
52498
|
+
/* @__PURE__ */ jsx_dev_runtime22.jsxDEV(Text, {
|
|
52499
|
+
color: "gray",
|
|
52500
|
+
dimColor: true,
|
|
52501
|
+
children: c.desc
|
|
52502
|
+
}, undefined, false, undefined, this)
|
|
52503
|
+
]
|
|
52504
|
+
}, i, true, undefined, this);
|
|
52505
|
+
}),
|
|
52506
|
+
isChatLoad && filteredChats.length > 0 && /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(Box_default, {
|
|
52507
|
+
flexDirection: "column",
|
|
52508
|
+
marginTop: matches2.length ? 1 : 0,
|
|
52359
52509
|
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
52510
|
/* @__PURE__ */ jsx_dev_runtime22.jsxDEV(Text, {
|
|
52366
52511
|
color: "gray",
|
|
52367
52512
|
dimColor: true,
|
|
52368
|
-
children:
|
|
52369
|
-
}, undefined, false, undefined, this)
|
|
52513
|
+
children: chatFilter ? `matching "${chatFilter}":` : "recent chats:"
|
|
52514
|
+
}, undefined, false, undefined, this),
|
|
52515
|
+
filteredChats.map((name, i) => /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(Box_default, {
|
|
52516
|
+
gap: 1,
|
|
52517
|
+
marginLeft: 2,
|
|
52518
|
+
children: [
|
|
52519
|
+
/* @__PURE__ */ jsx_dev_runtime22.jsxDEV(Text, {
|
|
52520
|
+
color: ACCENT,
|
|
52521
|
+
children: "·"
|
|
52522
|
+
}, undefined, false, undefined, this),
|
|
52523
|
+
/* @__PURE__ */ jsx_dev_runtime22.jsxDEV(Text, {
|
|
52524
|
+
color: "white",
|
|
52525
|
+
children: name
|
|
52526
|
+
}, undefined, false, undefined, this)
|
|
52527
|
+
]
|
|
52528
|
+
}, i, true, undefined, this))
|
|
52370
52529
|
]
|
|
52371
|
-
},
|
|
52372
|
-
|
|
52373
|
-
}, undefined,
|
|
52530
|
+
}, undefined, true, undefined, this)
|
|
52531
|
+
]
|
|
52532
|
+
}, undefined, true, undefined, this);
|
|
52374
52533
|
}
|
|
52375
52534
|
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
|
|
52535
|
+
const [stage, setStage] = import_react49.useState({ type: "picking-provider" });
|
|
52536
|
+
const [committed, setCommitted] = import_react49.useState([]);
|
|
52537
|
+
const [provider, setProvider] = import_react49.useState(null);
|
|
52538
|
+
const [systemPrompt, setSystemPrompt] = import_react49.useState("");
|
|
52539
|
+
const [inputValue, setInputValue] = import_react49.useState("");
|
|
52540
|
+
const [pendingMsgIndex, setPendingMsgIndex] = import_react49.useState(null);
|
|
52541
|
+
const [allMessages, setAllMessages] = import_react49.useState([]);
|
|
52542
|
+
const [clonedUrls, setClonedUrls] = import_react49.useState(new Set);
|
|
52543
|
+
const [showTimeline, setShowTimeline] = import_react49.useState(false);
|
|
52544
|
+
const [showReview, setShowReview] = import_react49.useState(false);
|
|
52545
|
+
const [autoApprove, setAutoApprove] = import_react49.useState(false);
|
|
52546
|
+
const [chatName, setChatName] = import_react49.useState(null);
|
|
52547
|
+
const chatNameRef = import_react49.useRef(null);
|
|
52548
|
+
const [recentChats, setRecentChats] = import_react49.useState([]);
|
|
52549
|
+
const inputHistoryRef = import_react49.useRef([]);
|
|
52550
|
+
const historyIndexRef = import_react49.useRef(-1);
|
|
52551
|
+
const [inputKey, setInputKey] = import_react49.useState(0);
|
|
52552
|
+
const updateChatName = (name) => {
|
|
52553
|
+
chatNameRef.current = name;
|
|
52554
|
+
setChatName(name);
|
|
52555
|
+
};
|
|
52556
|
+
const abortControllerRef = import_react49.useRef(null);
|
|
52557
|
+
const toolResultCache = import_react49.useRef(new Map);
|
|
52558
|
+
const inputBuffer = import_react49.useRef("");
|
|
52559
|
+
const flushTimer = import_react49.useRef(null);
|
|
52391
52560
|
const thinkingPhrase = useThinkingPhrase(stage.type === "thinking");
|
|
52561
|
+
import_react48.default.useEffect(() => {
|
|
52562
|
+
const chats = listChats(repoPath);
|
|
52563
|
+
setRecentChats(chats.slice(0, 10).map((c) => c.name));
|
|
52564
|
+
}, [repoPath]);
|
|
52565
|
+
import_react48.default.useEffect(() => {
|
|
52566
|
+
if (chatNameRef.current && allMessages.length > 1) {
|
|
52567
|
+
saveChat(chatNameRef.current, repoPath, allMessages);
|
|
52568
|
+
}
|
|
52569
|
+
}, [allMessages]);
|
|
52392
52570
|
const flushBuffer = () => {
|
|
52393
52571
|
const buf = inputBuffer.current;
|
|
52394
52572
|
if (!buf)
|
|
@@ -52423,7 +52601,24 @@ var ChatRunner = ({ repoPath }) => {
|
|
|
52423
52601
|
setStage({ type: "idle" });
|
|
52424
52602
|
return;
|
|
52425
52603
|
}
|
|
52426
|
-
const
|
|
52604
|
+
const memAddMatches = [
|
|
52605
|
+
...raw.matchAll(/<memory-add>([\s\S]*?)<\/memory-add>/g)
|
|
52606
|
+
];
|
|
52607
|
+
const memDelMatches = [
|
|
52608
|
+
...raw.matchAll(/<memory-delete>([\s\S]*?)<\/memory-delete>/g)
|
|
52609
|
+
];
|
|
52610
|
+
for (const match of memAddMatches) {
|
|
52611
|
+
const content = match[1].trim();
|
|
52612
|
+
if (content)
|
|
52613
|
+
addMemory(content, repoPath);
|
|
52614
|
+
}
|
|
52615
|
+
for (const match of memDelMatches) {
|
|
52616
|
+
const id = match[1].trim();
|
|
52617
|
+
if (id)
|
|
52618
|
+
deleteMemory(id, repoPath);
|
|
52619
|
+
}
|
|
52620
|
+
const cleanRaw = raw.replace(/<memory-add>[\s\S]*?<\/memory-add>/g, "").replace(/<memory-delete>[\s\S]*?<\/memory-delete>/g, "").trim();
|
|
52621
|
+
const parsed = parseResponse(cleanRaw);
|
|
52427
52622
|
if (parsed.kind === "changes") {
|
|
52428
52623
|
if (parsed.patches.length === 0) {
|
|
52429
52624
|
const msg2 = {
|
|
@@ -52555,7 +52750,7 @@ var ChatRunner = ({ repoPath }) => {
|
|
|
52555
52750
|
"write-file": "file-written",
|
|
52556
52751
|
search: "url-fetched"
|
|
52557
52752
|
};
|
|
52558
|
-
|
|
52753
|
+
appendMemory({
|
|
52559
52754
|
kind: kindMap2[parsed.kind] ?? "shell-run",
|
|
52560
52755
|
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
52756
|
summary: result2.split(`
|
|
@@ -52652,7 +52847,7 @@ var ChatRunner = ({ repoPath }) => {
|
|
|
52652
52847
|
return;
|
|
52653
52848
|
}
|
|
52654
52849
|
if (text.trim().toLowerCase() === "/clear history") {
|
|
52655
|
-
|
|
52850
|
+
clearRepoMemory(repoPath);
|
|
52656
52851
|
const clearedMsg = {
|
|
52657
52852
|
role: "assistant",
|
|
52658
52853
|
content: "History cleared for this repo.",
|
|
@@ -52662,11 +52857,212 @@ var ChatRunner = ({ repoPath }) => {
|
|
|
52662
52857
|
setAllMessages((prev) => [...prev, clearedMsg]);
|
|
52663
52858
|
return;
|
|
52664
52859
|
}
|
|
52860
|
+
if (text.trim().toLowerCase() === "/chat") {
|
|
52861
|
+
const msg = {
|
|
52862
|
+
role: "assistant",
|
|
52863
|
+
content: "Chat commands: `/chat list` · `/chat load <n>` · `/chat rename <n>` · `/chat delete <n>`",
|
|
52864
|
+
type: "text"
|
|
52865
|
+
};
|
|
52866
|
+
setCommitted((prev) => [...prev, msg]);
|
|
52867
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
52868
|
+
return;
|
|
52869
|
+
}
|
|
52870
|
+
if (text.trim().toLowerCase().startsWith("/chat rename")) {
|
|
52871
|
+
const parts = text.trim().split(/\s+/);
|
|
52872
|
+
const newName = parts.slice(2).join("-");
|
|
52873
|
+
if (!newName) {
|
|
52874
|
+
const msg2 = {
|
|
52875
|
+
role: "assistant",
|
|
52876
|
+
content: "Usage: `/chat rename <new-name>`",
|
|
52877
|
+
type: "text"
|
|
52878
|
+
};
|
|
52879
|
+
setCommitted((prev) => [...prev, msg2]);
|
|
52880
|
+
setAllMessages((prev) => [...prev, msg2]);
|
|
52881
|
+
return;
|
|
52882
|
+
}
|
|
52883
|
+
const oldName = chatNameRef.current;
|
|
52884
|
+
if (oldName)
|
|
52885
|
+
deleteChat(oldName);
|
|
52886
|
+
updateChatName(newName);
|
|
52887
|
+
saveChat(newName, repoPath, allMessages);
|
|
52888
|
+
setRecentChats((prev) => [newName, ...prev.filter((n) => n !== newName && n !== oldName)].slice(0, 10));
|
|
52889
|
+
const msg = {
|
|
52890
|
+
role: "assistant",
|
|
52891
|
+
content: `Chat renamed to **${newName}**.`,
|
|
52892
|
+
type: "text"
|
|
52893
|
+
};
|
|
52894
|
+
setCommitted((prev) => [...prev, msg]);
|
|
52895
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
52896
|
+
return;
|
|
52897
|
+
}
|
|
52898
|
+
if (text.trim().toLowerCase().startsWith("/chat delete")) {
|
|
52899
|
+
const parts = text.trim().split(/\s+/);
|
|
52900
|
+
const name = parts.slice(2).join("-");
|
|
52901
|
+
if (!name) {
|
|
52902
|
+
const msg2 = {
|
|
52903
|
+
role: "assistant",
|
|
52904
|
+
content: "Usage: `/chat delete <name>`",
|
|
52905
|
+
type: "text"
|
|
52906
|
+
};
|
|
52907
|
+
setCommitted((prev) => [...prev, msg2]);
|
|
52908
|
+
setAllMessages((prev) => [...prev, msg2]);
|
|
52909
|
+
return;
|
|
52910
|
+
}
|
|
52911
|
+
const deleted = deleteChat(name);
|
|
52912
|
+
if (!deleted) {
|
|
52913
|
+
const msg2 = {
|
|
52914
|
+
role: "assistant",
|
|
52915
|
+
content: `Chat **${name}** not found.`,
|
|
52916
|
+
type: "text"
|
|
52917
|
+
};
|
|
52918
|
+
setCommitted((prev) => [...prev, msg2]);
|
|
52919
|
+
setAllMessages((prev) => [...prev, msg2]);
|
|
52920
|
+
return;
|
|
52921
|
+
}
|
|
52922
|
+
if (chatNameRef.current === name) {
|
|
52923
|
+
chatNameRef.current = null;
|
|
52924
|
+
setChatName(null);
|
|
52925
|
+
}
|
|
52926
|
+
setRecentChats((prev) => prev.filter((n) => n !== name));
|
|
52927
|
+
const msg = {
|
|
52928
|
+
role: "assistant",
|
|
52929
|
+
content: `Chat **${name}** deleted.`,
|
|
52930
|
+
type: "text"
|
|
52931
|
+
};
|
|
52932
|
+
setCommitted((prev) => [...prev, msg]);
|
|
52933
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
52934
|
+
return;
|
|
52935
|
+
}
|
|
52936
|
+
if (text.trim().toLowerCase() === "/chat list") {
|
|
52937
|
+
const chats = listChats(repoPath);
|
|
52938
|
+
const content = chats.length === 0 ? "No saved chats for this repo yet." : `Saved chats:
|
|
52939
|
+
|
|
52940
|
+
${chats.map((c) => `- **${c.name}** · ${c.userMessageCount} messages · ${new Date(c.savedAt).toLocaleString()}`).join(`
|
|
52941
|
+
`)}`;
|
|
52942
|
+
const msg = { role: "assistant", content, type: "text" };
|
|
52943
|
+
setCommitted((prev) => [...prev, msg]);
|
|
52944
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
52945
|
+
return;
|
|
52946
|
+
}
|
|
52947
|
+
if (text.trim().toLowerCase().startsWith("/chat load")) {
|
|
52948
|
+
const parts = text.trim().split(/\s+/);
|
|
52949
|
+
const name = parts.slice(2).join("-");
|
|
52950
|
+
if (!name) {
|
|
52951
|
+
const chats = listChats(repoPath);
|
|
52952
|
+
const content = chats.length === 0 ? "No saved chats found." : `Specify a chat name. Recent chats:
|
|
52953
|
+
|
|
52954
|
+
${chats.slice(0, 10).map((c) => `- **${c.name}**`).join(`
|
|
52955
|
+
`)}`;
|
|
52956
|
+
const msg = { role: "assistant", content, type: "text" };
|
|
52957
|
+
setCommitted((prev) => [...prev, msg]);
|
|
52958
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
52959
|
+
return;
|
|
52960
|
+
}
|
|
52961
|
+
const saved = loadChat(name);
|
|
52962
|
+
if (!saved) {
|
|
52963
|
+
const msg = {
|
|
52964
|
+
role: "assistant",
|
|
52965
|
+
content: `Chat **${name}** not found. Use \`/chat list\` to see saved chats.`,
|
|
52966
|
+
type: "text"
|
|
52967
|
+
};
|
|
52968
|
+
setCommitted((prev) => [...prev, msg]);
|
|
52969
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
52970
|
+
return;
|
|
52971
|
+
}
|
|
52972
|
+
updateChatName(name);
|
|
52973
|
+
setAllMessages(saved.messages);
|
|
52974
|
+
setCommitted(saved.messages);
|
|
52975
|
+
const notice = {
|
|
52976
|
+
role: "assistant",
|
|
52977
|
+
content: `Loaded chat **${name}** · ${saved.userMessageCount} messages · saved ${new Date(saved.savedAt).toLocaleString()}`,
|
|
52978
|
+
type: "text"
|
|
52979
|
+
};
|
|
52980
|
+
setCommitted((prev) => [...prev, notice]);
|
|
52981
|
+
setAllMessages((prev) => [...prev, notice]);
|
|
52982
|
+
return;
|
|
52983
|
+
}
|
|
52984
|
+
if (text.trim().toLowerCase() === "/memory list" || text.trim().toLowerCase() === "/memory") {
|
|
52985
|
+
const mems = listMemories(repoPath);
|
|
52986
|
+
const content = mems.length === 0 ? "No memories stored for this repo yet." : `Memories for this repo:
|
|
52987
|
+
|
|
52988
|
+
${mems.map((m) => `- [${m.id}] ${m.content}`).join(`
|
|
52989
|
+
`)}`;
|
|
52990
|
+
const msg = { role: "assistant", content, type: "text" };
|
|
52991
|
+
setCommitted((prev) => [...prev, msg]);
|
|
52992
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
52993
|
+
return;
|
|
52994
|
+
}
|
|
52995
|
+
if (text.trim().toLowerCase().startsWith("/memory add")) {
|
|
52996
|
+
const content = text.trim().slice("/memory add".length).trim();
|
|
52997
|
+
if (!content) {
|
|
52998
|
+
const msg2 = {
|
|
52999
|
+
role: "assistant",
|
|
53000
|
+
content: "Usage: `/memory add <content>`",
|
|
53001
|
+
type: "text"
|
|
53002
|
+
};
|
|
53003
|
+
setCommitted((prev) => [...prev, msg2]);
|
|
53004
|
+
setAllMessages((prev) => [...prev, msg2]);
|
|
53005
|
+
return;
|
|
53006
|
+
}
|
|
53007
|
+
const mem = addMemory(content, repoPath);
|
|
53008
|
+
const msg = {
|
|
53009
|
+
role: "assistant",
|
|
53010
|
+
content: `Memory saved **[${mem.id}]**: ${mem.content}`,
|
|
53011
|
+
type: "text"
|
|
53012
|
+
};
|
|
53013
|
+
setCommitted((prev) => [...prev, msg]);
|
|
53014
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
53015
|
+
return;
|
|
53016
|
+
}
|
|
53017
|
+
if (text.trim().toLowerCase().startsWith("/memory delete")) {
|
|
53018
|
+
const id = text.trim().split(/\s+/)[2];
|
|
53019
|
+
if (!id) {
|
|
53020
|
+
const msg2 = {
|
|
53021
|
+
role: "assistant",
|
|
53022
|
+
content: "Usage: `/memory delete <id>`",
|
|
53023
|
+
type: "text"
|
|
53024
|
+
};
|
|
53025
|
+
setCommitted((prev) => [...prev, msg2]);
|
|
53026
|
+
setAllMessages((prev) => [...prev, msg2]);
|
|
53027
|
+
return;
|
|
53028
|
+
}
|
|
53029
|
+
const deleted = deleteMemory(id, repoPath);
|
|
53030
|
+
const msg = {
|
|
53031
|
+
role: "assistant",
|
|
53032
|
+
content: deleted ? `Memory **[${id}]** deleted.` : `Memory **[${id}]** not found.`,
|
|
53033
|
+
type: "text"
|
|
53034
|
+
};
|
|
53035
|
+
setCommitted((prev) => [...prev, msg]);
|
|
53036
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
53037
|
+
return;
|
|
53038
|
+
}
|
|
53039
|
+
if (text.trim().toLowerCase() === "/memory clear") {
|
|
53040
|
+
clearRepoMemory(repoPath);
|
|
53041
|
+
const msg = {
|
|
53042
|
+
role: "assistant",
|
|
53043
|
+
content: "All memories cleared for this repo.",
|
|
53044
|
+
type: "text"
|
|
53045
|
+
};
|
|
53046
|
+
setCommitted((prev) => [...prev, msg]);
|
|
53047
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
53048
|
+
return;
|
|
53049
|
+
}
|
|
52665
53050
|
const userMsg = { role: "user", content: text, type: "text" };
|
|
52666
53051
|
const nextAll = [...allMessages, userMsg];
|
|
52667
53052
|
setCommitted((prev) => [...prev, userMsg]);
|
|
52668
53053
|
setAllMessages(nextAll);
|
|
52669
53054
|
toolResultCache.current.clear();
|
|
53055
|
+
inputHistoryRef.current = [
|
|
53056
|
+
text,
|
|
53057
|
+
...inputHistoryRef.current.filter((m) => m !== text)
|
|
53058
|
+
].slice(0, 50);
|
|
53059
|
+
historyIndexRef.current = -1;
|
|
53060
|
+
if (!chatName) {
|
|
53061
|
+
const name = getChatNameSuggestions(nextAll)[0] ?? `chat-${new Date().toISOString().slice(0, 10)}`;
|
|
53062
|
+
updateChatName(name);
|
|
53063
|
+
setRecentChats((prev) => [name, ...prev.filter((n) => n !== name)].slice(0, 10));
|
|
53064
|
+
saveChat(name, repoPath, nextAll);
|
|
53065
|
+
}
|
|
52670
53066
|
const abort = new AbortController;
|
|
52671
53067
|
abortControllerRef.current = abort;
|
|
52672
53068
|
setStage({ type: "thinking" });
|
|
@@ -52686,6 +53082,21 @@ var ChatRunner = ({ repoPath }) => {
|
|
|
52686
53082
|
process.exit(0);
|
|
52687
53083
|
return;
|
|
52688
53084
|
}
|
|
53085
|
+
if (key.upArrow && inputHistoryRef.current.length > 0) {
|
|
53086
|
+
const next = Math.min(historyIndexRef.current + 1, inputHistoryRef.current.length - 1);
|
|
53087
|
+
historyIndexRef.current = next;
|
|
53088
|
+
setInputValue(inputHistoryRef.current[next]);
|
|
53089
|
+
setInputKey((k) => k + 1);
|
|
53090
|
+
return;
|
|
53091
|
+
}
|
|
53092
|
+
if (key.downArrow) {
|
|
53093
|
+
const next = historyIndexRef.current - 1;
|
|
53094
|
+
historyIndexRef.current = next;
|
|
53095
|
+
const val = next < 0 ? "" : inputHistoryRef.current[next];
|
|
53096
|
+
setInputValue(val);
|
|
53097
|
+
setInputKey((k) => k + 1);
|
|
53098
|
+
return;
|
|
53099
|
+
}
|
|
52689
53100
|
if (key.tab && inputValue.startsWith("/")) {
|
|
52690
53101
|
const q = inputValue.toLowerCase();
|
|
52691
53102
|
const match = COMMANDS.find((c) => c.cmd.startsWith(q));
|
|
@@ -52704,9 +53115,9 @@ var ChatRunner = ({ repoPath }) => {
|
|
|
52704
53115
|
startCloneRepo(cloneUrl).then((result2) => {
|
|
52705
53116
|
if (result2.done) {
|
|
52706
53117
|
const repoName = cloneUrl.split("/").pop()?.replace(/\.git$/, "") ?? "repo";
|
|
52707
|
-
const destPath =
|
|
53118
|
+
const destPath = path18.join(os9.tmpdir(), repoName);
|
|
52708
53119
|
const fileCount = walkDir3(destPath).length;
|
|
52709
|
-
|
|
53120
|
+
appendMemory({
|
|
52710
53121
|
kind: "url-fetched",
|
|
52711
53122
|
detail: repoUrl,
|
|
52712
53123
|
summary: `Cloned ${repoName} — ${fileCount} files`,
|
|
@@ -52834,7 +53245,7 @@ Ask me anything about it — I can read files, explain how it works, or suggest
|
|
|
52834
53245
|
const msg = allMessages[pendingMsgIndex];
|
|
52835
53246
|
if (msg?.type === "plan") {
|
|
52836
53247
|
setCommitted((prev) => [...prev, { ...msg, applied: false }]);
|
|
52837
|
-
|
|
53248
|
+
appendMemory({
|
|
52838
53249
|
kind: "code-skipped",
|
|
52839
53250
|
detail: msg.patches.map((p) => p.path).join(", "),
|
|
52840
53251
|
summary: `Skipped changes to ${msg.patches.length} file(s)`,
|
|
@@ -52849,7 +53260,7 @@ Ask me anything about it — I can read files, explain how it works, or suggest
|
|
|
52849
53260
|
if (key.return || input === "a" || input === "A") {
|
|
52850
53261
|
try {
|
|
52851
53262
|
applyPatches3(repoPath, stage.patches);
|
|
52852
|
-
|
|
53263
|
+
appendMemory({
|
|
52853
53264
|
kind: "code-applied",
|
|
52854
53265
|
detail: stage.patches.map((p) => p.path).join(", "),
|
|
52855
53266
|
summary: `Applied changes to ${stage.patches.length} file(s)`,
|
|
@@ -52892,7 +53303,7 @@ Ask me anything about it — I can read files, explain how it works, or suggest
|
|
|
52892
53303
|
setStage({ type: "loading" });
|
|
52893
53304
|
fetchFileTree(repoPath).catch(() => walkDir3(repoPath)).then((fileTree) => {
|
|
52894
53305
|
const importantFiles = readImportantFiles(repoPath, fileTree);
|
|
52895
|
-
const historySummary =
|
|
53306
|
+
const historySummary = buildMemorySummary(repoPath);
|
|
52896
53307
|
const lensFile = readLensFile(repoPath);
|
|
52897
53308
|
const lensContext = lensFile ? `
|
|
52898
53309
|
|
|
@@ -53029,18 +53440,21 @@ Tip: type /timeline to browse commit history.`,
|
|
|
53029
53440
|
children: [
|
|
53030
53441
|
inputValue.startsWith("/") && /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(CommandPalette, {
|
|
53031
53442
|
query: inputValue,
|
|
53032
|
-
onSelect: (cmd) =>
|
|
53033
|
-
|
|
53034
|
-
}
|
|
53443
|
+
onSelect: (cmd) => setInputValue(cmd),
|
|
53444
|
+
recentChats
|
|
53035
53445
|
}, undefined, false, undefined, this),
|
|
53036
53446
|
/* @__PURE__ */ jsx_dev_runtime22.jsxDEV(InputBox, {
|
|
53037
53447
|
value: inputValue,
|
|
53038
|
-
onChange:
|
|
53448
|
+
onChange: (v) => {
|
|
53449
|
+
historyIndexRef.current = -1;
|
|
53450
|
+
setInputValue(v);
|
|
53451
|
+
},
|
|
53039
53452
|
onSubmit: (val) => {
|
|
53040
53453
|
if (val.trim())
|
|
53041
53454
|
sendMessage(val.trim());
|
|
53042
53455
|
setInputValue("");
|
|
53043
|
-
}
|
|
53456
|
+
},
|
|
53457
|
+
inputKey
|
|
53044
53458
|
}, undefined, false, undefined, this),
|
|
53045
53459
|
/* @__PURE__ */ jsx_dev_runtime22.jsxDEV(ShortcutBar, {
|
|
53046
53460
|
autoApprove
|
|
@@ -53054,8 +53468,8 @@ Tip: type /timeline to browse commit history.`,
|
|
|
53054
53468
|
// src/commands/chat.tsx
|
|
53055
53469
|
var jsx_dev_runtime23 = __toESM(require_jsx_dev_runtime(), 1);
|
|
53056
53470
|
var ChatCommand = ({ path: inputPath }) => {
|
|
53057
|
-
const resolvedPath =
|
|
53058
|
-
if (!
|
|
53471
|
+
const resolvedPath = path19.resolve(inputPath);
|
|
53472
|
+
if (!existsSync14(resolvedPath)) {
|
|
53059
53473
|
return /* @__PURE__ */ jsx_dev_runtime23.jsxDEV(Box_default, {
|
|
53060
53474
|
marginTop: 1,
|
|
53061
53475
|
children: /* @__PURE__ */ jsx_dev_runtime23.jsxDEV(Text, {
|
|
@@ -53074,12 +53488,12 @@ var ChatCommand = ({ path: inputPath }) => {
|
|
|
53074
53488
|
};
|
|
53075
53489
|
|
|
53076
53490
|
// src/commands/timeline.tsx
|
|
53077
|
-
import { existsSync as
|
|
53078
|
-
import
|
|
53491
|
+
import { existsSync as existsSync15 } from "fs";
|
|
53492
|
+
import path20 from "path";
|
|
53079
53493
|
var jsx_dev_runtime24 = __toESM(require_jsx_dev_runtime(), 1);
|
|
53080
53494
|
var TimelineCommand = ({ path: inputPath }) => {
|
|
53081
|
-
const resolvedPath =
|
|
53082
|
-
if (!
|
|
53495
|
+
const resolvedPath = path20.resolve(inputPath);
|
|
53496
|
+
if (!existsSync15(resolvedPath)) {
|
|
53083
53497
|
return /* @__PURE__ */ jsx_dev_runtime24.jsxDEV(Box_default, {
|
|
53084
53498
|
marginTop: 1,
|
|
53085
53499
|
children: /* @__PURE__ */ jsx_dev_runtime24.jsxDEV(Text, {
|