@ridit/lens 0.2.1 → 0.2.4
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/LENS.md +62 -19
- package/dist/index.mjs +660 -469
- package/package.json +2 -1
- package/src/components/chat/ChatOverlays.tsx +73 -46
- package/src/components/chat/ChatRunner.tsx +203 -369
- package/src/index.tsx +6 -0
- package/src/prompts/system.ts +79 -102
- package/src/utils/addons/loadAddons.ts +29 -0
- package/src/utils/chat.ts +95 -176
- package/src/utils/memory.ts +7 -12
- package/src/utils/tools/builtins.ts +324 -0
- package/src/utils/tools/registry.ts +63 -0
- package/hello.py +0 -51
package/dist/index.mjs
CHANGED
|
@@ -35841,6 +35841,43 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
|
|
|
35841
35841
|
}
|
|
35842
35842
|
});
|
|
35843
35843
|
|
|
35844
|
+
// src/utils/tools/registry.ts
|
|
35845
|
+
class ToolRegistry {
|
|
35846
|
+
tools = new Map;
|
|
35847
|
+
register(tool) {
|
|
35848
|
+
if (this.tools.has(tool.name)) {
|
|
35849
|
+
console.warn(`[ToolRegistry] Overwriting existing tool: "${tool.name}"`);
|
|
35850
|
+
}
|
|
35851
|
+
this.tools.set(tool.name, tool);
|
|
35852
|
+
}
|
|
35853
|
+
unregister(name) {
|
|
35854
|
+
this.tools.delete(name);
|
|
35855
|
+
}
|
|
35856
|
+
get(name) {
|
|
35857
|
+
return this.tools.get(name);
|
|
35858
|
+
}
|
|
35859
|
+
all() {
|
|
35860
|
+
return Array.from(this.tools.values());
|
|
35861
|
+
}
|
|
35862
|
+
names() {
|
|
35863
|
+
return Array.from(this.tools.keys());
|
|
35864
|
+
}
|
|
35865
|
+
buildSystemPromptSection() {
|
|
35866
|
+
const lines = [`## TOOLS
|
|
35867
|
+
`];
|
|
35868
|
+
lines.push("You have exactly " + this.tools.size + ` tools. To use a tool you MUST wrap it in the exact XML tags shown below — no other format will work.
|
|
35869
|
+
`);
|
|
35870
|
+
let i = 1;
|
|
35871
|
+
for (const tool of this.tools.values()) {
|
|
35872
|
+
lines.push(tool.systemPromptEntry(i++));
|
|
35873
|
+
}
|
|
35874
|
+
return lines.join(`
|
|
35875
|
+
`);
|
|
35876
|
+
}
|
|
35877
|
+
}
|
|
35878
|
+
var registry = new ToolRegistry;
|
|
35879
|
+
globalThis.__lens_registry = registry;
|
|
35880
|
+
|
|
35844
35881
|
// node_modules/ink/build/render.js
|
|
35845
35882
|
import { Stream } from "node:stream";
|
|
35846
35883
|
import process13 from "node:process";
|
|
@@ -49206,72 +49243,17 @@ print("OK")
|
|
|
49206
49243
|
}
|
|
49207
49244
|
}
|
|
49208
49245
|
// src/prompts/system.ts
|
|
49209
|
-
function buildSystemPrompt(files, memorySummary = "") {
|
|
49246
|
+
function buildSystemPrompt(files, memorySummary = "", toolsSection) {
|
|
49210
49247
|
const fileList = files.map((f) => `### ${f.path}
|
|
49211
49248
|
\`\`\`
|
|
49212
49249
|
${f.content.slice(0, 2000)}
|
|
49213
49250
|
\`\`\``).join(`
|
|
49214
49251
|
|
|
49215
49252
|
`);
|
|
49253
|
+
const tools = toolsSection ?? BUILTIN_TOOLS_SECTION;
|
|
49216
49254
|
return `You are an expert software engineer assistant with access to the user's codebase and tools.
|
|
49217
49255
|
|
|
49218
|
-
|
|
49219
|
-
|
|
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.
|
|
49221
|
-
|
|
49222
|
-
### 1. fetch — load a URL
|
|
49223
|
-
<fetch>https://example.com</fetch>
|
|
49224
|
-
|
|
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>
|
|
49256
|
+
${tools}
|
|
49275
49257
|
|
|
49276
49258
|
## MEMORY OPERATIONS
|
|
49277
49259
|
|
|
@@ -49321,9 +49303,26 @@ You may emit multiple memory operations in a single response alongside normal co
|
|
|
49321
49303
|
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
49304
|
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
49305
|
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
|
|
49325
|
-
26. NEVER
|
|
49326
|
-
|
|
49306
|
+
25. NEVER read files, list folders, or run tools that were not asked for in the current user message
|
|
49307
|
+
26. NEVER use markdown formatting in plain text responses — no **bold**, no *italics*, no # headings, no bullet points with -, *, or +, no numbered lists, no backtick inline code. Write in plain prose. Only use fenced \`\`\` code blocks when showing actual code.
|
|
49308
|
+
|
|
49309
|
+
## SCAFFOLDING — CHAINING WRITE-FILE CALLS
|
|
49310
|
+
|
|
49311
|
+
When creating multiple files (e.g. scaffolding a project or creating 10 files), emit ALL of them
|
|
49312
|
+
in a single response by chaining the tags back-to-back with no text between them:
|
|
49313
|
+
|
|
49314
|
+
<write-file>
|
|
49315
|
+
{"path": "test/file1.txt", "content": "File 1 content"}
|
|
49316
|
+
</write-file>
|
|
49317
|
+
<write-file>
|
|
49318
|
+
{"path": "test/file2.txt", "content": "File 2 content"}
|
|
49319
|
+
</write-file>
|
|
49320
|
+
<write-file>
|
|
49321
|
+
{"path": "test/file3.txt", "content": "File 3 content"}
|
|
49322
|
+
</write-file>
|
|
49323
|
+
|
|
49324
|
+
The system processes each tag sequentially and automatically continues to the next one.
|
|
49325
|
+
Do NOT wait for a user message between files — emit all tags at once.
|
|
49327
49326
|
|
|
49328
49327
|
## CRITICAL: READ BEFORE YOU WRITE
|
|
49329
49328
|
|
|
@@ -49345,61 +49344,62 @@ These rules are mandatory whenever you plan to edit or create a file:
|
|
|
49345
49344
|
- NEVER produce a file that is shorter than the original unless you are explicitly asked to delete things
|
|
49346
49345
|
- If you catch yourself rewriting a file from scratch, STOP — go back and read the original first
|
|
49347
49346
|
|
|
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
49347
|
## CODEBASE
|
|
49398
49348
|
|
|
49399
49349
|
${fileList.length > 0 ? fileList : "(no files indexed)"}
|
|
49400
49350
|
|
|
49401
49351
|
${memorySummary}`;
|
|
49402
49352
|
}
|
|
49353
|
+
var BUILTIN_TOOLS_SECTION = `## TOOLS
|
|
49354
|
+
|
|
49355
|
+
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.
|
|
49356
|
+
|
|
49357
|
+
### 1. fetch — load a URL
|
|
49358
|
+
<fetch>https://example.com</fetch>
|
|
49359
|
+
|
|
49360
|
+
### 2. shell — run a terminal command
|
|
49361
|
+
<shell>node -v</shell>
|
|
49362
|
+
|
|
49363
|
+
### 3. read-file — read a file from the repo
|
|
49364
|
+
<read-file>src/foo.ts</read-file>
|
|
49365
|
+
|
|
49366
|
+
### 4. read-folder — list contents of a folder (files + subfolders, one level deep)
|
|
49367
|
+
<read-folder>src/components</read-folder>
|
|
49368
|
+
|
|
49369
|
+
### 5. grep — search for a pattern across files in the repo (cross-platform, no shell needed)
|
|
49370
|
+
<grep>
|
|
49371
|
+
{"pattern": "ChatRunner", "glob": "src/**/*.tsx"}
|
|
49372
|
+
</grep>
|
|
49373
|
+
|
|
49374
|
+
### 6. write-file — create or overwrite a file
|
|
49375
|
+
<write-file>
|
|
49376
|
+
{"path": "data/output.csv", "content": "col1,col2\\nval1,val2"}
|
|
49377
|
+
</write-file>
|
|
49378
|
+
|
|
49379
|
+
### 7. delete-file — permanently delete a single file
|
|
49380
|
+
<delete-file>src/old-component.tsx</delete-file>
|
|
49381
|
+
|
|
49382
|
+
### 8. delete-folder — permanently delete a folder and all its contents
|
|
49383
|
+
<delete-folder>src/legacy</delete-folder>
|
|
49384
|
+
|
|
49385
|
+
### 9. open-url — open a URL in the user's default browser
|
|
49386
|
+
<open-url>https://github.com/owner/repo</open-url>
|
|
49387
|
+
|
|
49388
|
+
### 10. generate-pdf — generate a PDF file from markdown-style content
|
|
49389
|
+
<generate-pdf>
|
|
49390
|
+
{"path": "output/report.pdf", "content": "# Title\\n\\nSome body text.\\n\\n## Section\\n\\nMore content."}
|
|
49391
|
+
</generate-pdf>
|
|
49392
|
+
|
|
49393
|
+
### 11. search — search the internet for anything you are unsure about
|
|
49394
|
+
<search>how to use React useEffect cleanup function</search>
|
|
49395
|
+
|
|
49396
|
+
### 12. clone — clone a GitHub repo so you can explore and discuss it
|
|
49397
|
+
<clone>https://github.com/owner/repo</clone>
|
|
49398
|
+
|
|
49399
|
+
### 13. changes — propose code edits (shown as a diff for user approval)
|
|
49400
|
+
<changes>
|
|
49401
|
+
{"summary": "what changed and why", "patches": [{"path": "src/foo.ts", "content": "COMPLETE file content", "isNew": false}]}
|
|
49402
|
+
</changes>`;
|
|
49403
49403
|
// src/prompts/fewshot.ts
|
|
49404
49404
|
var FEW_SHOT_MESSAGES = [
|
|
49405
49405
|
{
|
|
@@ -49846,129 +49846,89 @@ Done — removed that memory.`
|
|
|
49846
49846
|
function parseResponse(text) {
|
|
49847
49847
|
const scanText = text.replace(/```[\s\S]*?```/g, (m) => " ".repeat(m.length));
|
|
49848
49848
|
const candidates = [];
|
|
49849
|
-
const
|
|
49850
|
-
|
|
49851
|
-
|
|
49852
|
-
|
|
49853
|
-
|
|
49854
|
-
|
|
49855
|
-
|
|
49856
|
-
|
|
49857
|
-
|
|
49858
|
-
|
|
49859
|
-
|
|
49860
|
-
|
|
49861
|
-
|
|
49862
|
-
|
|
49863
|
-
|
|
49864
|
-
|
|
49865
|
-
|
|
49866
|
-
|
|
49867
|
-
|
|
49868
|
-
|
|
49869
|
-
|
|
49870
|
-
|
|
49871
|
-
|
|
49872
|
-
|
|
49873
|
-
|
|
49874
|
-
|
|
49875
|
-
|
|
49876
|
-
|
|
49877
|
-
|
|
49878
|
-
|
|
49879
|
-
|
|
49880
|
-
|
|
49881
|
-
|
|
49882
|
-
|
|
49883
|
-
originalMatch[1]
|
|
49884
|
-
], { index: m.index, input: text, groups: undefined });
|
|
49885
|
-
candidates.push({ index: m.index, kind: kind2, match: fakeMatch });
|
|
49849
|
+
for (const toolName2 of registry.names()) {
|
|
49850
|
+
const escaped = toolName2.replace(/[-]/g, "\\-");
|
|
49851
|
+
const xmlRe = new RegExp(`<${escaped}>([\\s\\S]*?)<\\/${escaped}>`, "g");
|
|
49852
|
+
xmlRe.lastIndex = 0;
|
|
49853
|
+
const xmlM = xmlRe.exec(scanText);
|
|
49854
|
+
if (xmlM) {
|
|
49855
|
+
const orig = new RegExp(xmlRe.source);
|
|
49856
|
+
const origM = orig.exec(text.slice(xmlM.index));
|
|
49857
|
+
if (origM) {
|
|
49858
|
+
candidates.push({
|
|
49859
|
+
index: xmlM.index,
|
|
49860
|
+
toolName: toolName2,
|
|
49861
|
+
match: Object.assign([
|
|
49862
|
+
text.slice(xmlM.index, xmlM.index + origM[0].length),
|
|
49863
|
+
origM[1]
|
|
49864
|
+
], { index: xmlM.index, input: text, groups: undefined })
|
|
49865
|
+
});
|
|
49866
|
+
}
|
|
49867
|
+
}
|
|
49868
|
+
const fencedRe = new RegExp(`\`\`\`${escaped}\\r?\\n([\\s\\S]*?)\\r?\\n\`\`\``, "g");
|
|
49869
|
+
fencedRe.lastIndex = 0;
|
|
49870
|
+
const fencedM = fencedRe.exec(scanText);
|
|
49871
|
+
if (fencedM) {
|
|
49872
|
+
const orig = new RegExp(fencedRe.source);
|
|
49873
|
+
const origM = orig.exec(text.slice(fencedM.index));
|
|
49874
|
+
if (origM) {
|
|
49875
|
+
candidates.push({
|
|
49876
|
+
index: fencedM.index,
|
|
49877
|
+
toolName: toolName2,
|
|
49878
|
+
match: Object.assign([
|
|
49879
|
+
text.slice(fencedM.index, fencedM.index + origM[0].length),
|
|
49880
|
+
origM[1]
|
|
49881
|
+
], { index: fencedM.index, input: text, groups: undefined })
|
|
49882
|
+
});
|
|
49886
49883
|
}
|
|
49887
49884
|
}
|
|
49888
49885
|
}
|
|
49889
49886
|
if (candidates.length === 0)
|
|
49890
49887
|
return { kind: "text", content: text.trim() };
|
|
49891
49888
|
candidates.sort((a, b) => a.index - b.index);
|
|
49892
|
-
const {
|
|
49893
|
-
const before2 = text.slice(0, match.index).
|
|
49889
|
+
const { toolName, match } = candidates[0];
|
|
49890
|
+
const before2 = text.slice(0, match.index).trim();
|
|
49894
49891
|
const body = (match[1] ?? "").trim();
|
|
49895
|
-
|
|
49892
|
+
const afterMatch = text.slice(match.index + match[0].length).trim();
|
|
49893
|
+
const remainder = afterMatch.length > 0 ? afterMatch : undefined;
|
|
49894
|
+
if (toolName === "changes") {
|
|
49896
49895
|
try {
|
|
49897
49896
|
const parsed = JSON.parse(body);
|
|
49898
49897
|
const display = [before2, parsed.summary].filter(Boolean).join(`
|
|
49899
49898
|
|
|
49900
49899
|
`);
|
|
49901
|
-
return { kind: "changes", content: display, patches: parsed.patches };
|
|
49902
|
-
} catch {}
|
|
49903
|
-
}
|
|
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
49900
|
return {
|
|
49938
|
-
kind: "
|
|
49939
|
-
content:
|
|
49940
|
-
|
|
49941
|
-
|
|
49942
|
-
};
|
|
49943
|
-
} catch {
|
|
49944
|
-
return { kind: "text", content: text };
|
|
49945
|
-
}
|
|
49946
|
-
}
|
|
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 ?? "**/*"
|
|
49901
|
+
kind: "changes",
|
|
49902
|
+
content: display,
|
|
49903
|
+
patches: parsed.patches,
|
|
49904
|
+
remainder
|
|
49955
49905
|
};
|
|
49956
49906
|
} catch {
|
|
49957
|
-
return { kind: "
|
|
49907
|
+
return { kind: "text", content: text.trim() };
|
|
49958
49908
|
}
|
|
49959
49909
|
}
|
|
49960
|
-
if (
|
|
49961
|
-
|
|
49962
|
-
|
|
49963
|
-
|
|
49964
|
-
|
|
49965
|
-
|
|
49966
|
-
|
|
49967
|
-
fileContent: parsed.content
|
|
49968
|
-
};
|
|
49969
|
-
} catch {}
|
|
49910
|
+
if (toolName === "clone") {
|
|
49911
|
+
return {
|
|
49912
|
+
kind: "clone",
|
|
49913
|
+
content: before2,
|
|
49914
|
+
repoUrl: body.replace(/^<|>$/g, "").trim(),
|
|
49915
|
+
remainder
|
|
49916
|
+
};
|
|
49970
49917
|
}
|
|
49971
|
-
|
|
49918
|
+
const tool = registry.get(toolName);
|
|
49919
|
+
if (!tool)
|
|
49920
|
+
return { kind: "text", content: text.trim() };
|
|
49921
|
+
const input = tool.parseInput(body);
|
|
49922
|
+
if (input === null)
|
|
49923
|
+
return { kind: "text", content: text.trim() };
|
|
49924
|
+
return {
|
|
49925
|
+
kind: "tool",
|
|
49926
|
+
toolName,
|
|
49927
|
+
input,
|
|
49928
|
+
rawInput: body,
|
|
49929
|
+
content: before2,
|
|
49930
|
+
remainder
|
|
49931
|
+
};
|
|
49972
49932
|
}
|
|
49973
49933
|
function extractGithubUrl(text) {
|
|
49974
49934
|
const match = text.match(/https?:\/\/github\.com\/[\w.-]+\/[\w.-]+/);
|
|
@@ -49987,10 +49947,9 @@ function buildApiMessages(messages) {
|
|
|
49987
49947
|
content: "The tool call was denied by the user. Please respond without using that tool."
|
|
49988
49948
|
};
|
|
49989
49949
|
}
|
|
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
49950
|
return {
|
|
49992
49951
|
role: "user",
|
|
49993
|
-
content: `Here is the output from the ${
|
|
49952
|
+
content: `Here is the output from the ${m.toolName} of ${m.content}:
|
|
49994
49953
|
|
|
49995
49954
|
${m.result}
|
|
49996
49955
|
|
|
@@ -50489,50 +50448,71 @@ function PermissionPrompt({
|
|
|
50489
50448
|
let icon;
|
|
50490
50449
|
let label;
|
|
50491
50450
|
let value;
|
|
50492
|
-
if (
|
|
50493
|
-
|
|
50494
|
-
|
|
50495
|
-
|
|
50496
|
-
|
|
50497
|
-
|
|
50498
|
-
|
|
50499
|
-
|
|
50500
|
-
|
|
50501
|
-
|
|
50502
|
-
|
|
50503
|
-
|
|
50504
|
-
|
|
50505
|
-
|
|
50506
|
-
|
|
50507
|
-
|
|
50508
|
-
|
|
50509
|
-
|
|
50510
|
-
|
|
50511
|
-
value = `${tool.pattern} ${tool.glob}`;
|
|
50512
|
-
} else if (tool.type === "delete-file") {
|
|
50513
|
-
icon = "x";
|
|
50514
|
-
label = "delete";
|
|
50515
|
-
value = tool.filePath;
|
|
50516
|
-
} else if (tool.type === "delete-folder") {
|
|
50517
|
-
icon = "X";
|
|
50518
|
-
label = "delete folder";
|
|
50519
|
-
value = tool.folderPath;
|
|
50520
|
-
} else if (tool.type === "open-url") {
|
|
50521
|
-
icon = "↗";
|
|
50522
|
-
label = "open";
|
|
50523
|
-
value = tool.url;
|
|
50524
|
-
} else if (tool.type === "generate-pdf") {
|
|
50525
|
-
icon = "P";
|
|
50526
|
-
label = "pdf";
|
|
50527
|
-
value = tool.filePath;
|
|
50528
|
-
} else if (tool.type === "write-file") {
|
|
50529
|
-
icon = "w";
|
|
50530
|
-
label = "write";
|
|
50531
|
-
value = `${tool.filePath} (${tool.fileContent.length} bytes)`;
|
|
50451
|
+
if ("_label" in tool) {
|
|
50452
|
+
const iconMap = {
|
|
50453
|
+
run: "$",
|
|
50454
|
+
fetch: "~>",
|
|
50455
|
+
read: "r",
|
|
50456
|
+
write: "w",
|
|
50457
|
+
delete: "x",
|
|
50458
|
+
"delete folder": "X",
|
|
50459
|
+
open: "↗",
|
|
50460
|
+
pdf: "P",
|
|
50461
|
+
search: "?",
|
|
50462
|
+
folder: "d",
|
|
50463
|
+
grep: "/",
|
|
50464
|
+
clone: "↓",
|
|
50465
|
+
query: "⌗"
|
|
50466
|
+
};
|
|
50467
|
+
icon = iconMap[tool._label] ?? "·";
|
|
50468
|
+
label = tool._label;
|
|
50469
|
+
value = tool._display;
|
|
50532
50470
|
} else {
|
|
50533
|
-
|
|
50534
|
-
|
|
50535
|
-
|
|
50471
|
+
if (tool.type === "shell") {
|
|
50472
|
+
icon = "$";
|
|
50473
|
+
label = "run";
|
|
50474
|
+
value = tool.command;
|
|
50475
|
+
} else if (tool.type === "fetch") {
|
|
50476
|
+
icon = "~>";
|
|
50477
|
+
label = "fetch";
|
|
50478
|
+
value = tool.url;
|
|
50479
|
+
} else if (tool.type === "read-file") {
|
|
50480
|
+
icon = "r";
|
|
50481
|
+
label = "read";
|
|
50482
|
+
value = tool.filePath;
|
|
50483
|
+
} else if (tool.type === "read-folder") {
|
|
50484
|
+
icon = "d";
|
|
50485
|
+
label = "folder";
|
|
50486
|
+
value = tool.folderPath;
|
|
50487
|
+
} else if (tool.type === "grep") {
|
|
50488
|
+
icon = "/";
|
|
50489
|
+
label = "grep";
|
|
50490
|
+
value = `${tool.pattern} ${tool.glob}`;
|
|
50491
|
+
} else if (tool.type === "delete-file") {
|
|
50492
|
+
icon = "x";
|
|
50493
|
+
label = "delete";
|
|
50494
|
+
value = tool.filePath;
|
|
50495
|
+
} else if (tool.type === "delete-folder") {
|
|
50496
|
+
icon = "X";
|
|
50497
|
+
label = "delete folder";
|
|
50498
|
+
value = tool.folderPath;
|
|
50499
|
+
} else if (tool.type === "open-url") {
|
|
50500
|
+
icon = "↗";
|
|
50501
|
+
label = "open";
|
|
50502
|
+
value = tool.url;
|
|
50503
|
+
} else if (tool.type === "generate-pdf") {
|
|
50504
|
+
icon = "P";
|
|
50505
|
+
label = "pdf";
|
|
50506
|
+
value = tool.filePath;
|
|
50507
|
+
} else if (tool.type === "write-file") {
|
|
50508
|
+
icon = "w";
|
|
50509
|
+
label = "write";
|
|
50510
|
+
value = `${tool.filePath} (${tool.fileContent.length} bytes)`;
|
|
50511
|
+
} else {
|
|
50512
|
+
icon = "?";
|
|
50513
|
+
label = "search";
|
|
50514
|
+
value = tool.query ?? "";
|
|
50515
|
+
}
|
|
50536
50516
|
}
|
|
50537
50517
|
return /* @__PURE__ */ jsx_dev_runtime20.jsxDEV(Box_default, {
|
|
50538
50518
|
flexDirection: "column",
|
|
@@ -52389,8 +52369,8 @@ function appendMemory(entry) {
|
|
|
52389
52369
|
}
|
|
52390
52370
|
function buildMemorySummary(repoPath) {
|
|
52391
52371
|
const m = loadMemoryFile();
|
|
52392
|
-
const relevant = m.entries.
|
|
52393
|
-
const memories = m.memories
|
|
52372
|
+
const relevant = m.entries.slice(-50);
|
|
52373
|
+
const memories = m.memories;
|
|
52394
52374
|
const parts = [];
|
|
52395
52375
|
if (memories.length > 0) {
|
|
52396
52376
|
parts.push(`## MEMORIES ABOUT THIS REPO
|
|
@@ -52416,8 +52396,8 @@ ${lines.join(`
|
|
|
52416
52396
|
}
|
|
52417
52397
|
function clearRepoMemory(repoPath) {
|
|
52418
52398
|
const m = loadMemoryFile();
|
|
52419
|
-
m.entries = m.entries
|
|
52420
|
-
m.memories = m.memories
|
|
52399
|
+
m.entries = m.entries = [];
|
|
52400
|
+
m.memories = m.memories = [];
|
|
52421
52401
|
saveMemoryFile(m);
|
|
52422
52402
|
}
|
|
52423
52403
|
function generateId() {
|
|
@@ -52428,8 +52408,7 @@ function addMemory(content, repoPath) {
|
|
|
52428
52408
|
const memory = {
|
|
52429
52409
|
id: generateId(),
|
|
52430
52410
|
content,
|
|
52431
|
-
timestamp: new Date().toISOString()
|
|
52432
|
-
repoPath
|
|
52411
|
+
timestamp: new Date().toISOString()
|
|
52433
52412
|
};
|
|
52434
52413
|
m.memories.push(memory);
|
|
52435
52414
|
saveMemoryFile(m);
|
|
@@ -52438,14 +52417,14 @@ function addMemory(content, repoPath) {
|
|
|
52438
52417
|
function deleteMemory(id, repoPath) {
|
|
52439
52418
|
const m = loadMemoryFile();
|
|
52440
52419
|
const before2 = m.memories.length;
|
|
52441
|
-
m.memories = m.memories.filter((mem) => !(mem.id === id
|
|
52420
|
+
m.memories = m.memories.filter((mem) => !(mem.id === id));
|
|
52442
52421
|
if (m.memories.length === before2)
|
|
52443
52422
|
return false;
|
|
52444
52423
|
saveMemoryFile(m);
|
|
52445
52424
|
return true;
|
|
52446
52425
|
}
|
|
52447
52426
|
function listMemories(repoPath) {
|
|
52448
|
-
return loadMemoryFile().memories
|
|
52427
|
+
return loadMemoryFile().memories;
|
|
52449
52428
|
}
|
|
52450
52429
|
|
|
52451
52430
|
// src/components/chat/ChatRunner.tsx
|
|
@@ -52555,8 +52534,7 @@ var ChatRunner = ({ repoPath }) => {
|
|
|
52555
52534
|
};
|
|
52556
52535
|
const abortControllerRef = import_react49.useRef(null);
|
|
52557
52536
|
const toolResultCache = import_react49.useRef(new Map);
|
|
52558
|
-
const
|
|
52559
|
-
const flushTimer = import_react49.useRef(null);
|
|
52537
|
+
const batchApprovedRef = import_react49.useRef(false);
|
|
52560
52538
|
const thinkingPhrase = useThinkingPhrase(stage.type === "thinking");
|
|
52561
52539
|
import_react48.default.useEffect(() => {
|
|
52562
52540
|
const chats = listChats(repoPath);
|
|
@@ -52567,22 +52545,8 @@ var ChatRunner = ({ repoPath }) => {
|
|
|
52567
52545
|
saveChat(chatNameRef.current, repoPath, allMessages);
|
|
52568
52546
|
}
|
|
52569
52547
|
}, [allMessages]);
|
|
52570
|
-
const flushBuffer = () => {
|
|
52571
|
-
const buf = inputBuffer.current;
|
|
52572
|
-
if (!buf)
|
|
52573
|
-
return;
|
|
52574
|
-
inputBuffer.current = "";
|
|
52575
|
-
setInputValue((v) => v + buf);
|
|
52576
|
-
};
|
|
52577
|
-
const scheduleFlush = () => {
|
|
52578
|
-
if (flushTimer.current !== null)
|
|
52579
|
-
return;
|
|
52580
|
-
flushTimer.current = setTimeout(() => {
|
|
52581
|
-
flushTimer.current = null;
|
|
52582
|
-
flushBuffer();
|
|
52583
|
-
}, 16);
|
|
52584
|
-
};
|
|
52585
52548
|
const handleError = (currentAll) => (err) => {
|
|
52549
|
+
batchApprovedRef.current = false;
|
|
52586
52550
|
if (err instanceof Error && err.name === "AbortError") {
|
|
52587
52551
|
setStage({ type: "idle" });
|
|
52588
52552
|
return;
|
|
@@ -52598,6 +52562,7 @@ var ChatRunner = ({ repoPath }) => {
|
|
|
52598
52562
|
};
|
|
52599
52563
|
const processResponse = (raw, currentAll, signal) => {
|
|
52600
52564
|
if (signal.aborted) {
|
|
52565
|
+
batchApprovedRef.current = false;
|
|
52601
52566
|
setStage({ type: "idle" });
|
|
52602
52567
|
return;
|
|
52603
52568
|
}
|
|
@@ -52620,14 +52585,15 @@ var ChatRunner = ({ repoPath }) => {
|
|
|
52620
52585
|
const cleanRaw = raw.replace(/<memory-add>[\s\S]*?<\/memory-add>/g, "").replace(/<memory-delete>[\s\S]*?<\/memory-delete>/g, "").trim();
|
|
52621
52586
|
const parsed = parseResponse(cleanRaw);
|
|
52622
52587
|
if (parsed.kind === "changes") {
|
|
52588
|
+
batchApprovedRef.current = false;
|
|
52623
52589
|
if (parsed.patches.length === 0) {
|
|
52624
|
-
const
|
|
52590
|
+
const msg = {
|
|
52625
52591
|
role: "assistant",
|
|
52626
52592
|
content: parsed.content,
|
|
52627
52593
|
type: "text"
|
|
52628
52594
|
};
|
|
52629
|
-
setAllMessages([...currentAll,
|
|
52630
|
-
setCommitted((prev) => [...prev,
|
|
52595
|
+
setAllMessages([...currentAll, msg]);
|
|
52596
|
+
setCommitted((prev) => [...prev, msg]);
|
|
52631
52597
|
setStage({ type: "idle" });
|
|
52632
52598
|
return;
|
|
52633
52599
|
}
|
|
@@ -52651,144 +52617,8 @@ var ChatRunner = ({ repoPath }) => {
|
|
|
52651
52617
|
});
|
|
52652
52618
|
return;
|
|
52653
52619
|
}
|
|
52654
|
-
if (parsed.kind === "shell" || parsed.kind === "fetch" || parsed.kind === "read-file" || parsed.kind === "read-folder" || parsed.kind === "grep" || parsed.kind === "write-file" || parsed.kind === "delete-file" || parsed.kind === "delete-folder" || parsed.kind === "open-url" || parsed.kind === "generate-pdf" || parsed.kind === "search") {
|
|
52655
|
-
let tool;
|
|
52656
|
-
if (parsed.kind === "shell") {
|
|
52657
|
-
tool = { type: "shell", command: parsed.command };
|
|
52658
|
-
} else if (parsed.kind === "fetch") {
|
|
52659
|
-
tool = { type: "fetch", url: parsed.url };
|
|
52660
|
-
} else if (parsed.kind === "read-file") {
|
|
52661
|
-
tool = { type: "read-file", filePath: parsed.filePath };
|
|
52662
|
-
} else if (parsed.kind === "read-folder") {
|
|
52663
|
-
tool = { type: "read-folder", folderPath: parsed.folderPath };
|
|
52664
|
-
} else if (parsed.kind === "grep") {
|
|
52665
|
-
tool = { type: "grep", pattern: parsed.pattern, glob: parsed.glob };
|
|
52666
|
-
} else if (parsed.kind === "delete-file") {
|
|
52667
|
-
tool = { type: "delete-file", filePath: parsed.filePath };
|
|
52668
|
-
} else if (parsed.kind === "delete-folder") {
|
|
52669
|
-
tool = { type: "delete-folder", folderPath: parsed.folderPath };
|
|
52670
|
-
} else if (parsed.kind === "open-url") {
|
|
52671
|
-
tool = { type: "open-url", url: parsed.url };
|
|
52672
|
-
} else if (parsed.kind === "generate-pdf") {
|
|
52673
|
-
tool = {
|
|
52674
|
-
type: "generate-pdf",
|
|
52675
|
-
filePath: parsed.filePath,
|
|
52676
|
-
content: parsed.pdfContent
|
|
52677
|
-
};
|
|
52678
|
-
} else if (parsed.kind === "search") {
|
|
52679
|
-
tool = { type: "search", query: parsed.query };
|
|
52680
|
-
} else {
|
|
52681
|
-
tool = {
|
|
52682
|
-
type: "write-file",
|
|
52683
|
-
filePath: parsed.filePath,
|
|
52684
|
-
fileContent: parsed.fileContent
|
|
52685
|
-
};
|
|
52686
|
-
}
|
|
52687
|
-
if (parsed.content) {
|
|
52688
|
-
const preambleMsg = {
|
|
52689
|
-
role: "assistant",
|
|
52690
|
-
content: parsed.content,
|
|
52691
|
-
type: "text"
|
|
52692
|
-
};
|
|
52693
|
-
setAllMessages([...currentAll, preambleMsg]);
|
|
52694
|
-
setCommitted((prev) => [...prev, preambleMsg]);
|
|
52695
|
-
}
|
|
52696
|
-
const isSafeTool = parsed.kind === "read-file" || parsed.kind === "read-folder" || parsed.kind === "grep" || parsed.kind === "fetch" || parsed.kind === "open-url" || parsed.kind === "search";
|
|
52697
|
-
const executeAndContinue = async (approved) => {
|
|
52698
|
-
let result2 = "(denied by user)";
|
|
52699
|
-
if (approved) {
|
|
52700
|
-
const cacheKey = parsed.kind === "read-file" ? `read-file:${parsed.filePath}` : parsed.kind === "read-folder" ? `read-folder:${parsed.folderPath}` : parsed.kind === "grep" ? `grep:${parsed.pattern}:${parsed.glob}` : null;
|
|
52701
|
-
if (cacheKey && toolResultCache.current.has(cacheKey)) {
|
|
52702
|
-
result2 = toolResultCache.current.get(cacheKey) + `
|
|
52703
|
-
|
|
52704
|
-
[NOTE: This result was already retrieved earlier. Do not request it again.]`;
|
|
52705
|
-
} else {
|
|
52706
|
-
try {
|
|
52707
|
-
setStage({ type: "thinking" });
|
|
52708
|
-
if (parsed.kind === "shell") {
|
|
52709
|
-
result2 = await runShell(parsed.command, repoPath);
|
|
52710
|
-
} else if (parsed.kind === "fetch") {
|
|
52711
|
-
result2 = await fetchUrl(parsed.url);
|
|
52712
|
-
} else if (parsed.kind === "read-file") {
|
|
52713
|
-
result2 = readFile(parsed.filePath, repoPath);
|
|
52714
|
-
} else if (parsed.kind === "read-folder") {
|
|
52715
|
-
result2 = readFolder(parsed.folderPath, repoPath);
|
|
52716
|
-
} else if (parsed.kind === "grep") {
|
|
52717
|
-
result2 = grepFiles(parsed.pattern, parsed.glob, repoPath);
|
|
52718
|
-
} else if (parsed.kind === "delete-file") {
|
|
52719
|
-
result2 = deleteFile(parsed.filePath, repoPath);
|
|
52720
|
-
} else if (parsed.kind === "delete-folder") {
|
|
52721
|
-
result2 = deleteFolder(parsed.folderPath, repoPath);
|
|
52722
|
-
} else if (parsed.kind === "open-url") {
|
|
52723
|
-
result2 = openUrl(parsed.url);
|
|
52724
|
-
} else if (parsed.kind === "generate-pdf") {
|
|
52725
|
-
result2 = generatePdf(parsed.filePath, parsed.pdfContent, repoPath);
|
|
52726
|
-
} else if (parsed.kind === "write-file") {
|
|
52727
|
-
result2 = writeFile(parsed.filePath, parsed.fileContent, repoPath);
|
|
52728
|
-
} else if (parsed.kind === "search") {
|
|
52729
|
-
result2 = await searchWeb(parsed.query);
|
|
52730
|
-
}
|
|
52731
|
-
if (cacheKey) {
|
|
52732
|
-
toolResultCache.current.set(cacheKey, result2);
|
|
52733
|
-
}
|
|
52734
|
-
} catch (err) {
|
|
52735
|
-
result2 = `Error: ${err instanceof Error ? err.message : "failed"}`;
|
|
52736
|
-
}
|
|
52737
|
-
}
|
|
52738
|
-
}
|
|
52739
|
-
if (approved && !result2.startsWith("Error:")) {
|
|
52740
|
-
const kindMap2 = {
|
|
52741
|
-
shell: "shell-run",
|
|
52742
|
-
fetch: "url-fetched",
|
|
52743
|
-
"read-file": "file-read",
|
|
52744
|
-
"read-folder": "file-read",
|
|
52745
|
-
grep: "file-read",
|
|
52746
|
-
"delete-file": "file-written",
|
|
52747
|
-
"delete-folder": "file-written",
|
|
52748
|
-
"open-url": "url-fetched",
|
|
52749
|
-
"generate-pdf": "file-written",
|
|
52750
|
-
"write-file": "file-written",
|
|
52751
|
-
search: "url-fetched"
|
|
52752
|
-
};
|
|
52753
|
-
appendMemory({
|
|
52754
|
-
kind: kindMap2[parsed.kind] ?? "shell-run",
|
|
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,
|
|
52756
|
-
summary: result2.split(`
|
|
52757
|
-
`)[0]?.slice(0, 120) ?? "",
|
|
52758
|
-
repoPath
|
|
52759
|
-
});
|
|
52760
|
-
}
|
|
52761
|
-
const toolName = parsed.kind === "shell" ? "shell" : parsed.kind === "fetch" ? "fetch" : parsed.kind === "read-file" ? "read-file" : parsed.kind === "read-folder" ? "read-folder" : parsed.kind === "grep" ? "grep" : parsed.kind === "delete-file" ? "delete-file" : parsed.kind === "delete-folder" ? "delete-folder" : parsed.kind === "open-url" ? "open-url" : parsed.kind === "generate-pdf" ? "generate-pdf" : parsed.kind === "search" ? "search" : "write-file";
|
|
52762
|
-
const toolContent = parsed.kind === "shell" ? parsed.command : parsed.kind === "fetch" ? parsed.url : parsed.kind === "search" ? parsed.query : parsed.kind === "read-folder" ? parsed.folderPath : parsed.kind === "grep" ? `${parsed.pattern} — ${parsed.glob}` : parsed.kind === "delete-file" ? parsed.filePath : parsed.kind === "delete-folder" ? parsed.folderPath : parsed.kind === "open-url" ? parsed.url : parsed.kind === "generate-pdf" ? parsed.filePath : parsed.filePath;
|
|
52763
|
-
const toolMsg = {
|
|
52764
|
-
role: "assistant",
|
|
52765
|
-
type: "tool",
|
|
52766
|
-
toolName,
|
|
52767
|
-
content: toolContent,
|
|
52768
|
-
result: result2,
|
|
52769
|
-
approved
|
|
52770
|
-
};
|
|
52771
|
-
const withTool = [...currentAll, toolMsg];
|
|
52772
|
-
setAllMessages(withTool);
|
|
52773
|
-
setCommitted((prev) => [...prev, toolMsg]);
|
|
52774
|
-
const nextAbort = new AbortController;
|
|
52775
|
-
abortControllerRef.current = nextAbort;
|
|
52776
|
-
setStage({ type: "thinking" });
|
|
52777
|
-
callChat(provider, systemPrompt, withTool, nextAbort.signal).then((r) => processResponse(r, withTool, nextAbort.signal)).catch(handleError(withTool));
|
|
52778
|
-
};
|
|
52779
|
-
if (autoApprove && isSafeTool) {
|
|
52780
|
-
executeAndContinue(true);
|
|
52781
|
-
return;
|
|
52782
|
-
}
|
|
52783
|
-
setStage({
|
|
52784
|
-
type: "permission",
|
|
52785
|
-
tool,
|
|
52786
|
-
pendingMessages: currentAll,
|
|
52787
|
-
resolve: executeAndContinue
|
|
52788
|
-
});
|
|
52789
|
-
return;
|
|
52790
|
-
}
|
|
52791
52620
|
if (parsed.kind === "clone") {
|
|
52621
|
+
batchApprovedRef.current = false;
|
|
52792
52622
|
if (parsed.content) {
|
|
52793
52623
|
const preambleMsg = {
|
|
52794
52624
|
role: "assistant",
|
|
@@ -52805,23 +52635,116 @@ var ChatRunner = ({ repoPath }) => {
|
|
|
52805
52635
|
});
|
|
52806
52636
|
return;
|
|
52807
52637
|
}
|
|
52808
|
-
|
|
52809
|
-
|
|
52810
|
-
|
|
52811
|
-
|
|
52812
|
-
|
|
52813
|
-
|
|
52814
|
-
|
|
52815
|
-
|
|
52816
|
-
|
|
52817
|
-
|
|
52818
|
-
|
|
52819
|
-
|
|
52820
|
-
|
|
52821
|
-
|
|
52822
|
-
|
|
52638
|
+
if (parsed.kind === "text") {
|
|
52639
|
+
batchApprovedRef.current = false;
|
|
52640
|
+
const msg = {
|
|
52641
|
+
role: "assistant",
|
|
52642
|
+
content: parsed.content,
|
|
52643
|
+
type: "text"
|
|
52644
|
+
};
|
|
52645
|
+
const withMsg = [...currentAll, msg];
|
|
52646
|
+
setAllMessages(withMsg);
|
|
52647
|
+
setCommitted((prev) => [...prev, msg]);
|
|
52648
|
+
const lastUserMsg = [...currentAll].reverse().find((m) => m.role === "user");
|
|
52649
|
+
const githubUrl = lastUserMsg ? extractGithubUrl(lastUserMsg.content) : null;
|
|
52650
|
+
if (githubUrl && !clonedUrls.has(githubUrl)) {
|
|
52651
|
+
setTimeout(() => setStage({ type: "clone-offer", repoUrl: githubUrl }), 80);
|
|
52652
|
+
} else {
|
|
52653
|
+
setStage({ type: "idle" });
|
|
52654
|
+
}
|
|
52655
|
+
return;
|
|
52656
|
+
}
|
|
52657
|
+
const tool = registry.get(parsed.toolName);
|
|
52658
|
+
if (!tool) {
|
|
52659
|
+
batchApprovedRef.current = false;
|
|
52823
52660
|
setStage({ type: "idle" });
|
|
52661
|
+
return;
|
|
52662
|
+
}
|
|
52663
|
+
if (parsed.content) {
|
|
52664
|
+
const preambleMsg = {
|
|
52665
|
+
role: "assistant",
|
|
52666
|
+
content: parsed.content,
|
|
52667
|
+
type: "text"
|
|
52668
|
+
};
|
|
52669
|
+
setAllMessages([...currentAll, preambleMsg]);
|
|
52670
|
+
setCommitted((prev) => [...prev, preambleMsg]);
|
|
52824
52671
|
}
|
|
52672
|
+
const remainder = parsed.remainder;
|
|
52673
|
+
const isSafe = tool.safe ?? false;
|
|
52674
|
+
const executeAndContinue = async (approved) => {
|
|
52675
|
+
if (approved && remainder) {
|
|
52676
|
+
batchApprovedRef.current = true;
|
|
52677
|
+
}
|
|
52678
|
+
let result2 = "(denied by user)";
|
|
52679
|
+
if (approved) {
|
|
52680
|
+
const cacheKey = isSafe ? `${parsed.toolName}:${parsed.rawInput}` : null;
|
|
52681
|
+
if (cacheKey && toolResultCache.current.has(cacheKey)) {
|
|
52682
|
+
result2 = toolResultCache.current.get(cacheKey) + `
|
|
52683
|
+
|
|
52684
|
+
[NOTE: This result was already retrieved earlier. Do not request it again.]`;
|
|
52685
|
+
} else {
|
|
52686
|
+
try {
|
|
52687
|
+
setStage({ type: "thinking" });
|
|
52688
|
+
const toolResult = await tool.execute(parsed.input, {
|
|
52689
|
+
repoPath,
|
|
52690
|
+
messages: currentAll
|
|
52691
|
+
});
|
|
52692
|
+
result2 = toolResult.value;
|
|
52693
|
+
if (cacheKey && toolResult.kind === "text") {
|
|
52694
|
+
toolResultCache.current.set(cacheKey, result2);
|
|
52695
|
+
}
|
|
52696
|
+
} catch (err) {
|
|
52697
|
+
result2 = `Error: ${err instanceof Error ? err.message : "failed"}`;
|
|
52698
|
+
}
|
|
52699
|
+
}
|
|
52700
|
+
}
|
|
52701
|
+
if (approved && !result2.startsWith("Error:")) {
|
|
52702
|
+
appendMemory({
|
|
52703
|
+
kind: "shell-run",
|
|
52704
|
+
detail: tool.summariseInput ? String(tool.summariseInput(parsed.input)) : parsed.rawInput,
|
|
52705
|
+
summary: result2.split(`
|
|
52706
|
+
`)[0]?.slice(0, 120) ?? "",
|
|
52707
|
+
repoPath
|
|
52708
|
+
});
|
|
52709
|
+
}
|
|
52710
|
+
const displayContent = tool.summariseInput ? String(tool.summariseInput(parsed.input)) : parsed.rawInput;
|
|
52711
|
+
const toolMsg = {
|
|
52712
|
+
role: "assistant",
|
|
52713
|
+
type: "tool",
|
|
52714
|
+
toolName: parsed.toolName,
|
|
52715
|
+
content: displayContent,
|
|
52716
|
+
result: result2,
|
|
52717
|
+
approved
|
|
52718
|
+
};
|
|
52719
|
+
const withTool = [...currentAll, toolMsg];
|
|
52720
|
+
setAllMessages(withTool);
|
|
52721
|
+
setCommitted((prev) => [...prev, toolMsg]);
|
|
52722
|
+
if (approved && remainder && remainder.length > 0) {
|
|
52723
|
+
processResponse(remainder, withTool, signal);
|
|
52724
|
+
return;
|
|
52725
|
+
}
|
|
52726
|
+
batchApprovedRef.current = false;
|
|
52727
|
+
const nextAbort = new AbortController;
|
|
52728
|
+
abortControllerRef.current = nextAbort;
|
|
52729
|
+
setStage({ type: "thinking" });
|
|
52730
|
+
callChat(provider, systemPrompt, withTool, nextAbort.signal).then((r) => processResponse(r, withTool, nextAbort.signal)).catch(handleError(withTool));
|
|
52731
|
+
};
|
|
52732
|
+
if (autoApprove && isSafe || batchApprovedRef.current) {
|
|
52733
|
+
executeAndContinue(true);
|
|
52734
|
+
return;
|
|
52735
|
+
}
|
|
52736
|
+
const permLabel = tool.permissionLabel ?? tool.name;
|
|
52737
|
+
const permValue = tool.summariseInput ? String(tool.summariseInput(parsed.input)) : parsed.rawInput;
|
|
52738
|
+
setStage({
|
|
52739
|
+
type: "permission",
|
|
52740
|
+
tool: {
|
|
52741
|
+
type: parsed.toolName,
|
|
52742
|
+
_display: permValue,
|
|
52743
|
+
_label: permLabel
|
|
52744
|
+
},
|
|
52745
|
+
pendingMessages: currentAll,
|
|
52746
|
+
resolve: executeAndContinue
|
|
52747
|
+
});
|
|
52825
52748
|
};
|
|
52826
52749
|
const sendMessage = (text) => {
|
|
52827
52750
|
if (!provider)
|
|
@@ -52839,7 +52762,7 @@ var ChatRunner = ({ repoPath }) => {
|
|
|
52839
52762
|
setAutoApprove(next);
|
|
52840
52763
|
const msg = {
|
|
52841
52764
|
role: "assistant",
|
|
52842
|
-
content: next ? "Auto-approve ON — read, search,
|
|
52765
|
+
content: next ? "Auto-approve ON — safe tools (read, search, fetch) will run without asking." : "Auto-approve OFF — all tools will ask for permission.",
|
|
52843
52766
|
type: "text"
|
|
52844
52767
|
};
|
|
52845
52768
|
setCommitted((prev) => [...prev, msg]);
|
|
@@ -52848,13 +52771,13 @@ var ChatRunner = ({ repoPath }) => {
|
|
|
52848
52771
|
}
|
|
52849
52772
|
if (text.trim().toLowerCase() === "/clear history") {
|
|
52850
52773
|
clearRepoMemory(repoPath);
|
|
52851
|
-
const
|
|
52774
|
+
const msg = {
|
|
52852
52775
|
role: "assistant",
|
|
52853
52776
|
content: "History cleared for this repo.",
|
|
52854
52777
|
type: "text"
|
|
52855
52778
|
};
|
|
52856
|
-
setCommitted((prev) => [...prev,
|
|
52857
|
-
setAllMessages((prev) => [...prev,
|
|
52779
|
+
setCommitted((prev) => [...prev, msg]);
|
|
52780
|
+
setAllMessages((prev) => [...prev, msg]);
|
|
52858
52781
|
return;
|
|
52859
52782
|
}
|
|
52860
52783
|
if (text.trim().toLowerCase() === "/chat") {
|
|
@@ -52901,7 +52824,7 @@ var ChatRunner = ({ repoPath }) => {
|
|
|
52901
52824
|
if (!name) {
|
|
52902
52825
|
const msg2 = {
|
|
52903
52826
|
role: "assistant",
|
|
52904
|
-
content: "Usage: `/chat delete <
|
|
52827
|
+
content: "Usage: `/chat delete <n>`",
|
|
52905
52828
|
type: "text"
|
|
52906
52829
|
};
|
|
52907
52830
|
setCommitted((prev) => [...prev, msg2]);
|
|
@@ -53052,6 +52975,7 @@ ${mems.map((m) => `- [${m.id}] ${m.content}`).join(`
|
|
|
53052
52975
|
setCommitted((prev) => [...prev, userMsg]);
|
|
53053
52976
|
setAllMessages(nextAll);
|
|
53054
52977
|
toolResultCache.current.clear();
|
|
52978
|
+
batchApprovedRef.current = false;
|
|
53055
52979
|
inputHistoryRef.current = [
|
|
53056
52980
|
text,
|
|
53057
52981
|
...inputHistoryRef.current.filter((m) => m !== text)
|
|
@@ -53074,6 +52998,7 @@ ${mems.map((m) => `- [${m.id}] ${m.content}`).join(`
|
|
|
53074
52998
|
if (stage.type === "thinking" && key.escape) {
|
|
53075
52999
|
abortControllerRef.current?.abort();
|
|
53076
53000
|
abortControllerRef.current = null;
|
|
53001
|
+
batchApprovedRef.current = false;
|
|
53077
53002
|
setStage({ type: "idle" });
|
|
53078
53003
|
return;
|
|
53079
53004
|
}
|
|
@@ -53092,8 +53017,7 @@ ${mems.map((m) => `- [${m.id}] ${m.content}`).join(`
|
|
|
53092
53017
|
if (key.downArrow) {
|
|
53093
53018
|
const next = historyIndexRef.current - 1;
|
|
53094
53019
|
historyIndexRef.current = next;
|
|
53095
|
-
|
|
53096
|
-
setInputValue(val);
|
|
53020
|
+
setInputValue(next < 0 ? "" : inputHistoryRef.current[next]);
|
|
53097
53021
|
setInputKey((k) => k + 1);
|
|
53098
53022
|
return;
|
|
53099
53023
|
}
|
|
@@ -53153,16 +53077,14 @@ ${mems.map((m) => `- [${m.id}] ${m.content}`).join(`
|
|
|
53153
53077
|
if (stage.type === "clone-exists") {
|
|
53154
53078
|
if (input === "y" || input === "Y") {
|
|
53155
53079
|
const { repoUrl, repoPath: existingPath } = stage;
|
|
53156
|
-
const cloneUrl = toCloneUrl(repoUrl);
|
|
53157
53080
|
setStage({ type: "cloning", repoUrl });
|
|
53158
|
-
startCloneRepo(
|
|
53081
|
+
startCloneRepo(toCloneUrl(repoUrl), { forceReclone: true }).then((result2) => {
|
|
53159
53082
|
if (result2.done) {
|
|
53160
|
-
const fileCount = walkDir3(existingPath).length;
|
|
53161
53083
|
setStage({
|
|
53162
53084
|
type: "clone-done",
|
|
53163
53085
|
repoUrl,
|
|
53164
53086
|
destPath: existingPath,
|
|
53165
|
-
fileCount
|
|
53087
|
+
fileCount: walkDir3(existingPath).length
|
|
53166
53088
|
});
|
|
53167
53089
|
} else {
|
|
53168
53090
|
setStage({
|
|
@@ -53175,12 +53097,11 @@ ${mems.map((m) => `- [${m.id}] ${m.content}`).join(`
|
|
|
53175
53097
|
}
|
|
53176
53098
|
if (input === "n" || input === "N") {
|
|
53177
53099
|
const { repoUrl, repoPath: existingPath } = stage;
|
|
53178
|
-
const fileCount = walkDir3(existingPath).length;
|
|
53179
53100
|
setStage({
|
|
53180
53101
|
type: "clone-done",
|
|
53181
53102
|
repoUrl,
|
|
53182
53103
|
destPath: existingPath,
|
|
53183
|
-
fileCount
|
|
53104
|
+
fileCount: walkDir3(existingPath).length
|
|
53184
53105
|
});
|
|
53185
53106
|
return;
|
|
53186
53107
|
}
|
|
@@ -53202,7 +53123,7 @@ Ask me anything about it — I can read files, explain how it works, or suggest
|
|
|
53202
53123
|
type: "tool",
|
|
53203
53124
|
toolName: "fetch",
|
|
53204
53125
|
content: stage.repoUrl,
|
|
53205
|
-
result: `Clone complete. Repo: ${repoName}. Local path: ${stage.destPath}. ${stage.fileCount} files
|
|
53126
|
+
result: `Clone complete. Repo: ${repoName}. Local path: ${stage.destPath}. ${stage.fileCount} files.`,
|
|
53206
53127
|
approved: true
|
|
53207
53128
|
};
|
|
53208
53129
|
const withClone = [...allMessages, contextMsg, summaryMsg];
|
|
@@ -53223,6 +53144,7 @@ Ask me anything about it — I can read files, explain how it works, or suggest
|
|
|
53223
53144
|
return;
|
|
53224
53145
|
}
|
|
53225
53146
|
if (input === "n" || input === "N" || key.escape) {
|
|
53147
|
+
batchApprovedRef.current = false;
|
|
53226
53148
|
stage.resolve(false);
|
|
53227
53149
|
return;
|
|
53228
53150
|
}
|
|
@@ -53312,17 +53234,16 @@ ${lensFile.overview}
|
|
|
53312
53234
|
|
|
53313
53235
|
Important folders: ${lensFile.importantFolders.join(", ")}
|
|
53314
53236
|
Suggestions: ${lensFile.suggestions.slice(0, 3).join("; ")}` : "";
|
|
53315
|
-
|
|
53316
|
-
|
|
53317
|
-
|
|
53318
|
-
I have memory of previous actions in this repo.` : "";
|
|
53319
|
-
const lensGreetNote = lensFile ? `
|
|
53320
|
-
|
|
53321
|
-
Found LENS.md — I have context from a previous analysis of this repo.` : "";
|
|
53237
|
+
const toolsSection = registry.buildSystemPromptSection();
|
|
53238
|
+
setSystemPrompt(buildSystemPrompt(importantFiles, historySummary, toolsSection) + lensContext);
|
|
53322
53239
|
const greeting = {
|
|
53323
53240
|
role: "assistant",
|
|
53324
|
-
content: `Welcome to Lens
|
|
53325
|
-
Codebase loaded — ${importantFiles.length} files indexed.${
|
|
53241
|
+
content: `Welcome to Lens
|
|
53242
|
+
Codebase loaded — ${importantFiles.length} files indexed.${historySummary ? `
|
|
53243
|
+
|
|
53244
|
+
I have memory of previous actions in this repo.` : ""}${lensFile ? `
|
|
53245
|
+
|
|
53246
|
+
Found LENS.md — I have context from a previous analysis of this repo.` : ""}
|
|
53326
53247
|
Ask me anything, tell me what to build, share a URL, or ask me to read/write files.
|
|
53327
53248
|
|
|
53328
53249
|
Tip: type /timeline to browse commit history.`,
|
|
@@ -53337,7 +53258,7 @@ Tip: type /timeline to browse commit history.`,
|
|
|
53337
53258
|
return /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(ProviderPicker, {
|
|
53338
53259
|
onDone: handleProviderDone
|
|
53339
53260
|
}, undefined, false, undefined, this);
|
|
53340
|
-
if (stage.type === "loading")
|
|
53261
|
+
if (stage.type === "loading")
|
|
53341
53262
|
return /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(Box_default, {
|
|
53342
53263
|
gap: 1,
|
|
53343
53264
|
marginTop: 1,
|
|
@@ -53357,19 +53278,16 @@ Tip: type /timeline to browse commit history.`,
|
|
|
53357
53278
|
}, undefined, false, undefined, this)
|
|
53358
53279
|
]
|
|
53359
53280
|
}, undefined, true, undefined, this);
|
|
53360
|
-
|
|
53361
|
-
if (showTimeline) {
|
|
53281
|
+
if (showTimeline)
|
|
53362
53282
|
return /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(TimelineRunner, {
|
|
53363
53283
|
repoPath,
|
|
53364
53284
|
onExit: () => setShowTimeline(false)
|
|
53365
53285
|
}, undefined, false, undefined, this);
|
|
53366
|
-
|
|
53367
|
-
if (showReview) {
|
|
53286
|
+
if (showReview)
|
|
53368
53287
|
return /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(ReviewCommand, {
|
|
53369
53288
|
path: repoPath,
|
|
53370
53289
|
onExit: () => setShowReview(false)
|
|
53371
53290
|
}, undefined, false, undefined, this);
|
|
53372
|
-
}
|
|
53373
53291
|
if (stage.type === "clone-offer")
|
|
53374
53292
|
return /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(CloneOfferView, {
|
|
53375
53293
|
stage,
|
|
@@ -53510,9 +53428,282 @@ var TimelineCommand = ({ path: inputPath }) => {
|
|
|
53510
53428
|
repoPath: resolvedPath
|
|
53511
53429
|
}, undefined, false, undefined, this);
|
|
53512
53430
|
};
|
|
53431
|
+
// src/utils/tools/builtins.ts
|
|
53432
|
+
var fetchTool = {
|
|
53433
|
+
name: "fetch",
|
|
53434
|
+
description: "load a URL",
|
|
53435
|
+
safe: true,
|
|
53436
|
+
permissionLabel: "fetch",
|
|
53437
|
+
systemPromptEntry: (i) => `### ${i}. fetch — load a URL
|
|
53438
|
+
<fetch>https://example.com</fetch>`,
|
|
53439
|
+
parseInput: (body) => body.replace(/^<|>$/g, "").trim() || null,
|
|
53440
|
+
summariseInput: (url) => url,
|
|
53441
|
+
execute: async (url) => {
|
|
53442
|
+
try {
|
|
53443
|
+
const value = await fetchUrl(url);
|
|
53444
|
+
return { kind: "text", value };
|
|
53445
|
+
} catch (err) {
|
|
53446
|
+
return {
|
|
53447
|
+
kind: "error",
|
|
53448
|
+
value: `Fetch failed: ${err instanceof Error ? err.message : String(err)}`
|
|
53449
|
+
};
|
|
53450
|
+
}
|
|
53451
|
+
}
|
|
53452
|
+
};
|
|
53453
|
+
var shellTool = {
|
|
53454
|
+
name: "shell",
|
|
53455
|
+
description: "run a terminal command",
|
|
53456
|
+
safe: false,
|
|
53457
|
+
permissionLabel: "run",
|
|
53458
|
+
systemPromptEntry: (i) => `### ${i}. shell — run a terminal command
|
|
53459
|
+
<shell>node -v</shell>`,
|
|
53460
|
+
parseInput: (body) => body || null,
|
|
53461
|
+
summariseInput: (cmd) => cmd,
|
|
53462
|
+
execute: async (cmd, ctx) => {
|
|
53463
|
+
const value = await runShell(cmd, ctx.repoPath);
|
|
53464
|
+
return { kind: "text", value };
|
|
53465
|
+
}
|
|
53466
|
+
};
|
|
53467
|
+
var readFileTool = {
|
|
53468
|
+
name: "read-file",
|
|
53469
|
+
description: "read a file from the repo",
|
|
53470
|
+
safe: true,
|
|
53471
|
+
permissionLabel: "read",
|
|
53472
|
+
systemPromptEntry: (i) => `### ${i}. read-file — read a file from the repo
|
|
53473
|
+
<read-file>src/foo.ts</read-file>`,
|
|
53474
|
+
parseInput: (body) => body || null,
|
|
53475
|
+
summariseInput: (p) => p,
|
|
53476
|
+
execute: (filePath, ctx) => ({
|
|
53477
|
+
kind: "text",
|
|
53478
|
+
value: readFile(filePath, ctx.repoPath)
|
|
53479
|
+
})
|
|
53480
|
+
};
|
|
53481
|
+
var readFolderTool = {
|
|
53482
|
+
name: "read-folder",
|
|
53483
|
+
description: "list contents of a folder (files + subfolders, one level deep)",
|
|
53484
|
+
safe: true,
|
|
53485
|
+
permissionLabel: "folder",
|
|
53486
|
+
systemPromptEntry: (i) => `### ${i}. read-folder — list contents of a folder (files + subfolders, one level deep)
|
|
53487
|
+
<read-folder>src/components</read-folder>`,
|
|
53488
|
+
parseInput: (body) => body || null,
|
|
53489
|
+
summariseInput: (p) => p,
|
|
53490
|
+
execute: (folderPath, ctx) => ({
|
|
53491
|
+
kind: "text",
|
|
53492
|
+
value: readFolder(folderPath, ctx.repoPath)
|
|
53493
|
+
})
|
|
53494
|
+
};
|
|
53495
|
+
var grepTool = {
|
|
53496
|
+
name: "grep",
|
|
53497
|
+
description: "search for a pattern across files in the repo",
|
|
53498
|
+
safe: true,
|
|
53499
|
+
permissionLabel: "grep",
|
|
53500
|
+
systemPromptEntry: (i) => `### ${i}. grep — search for a pattern across files in the repo (cross-platform, no shell needed)
|
|
53501
|
+
<grep>
|
|
53502
|
+
{"pattern": "ChatRunner", "glob": "src/**/*.tsx"}
|
|
53503
|
+
</grep>`,
|
|
53504
|
+
parseInput: (body) => {
|
|
53505
|
+
try {
|
|
53506
|
+
const parsed = JSON.parse(body);
|
|
53507
|
+
return { pattern: parsed.pattern, glob: parsed.glob ?? "**/*" };
|
|
53508
|
+
} catch {
|
|
53509
|
+
return { pattern: body, glob: "**/*" };
|
|
53510
|
+
}
|
|
53511
|
+
},
|
|
53512
|
+
summariseInput: ({ pattern, glob }) => `${pattern} — ${glob}`,
|
|
53513
|
+
execute: ({ pattern, glob }, ctx) => ({
|
|
53514
|
+
kind: "text",
|
|
53515
|
+
value: grepFiles(pattern, glob, ctx.repoPath)
|
|
53516
|
+
})
|
|
53517
|
+
};
|
|
53518
|
+
var writeFileTool = {
|
|
53519
|
+
name: "write-file",
|
|
53520
|
+
description: "create or overwrite a file",
|
|
53521
|
+
safe: false,
|
|
53522
|
+
permissionLabel: "write",
|
|
53523
|
+
systemPromptEntry: (i) => `### ${i}. write-file — create or overwrite a file
|
|
53524
|
+
<write-file>
|
|
53525
|
+
{"path": "data/output.csv", "content": "col1,col2\\nval1,val2"}
|
|
53526
|
+
</write-file>`,
|
|
53527
|
+
parseInput: (body) => {
|
|
53528
|
+
try {
|
|
53529
|
+
const parsed = JSON.parse(body);
|
|
53530
|
+
if (!parsed.path)
|
|
53531
|
+
return null;
|
|
53532
|
+
return parsed;
|
|
53533
|
+
} catch {
|
|
53534
|
+
return null;
|
|
53535
|
+
}
|
|
53536
|
+
},
|
|
53537
|
+
summariseInput: ({ path: path21, content }) => `${path21} (${content.length} bytes)`,
|
|
53538
|
+
execute: ({ path: filePath, content }, ctx) => ({
|
|
53539
|
+
kind: "text",
|
|
53540
|
+
value: writeFile(filePath, content, ctx.repoPath)
|
|
53541
|
+
})
|
|
53542
|
+
};
|
|
53543
|
+
var deleteFileTool = {
|
|
53544
|
+
name: "delete-file",
|
|
53545
|
+
description: "permanently delete a single file",
|
|
53546
|
+
safe: false,
|
|
53547
|
+
permissionLabel: "delete",
|
|
53548
|
+
systemPromptEntry: (i) => `### ${i}. delete-file — permanently delete a single file
|
|
53549
|
+
<delete-file>src/old-component.tsx</delete-file>`,
|
|
53550
|
+
parseInput: (body) => body || null,
|
|
53551
|
+
summariseInput: (p) => p,
|
|
53552
|
+
execute: (filePath, ctx) => ({
|
|
53553
|
+
kind: "text",
|
|
53554
|
+
value: deleteFile(filePath, ctx.repoPath)
|
|
53555
|
+
})
|
|
53556
|
+
};
|
|
53557
|
+
var deleteFolderTool = {
|
|
53558
|
+
name: "delete-folder",
|
|
53559
|
+
description: "permanently delete a folder and all its contents",
|
|
53560
|
+
safe: false,
|
|
53561
|
+
permissionLabel: "delete folder",
|
|
53562
|
+
systemPromptEntry: (i) => `### ${i}. delete-folder — permanently delete a folder and all its contents
|
|
53563
|
+
<delete-folder>src/legacy</delete-folder>`,
|
|
53564
|
+
parseInput: (body) => body || null,
|
|
53565
|
+
summariseInput: (p) => p,
|
|
53566
|
+
execute: (folderPath, ctx) => ({
|
|
53567
|
+
kind: "text",
|
|
53568
|
+
value: deleteFolder(folderPath, ctx.repoPath)
|
|
53569
|
+
})
|
|
53570
|
+
};
|
|
53571
|
+
var openUrlTool = {
|
|
53572
|
+
name: "open-url",
|
|
53573
|
+
description: "open a URL in the user's default browser",
|
|
53574
|
+
safe: true,
|
|
53575
|
+
permissionLabel: "open",
|
|
53576
|
+
systemPromptEntry: (i) => `### ${i}. open-url — open a URL in the user's default browser
|
|
53577
|
+
<open-url>https://github.com/owner/repo</open-url>`,
|
|
53578
|
+
parseInput: (body) => body.replace(/^<|>$/g, "").trim() || null,
|
|
53579
|
+
summariseInput: (url) => url,
|
|
53580
|
+
execute: (url) => ({ kind: "text", value: openUrl(url) })
|
|
53581
|
+
};
|
|
53582
|
+
var generatePdfTool = {
|
|
53583
|
+
name: "generate-pdf",
|
|
53584
|
+
description: "generate a PDF file from markdown-style content",
|
|
53585
|
+
safe: false,
|
|
53586
|
+
permissionLabel: "pdf",
|
|
53587
|
+
systemPromptEntry: (i) => `### ${i}. generate-pdf — generate a PDF file from markdown-style content
|
|
53588
|
+
<generate-pdf>
|
|
53589
|
+
{"path": "output/report.pdf", "content": "# Title\\n\\nSome body text."}
|
|
53590
|
+
</generate-pdf>`,
|
|
53591
|
+
parseInput: (body) => {
|
|
53592
|
+
try {
|
|
53593
|
+
const parsed = JSON.parse(body);
|
|
53594
|
+
return {
|
|
53595
|
+
filePath: parsed.path ?? parsed.filePath ?? "output.pdf",
|
|
53596
|
+
content: parsed.content ?? ""
|
|
53597
|
+
};
|
|
53598
|
+
} catch {
|
|
53599
|
+
return null;
|
|
53600
|
+
}
|
|
53601
|
+
},
|
|
53602
|
+
summariseInput: ({ filePath }) => filePath,
|
|
53603
|
+
execute: ({ filePath, content }, ctx) => ({
|
|
53604
|
+
kind: "text",
|
|
53605
|
+
value: generatePdf(filePath, content, ctx.repoPath)
|
|
53606
|
+
})
|
|
53607
|
+
};
|
|
53608
|
+
var searchTool = {
|
|
53609
|
+
name: "search",
|
|
53610
|
+
description: "search the internet for anything you are unsure about",
|
|
53611
|
+
safe: true,
|
|
53612
|
+
permissionLabel: "search",
|
|
53613
|
+
systemPromptEntry: (i) => `### ${i}. search — search the internet for anything you are unsure about
|
|
53614
|
+
<search>how to use React useEffect cleanup function</search>`,
|
|
53615
|
+
parseInput: (body) => body || null,
|
|
53616
|
+
summariseInput: (q) => `"${q}"`,
|
|
53617
|
+
execute: async (query) => {
|
|
53618
|
+
try {
|
|
53619
|
+
const value = await searchWeb(query);
|
|
53620
|
+
return { kind: "text", value };
|
|
53621
|
+
} catch (err) {
|
|
53622
|
+
return {
|
|
53623
|
+
kind: "error",
|
|
53624
|
+
value: `Search failed: ${err instanceof Error ? err.message : String(err)}`
|
|
53625
|
+
};
|
|
53626
|
+
}
|
|
53627
|
+
}
|
|
53628
|
+
};
|
|
53629
|
+
var cloneTool = {
|
|
53630
|
+
name: "clone",
|
|
53631
|
+
description: "clone a GitHub repo so you can explore and discuss it",
|
|
53632
|
+
safe: false,
|
|
53633
|
+
permissionLabel: "clone",
|
|
53634
|
+
systemPromptEntry: (i) => `### ${i}. clone — clone a GitHub repo so you can explore and discuss it
|
|
53635
|
+
<clone>https://github.com/owner/repo</clone>`,
|
|
53636
|
+
parseInput: (body) => body.replace(/^<|>$/g, "").trim() || null,
|
|
53637
|
+
summariseInput: (url) => url,
|
|
53638
|
+
execute: (repoUrl) => ({
|
|
53639
|
+
kind: "text",
|
|
53640
|
+
value: `Clone of ${repoUrl} was handled by the UI.`
|
|
53641
|
+
})
|
|
53642
|
+
};
|
|
53643
|
+
var changesTool = {
|
|
53644
|
+
name: "changes",
|
|
53645
|
+
description: "propose code edits (shown as a diff for user approval)",
|
|
53646
|
+
safe: false,
|
|
53647
|
+
permissionLabel: "changes",
|
|
53648
|
+
systemPromptEntry: (i) => `### ${i}. changes — propose code edits (shown as a diff for user approval)
|
|
53649
|
+
<changes>
|
|
53650
|
+
{"summary": "what changed and why", "patches": [{"path": "src/foo.ts", "content": "COMPLETE file content", "isNew": false}]}
|
|
53651
|
+
</changes>`,
|
|
53652
|
+
parseInput: (body) => {
|
|
53653
|
+
try {
|
|
53654
|
+
return JSON.parse(body);
|
|
53655
|
+
} catch {
|
|
53656
|
+
return null;
|
|
53657
|
+
}
|
|
53658
|
+
},
|
|
53659
|
+
summariseInput: ({ summary }) => summary,
|
|
53660
|
+
execute: ({ summary }) => ({
|
|
53661
|
+
kind: "text",
|
|
53662
|
+
value: `Changes proposed: ${summary}`
|
|
53663
|
+
})
|
|
53664
|
+
};
|
|
53665
|
+
function registerBuiltins() {
|
|
53666
|
+
registry.register(fetchTool);
|
|
53667
|
+
registry.register(shellTool);
|
|
53668
|
+
registry.register(readFileTool);
|
|
53669
|
+
registry.register(readFolderTool);
|
|
53670
|
+
registry.register(grepTool);
|
|
53671
|
+
registry.register(writeFileTool);
|
|
53672
|
+
registry.register(deleteFileTool);
|
|
53673
|
+
registry.register(deleteFolderTool);
|
|
53674
|
+
registry.register(openUrlTool);
|
|
53675
|
+
registry.register(generatePdfTool);
|
|
53676
|
+
registry.register(searchTool);
|
|
53677
|
+
registry.register(cloneTool);
|
|
53678
|
+
registry.register(changesTool);
|
|
53679
|
+
}
|
|
53680
|
+
|
|
53681
|
+
// src/utils/addons/loadAddons.ts
|
|
53682
|
+
import path21 from "path";
|
|
53683
|
+
import os10 from "os";
|
|
53684
|
+
import { existsSync as existsSync16, readdirSync as readdirSync5 } from "fs";
|
|
53685
|
+
var ADDONS_DIR = path21.join(os10.homedir(), ".lens", "addons");
|
|
53686
|
+
async function loadAddons() {
|
|
53687
|
+
if (!existsSync16(ADDONS_DIR)) {
|
|
53688
|
+
return;
|
|
53689
|
+
}
|
|
53690
|
+
const files = readdirSync5(ADDONS_DIR).filter((f) => f.endsWith(".js") && !f.startsWith("_"));
|
|
53691
|
+
for (const file of files) {
|
|
53692
|
+
const fullPath = path21.join(ADDONS_DIR, file);
|
|
53693
|
+
try {
|
|
53694
|
+
await import(fullPath);
|
|
53695
|
+
console.log(`[addons] loaded: ${file}
|
|
53696
|
+
`);
|
|
53697
|
+
} catch (err) {
|
|
53698
|
+
console.error(`[addons] failed to load ${file}:`, err instanceof Error ? err.message : String(err));
|
|
53699
|
+
}
|
|
53700
|
+
}
|
|
53701
|
+
}
|
|
53513
53702
|
|
|
53514
53703
|
// src/index.tsx
|
|
53515
53704
|
var jsx_dev_runtime25 = __toESM(require_jsx_dev_runtime(), 1);
|
|
53705
|
+
registerBuiltins();
|
|
53706
|
+
await loadAddons();
|
|
53516
53707
|
var program2 = new Command;
|
|
53517
53708
|
program2.command("repo <url>").description("Analyze a remote repository").action((url) => {
|
|
53518
53709
|
render_default(/* @__PURE__ */ jsx_dev_runtime25.jsxDEV(RepoCommand, {
|