@ridit/lens 0.3.5 → 0.3.7
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/CLAUDE.md +50 -0
- package/dist/index.mjs +1484 -1058
- package/package.json +2 -2
- package/src/components/chat/ChatOverlays.tsx +12 -13
- package/src/components/chat/ChatRunner.tsx +2 -2
- package/src/components/chat/TextArea.tsx +176 -0
- package/src/components/chat/hooks/useChat.ts +160 -60
- package/src/components/repo/RepoAnalysis.tsx +2 -2
- package/src/components/timeline/TimelineRunner.tsx +2 -2
- package/src/components/watch/RunRunner.tsx +2 -1
- package/src/prompts/fewshot.ts +18 -0
- package/src/prompts/system.ts +157 -22
- package/src/tools/git.ts +26 -0
- package/src/utils/chat.ts +16 -4
- package/src/utils/intentClassifier.ts +58 -0
- package/src/utils/memory.ts +103 -26
- package/src/utils/tools/builtins.ts +46 -5
- package/src/utils/tools/registry.ts +65 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ridit/lens",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "Know Your Codebase.",
|
|
5
5
|
"author": "Ridit Jangra <riditjangra09@gmail.com> (https://ridit.space)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"prepublishOnly": "npm run build && npm run tag"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@ridit/lens-sdk": "0.
|
|
22
|
+
"@ridit/lens-sdk": "^0.2.0",
|
|
23
23
|
"asciichart": "^1.5.25",
|
|
24
24
|
"bun": "^1.3.11",
|
|
25
25
|
"commander": "^14.0.3",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Box, Static, Text } from "ink";
|
|
2
|
+
import { Box, Static, Text, useStdout } from "ink";
|
|
3
3
|
import Spinner from "ink-spinner";
|
|
4
|
-
import
|
|
4
|
+
import { TextArea } from "./TextArea";
|
|
5
5
|
import { ACCENT, GREEN, RED } from "../../colors";
|
|
6
6
|
import { DiffViewer } from "../repo/DiffViewer";
|
|
7
7
|
import { StaticMessage } from "./ChatMessage";
|
|
@@ -134,25 +134,24 @@ export function InputBox({
|
|
|
134
134
|
onSubmit: (v: string) => void;
|
|
135
135
|
inputKey?: number;
|
|
136
136
|
}) {
|
|
137
|
+
const { stdout } = useStdout();
|
|
138
|
+
const cols = stdout?.columns ?? 80;
|
|
139
|
+
const rule = "─".repeat(Math.max(1, cols));
|
|
140
|
+
|
|
137
141
|
return (
|
|
138
|
-
<Box
|
|
139
|
-
|
|
140
|
-
borderBottom
|
|
141
|
-
borderTop
|
|
142
|
-
borderRight={false}
|
|
143
|
-
borderLeft={false}
|
|
144
|
-
borderColor={"gray"}
|
|
145
|
-
borderStyle="single"
|
|
146
|
-
>
|
|
142
|
+
<Box flexDirection="column" marginTop={1}>
|
|
143
|
+
<Text color="gray" dimColor>{rule}</Text>
|
|
147
144
|
<Box gap={1}>
|
|
148
145
|
<Text color={ACCENT}>{">"}</Text>
|
|
149
|
-
<
|
|
146
|
+
<TextArea
|
|
150
147
|
key={inputKey}
|
|
151
148
|
value={value}
|
|
152
149
|
onChange={onChange}
|
|
153
150
|
onSubmit={onSubmit}
|
|
151
|
+
placeholder="ask anything..."
|
|
154
152
|
/>
|
|
155
153
|
</Box>
|
|
154
|
+
<Text color="gray" dimColor>{rule}</Text>
|
|
156
155
|
</Box>
|
|
157
156
|
);
|
|
158
157
|
}
|
|
@@ -196,7 +195,7 @@ export function ShortcutBar({
|
|
|
196
195
|
return (
|
|
197
196
|
<Box gap={3} marginTop={0}>
|
|
198
197
|
<Text color="gray" dimColor>
|
|
199
|
-
enter send · ^
|
|
198
|
+
enter send · alt+enter newline · ^w del word · ^c exit
|
|
200
199
|
</Text>
|
|
201
200
|
{forceApprove ? (
|
|
202
201
|
<Text color={RED}>⚡⚡ force-all</Text>
|
|
@@ -4,7 +4,7 @@ import Spinner from "ink-spinner";
|
|
|
4
4
|
import { useState } from "react";
|
|
5
5
|
import path from "path";
|
|
6
6
|
import os from "os";
|
|
7
|
-
import
|
|
7
|
+
import { TextArea } from "./TextArea";
|
|
8
8
|
import { ACCENT } from "../../colors";
|
|
9
9
|
import { ProviderPicker } from "../provider/ProviderPicker";
|
|
10
10
|
import { startCloneRepo } from "../../utils/repo";
|
|
@@ -136,7 +136,7 @@ function ForceAllWarning({
|
|
|
136
136
|
esc
|
|
137
137
|
</Text>
|
|
138
138
|
<Text color="gray"> to cancel: </Text>
|
|
139
|
-
<
|
|
139
|
+
<TextArea
|
|
140
140
|
value={input}
|
|
141
141
|
onChange={setInput}
|
|
142
142
|
onSubmit={(v) => onConfirm(v.trim().toLowerCase() === "yes")}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { Text, useInput } from "ink";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
|
|
5
|
+
function isWordChar(ch: string): boolean {
|
|
6
|
+
return /[\w]/.test(ch);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function wordBoundaryLeft(text: string, pos: number): number {
|
|
10
|
+
if (pos <= 0) return 0;
|
|
11
|
+
let i = pos - 1;
|
|
12
|
+
|
|
13
|
+
while (i > 0 && !isWordChar(text[i]!)) i--;
|
|
14
|
+
|
|
15
|
+
while (i > 0 && isWordChar(text[i - 1]!)) i--;
|
|
16
|
+
return i;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function wordBoundaryRight(text: string, pos: number): number {
|
|
20
|
+
const len = text.length;
|
|
21
|
+
if (pos >= len) return len;
|
|
22
|
+
let i = pos;
|
|
23
|
+
|
|
24
|
+
while (i < len && isWordChar(text[i]!)) i++;
|
|
25
|
+
|
|
26
|
+
while (i < len && !isWordChar(text[i]!)) i++;
|
|
27
|
+
return i;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface TextAreaProps {
|
|
31
|
+
value: string;
|
|
32
|
+
onChange: (value: string) => void;
|
|
33
|
+
onSubmit: (value: string) => void;
|
|
34
|
+
focus?: boolean;
|
|
35
|
+
placeholder?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function TextArea({
|
|
39
|
+
value,
|
|
40
|
+
onChange,
|
|
41
|
+
onSubmit,
|
|
42
|
+
focus = true,
|
|
43
|
+
placeholder = "",
|
|
44
|
+
}: TextAreaProps) {
|
|
45
|
+
const [cursor, setCursor] = useState(value.length);
|
|
46
|
+
const [prevValue, setPrevValue] = useState(value);
|
|
47
|
+
|
|
48
|
+
if (value !== prevValue) {
|
|
49
|
+
setPrevValue(value);
|
|
50
|
+
const lenDiff = Math.abs(value.length - prevValue.length);
|
|
51
|
+
if (cursor > value.length || lenDiff > 1) {
|
|
52
|
+
setCursor(value.length);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
useInput(
|
|
57
|
+
(input, key) => {
|
|
58
|
+
if (key.upArrow || key.downArrow) return;
|
|
59
|
+
if (key.tab || (key.shift && key.tab)) return;
|
|
60
|
+
if (key.ctrl && input === "c") return;
|
|
61
|
+
|
|
62
|
+
if (key.return && !key.meta) {
|
|
63
|
+
onSubmit(value);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if ((key.return && key.meta) || (key.ctrl && input === "j")) {
|
|
68
|
+
const next = value.slice(0, cursor) + "\n" + value.slice(cursor);
|
|
69
|
+
onChange(next);
|
|
70
|
+
setCursor((c) => c + 1);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (key.leftArrow && key.ctrl) {
|
|
75
|
+
setCursor(wordBoundaryLeft(value, cursor));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (key.rightArrow && key.ctrl) {
|
|
80
|
+
setCursor(wordBoundaryRight(value, cursor));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (key.leftArrow) {
|
|
85
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (key.rightArrow) {
|
|
90
|
+
setCursor((c) => Math.min(value.length, c + 1));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (key.ctrl && input === "a") {
|
|
95
|
+
const lineStart = value.lastIndexOf("\n", cursor - 1) + 1;
|
|
96
|
+
setCursor(lineStart);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (key.ctrl && input === "e") {
|
|
101
|
+
const lineEnd = value.indexOf("\n", cursor);
|
|
102
|
+
setCursor(lineEnd === -1 ? value.length : lineEnd);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (key.ctrl && input === "u") {
|
|
107
|
+
const lineStart = value.lastIndexOf("\n", cursor - 1) + 1;
|
|
108
|
+
onChange(value.slice(0, lineStart) + value.slice(cursor));
|
|
109
|
+
setCursor(lineStart);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (key.ctrl && input === "k") {
|
|
114
|
+
const lineEnd = value.indexOf("\n", cursor);
|
|
115
|
+
onChange(
|
|
116
|
+
value.slice(0, cursor) + (lineEnd === -1 ? "" : value.slice(lineEnd)),
|
|
117
|
+
);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (key.ctrl && input === "w") {
|
|
122
|
+
const to = wordBoundaryLeft(value, cursor);
|
|
123
|
+
onChange(value.slice(0, to) + value.slice(cursor));
|
|
124
|
+
setCursor(to);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (key.ctrl && key.delete) {
|
|
129
|
+
const to = wordBoundaryRight(value, cursor);
|
|
130
|
+
onChange(value.slice(0, cursor) + value.slice(to));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (key.backspace || key.delete) {
|
|
135
|
+
if (cursor > 0) {
|
|
136
|
+
onChange(value.slice(0, cursor - 1) + value.slice(cursor));
|
|
137
|
+
setCursor((c) => c - 1);
|
|
138
|
+
}
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (key.escape) return;
|
|
143
|
+
|
|
144
|
+
if (input) {
|
|
145
|
+
const next = value.slice(0, cursor) + input + value.slice(cursor);
|
|
146
|
+
onChange(next);
|
|
147
|
+
setCursor((c) => c + input.length);
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
{ isActive: focus },
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
if (value.length === 0 && placeholder) {
|
|
154
|
+
return (
|
|
155
|
+
<Text>
|
|
156
|
+
{chalk.inverse(placeholder[0] ?? " ")}
|
|
157
|
+
{placeholder.length > 1 ? chalk.gray(placeholder.slice(1)) : ""}
|
|
158
|
+
</Text>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let rendered = "";
|
|
163
|
+
for (let i = 0; i < value.length; i++) {
|
|
164
|
+
const ch = value[i]!;
|
|
165
|
+
if (i === cursor) {
|
|
166
|
+
rendered += ch === "\n" ? chalk.inverse(" ") + "\n" : chalk.inverse(ch);
|
|
167
|
+
} else {
|
|
168
|
+
rendered += ch;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (cursor === value.length) {
|
|
172
|
+
rendered += chalk.inverse(" ");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return <Text>{rendered}</Text>;
|
|
176
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState, useRef } from "react";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import type { Provider } from "../../../types/config";
|
|
4
|
+
import { classifyIntent } from "../../../utils/intentClassifier";
|
|
4
5
|
import type { Message, ChatStage } from "../../../types/chat";
|
|
5
6
|
import {
|
|
6
7
|
saveChat,
|
|
@@ -12,6 +13,8 @@ import {
|
|
|
12
13
|
buildMemorySummary,
|
|
13
14
|
addMemory,
|
|
14
15
|
deleteMemory,
|
|
16
|
+
getSessionToolSummary,
|
|
17
|
+
logToolCall,
|
|
15
18
|
} from "../../../utils/memory";
|
|
16
19
|
import { fetchFileTree, readImportantFiles } from "../../../utils/files";
|
|
17
20
|
import { readLensFile } from "../../../utils/lensfile";
|
|
@@ -25,6 +28,7 @@ import {
|
|
|
25
28
|
buildSystemPrompt,
|
|
26
29
|
parseResponse,
|
|
27
30
|
callChat,
|
|
31
|
+
type ChatResult,
|
|
28
32
|
} from "../../../utils/chat";
|
|
29
33
|
|
|
30
34
|
export function useChat(repoPath: string) {
|
|
@@ -89,32 +93,26 @@ export function useChat(repoPath: string) {
|
|
|
89
93
|
setStage({ type: "idle" });
|
|
90
94
|
};
|
|
91
95
|
|
|
92
|
-
const
|
|
93
|
-
"shell",
|
|
94
|
-
"fetch",
|
|
95
|
-
"read-file",
|
|
96
|
-
"read-folder",
|
|
97
|
-
"grep",
|
|
98
|
-
"write-file",
|
|
99
|
-
"delete-file",
|
|
100
|
-
"delete-folder",
|
|
101
|
-
"open-url",
|
|
102
|
-
"generate-pdf",
|
|
103
|
-
"search",
|
|
104
|
-
"clone",
|
|
105
|
-
"changes",
|
|
106
|
-
];
|
|
96
|
+
const MAX_AUTO_CONTINUES = 3;
|
|
107
97
|
|
|
108
98
|
function isLikelyTruncated(text: string): boolean {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
99
|
+
// Check unclosed XML tool tags (dynamic — includes addon tools)
|
|
100
|
+
for (const tag of registry.names()) {
|
|
101
|
+
if (text.includes(`<${tag}>`) && !text.includes(`</${tag}>`))
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
// Check unclosed fenced code blocks (```tool\n... without closing ```)
|
|
105
|
+
const fences = text.match(/```/g);
|
|
106
|
+
if (fences && fences.length % 2 !== 0) return true;
|
|
107
|
+
return false;
|
|
112
108
|
}
|
|
113
109
|
|
|
114
110
|
const processResponse = (
|
|
115
111
|
raw: string,
|
|
116
112
|
currentAll: Message[],
|
|
117
113
|
signal: AbortSignal,
|
|
114
|
+
truncated = false,
|
|
115
|
+
continueCount = 0,
|
|
118
116
|
) => {
|
|
119
117
|
if (signal.aborted) {
|
|
120
118
|
batchApprovedRef.current = false;
|
|
@@ -122,16 +120,73 @@ export function useChat(repoPath: string) {
|
|
|
122
120
|
return;
|
|
123
121
|
}
|
|
124
122
|
|
|
125
|
-
if (isLikelyTruncated(raw)) {
|
|
126
|
-
|
|
123
|
+
if (truncated || isLikelyTruncated(raw)) {
|
|
124
|
+
if (continueCount >= MAX_AUTO_CONTINUES) {
|
|
125
|
+
// Give up after max attempts — show whatever we have
|
|
126
|
+
batchApprovedRef.current = false;
|
|
127
|
+
const msg: Message = {
|
|
128
|
+
role: "assistant",
|
|
129
|
+
content:
|
|
130
|
+
raw.trim() ||
|
|
131
|
+
"(response was empty after multiple continuation attempts)",
|
|
132
|
+
type: "text",
|
|
133
|
+
};
|
|
134
|
+
setAllMessages([...currentAll, msg]);
|
|
135
|
+
setCommitted((prev) => [...prev, msg]);
|
|
136
|
+
setStage({ type: "idle" });
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Include the partial response so the model knows where it left off
|
|
141
|
+
const partialMsg: Message = {
|
|
127
142
|
role: "assistant",
|
|
143
|
+
content: raw,
|
|
144
|
+
type: "text",
|
|
145
|
+
};
|
|
146
|
+
const nudgeMsg: Message = {
|
|
147
|
+
role: "user",
|
|
128
148
|
content:
|
|
129
|
-
"
|
|
149
|
+
"Your response was cut off. Please continue exactly from where you left off.",
|
|
150
|
+
type: "text",
|
|
151
|
+
};
|
|
152
|
+
const withContext = [...currentAll, partialMsg, nudgeMsg];
|
|
153
|
+
|
|
154
|
+
const truncMsg: Message = {
|
|
155
|
+
role: "assistant",
|
|
156
|
+
content: `(response cut off — auto-continuing ${continueCount + 1}/${MAX_AUTO_CONTINUES}…)`,
|
|
130
157
|
type: "text",
|
|
131
158
|
};
|
|
132
159
|
setAllMessages([...currentAll, truncMsg]);
|
|
133
160
|
setCommitted((prev) => [...prev, truncMsg]);
|
|
134
|
-
|
|
161
|
+
|
|
162
|
+
const currentProvider = providerRef.current;
|
|
163
|
+
const currentSystemPrompt = systemPromptRef.current;
|
|
164
|
+
|
|
165
|
+
if (!currentProvider) {
|
|
166
|
+
setStage({ type: "idle" });
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const nextAbort = new AbortController();
|
|
171
|
+
abortControllerRef.current = nextAbort;
|
|
172
|
+
setStage({ type: "thinking" });
|
|
173
|
+
callChat(
|
|
174
|
+
currentProvider,
|
|
175
|
+
currentSystemPrompt,
|
|
176
|
+
withContext,
|
|
177
|
+
nextAbort.signal,
|
|
178
|
+
)
|
|
179
|
+
.then((result: ChatResult) => {
|
|
180
|
+
if (nextAbort.signal.aborted) return;
|
|
181
|
+
processResponse(
|
|
182
|
+
result.text ?? "",
|
|
183
|
+
withContext,
|
|
184
|
+
nextAbort.signal,
|
|
185
|
+
result.truncated,
|
|
186
|
+
continueCount + 1,
|
|
187
|
+
);
|
|
188
|
+
})
|
|
189
|
+
.catch(handleError(withContext));
|
|
135
190
|
return;
|
|
136
191
|
}
|
|
137
192
|
|
|
@@ -312,13 +367,14 @@ export function useChat(repoPath: string) {
|
|
|
312
367
|
}
|
|
313
368
|
|
|
314
369
|
if (approved && !result.startsWith("Error:")) {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
370
|
+
logToolCall(
|
|
371
|
+
parsed.toolName,
|
|
372
|
+
tool.summariseInput
|
|
318
373
|
? String(tool.summariseInput(parsed.input))
|
|
319
374
|
: parsed.rawInput,
|
|
320
|
-
|
|
321
|
-
|
|
375
|
+
result,
|
|
376
|
+
repoPath,
|
|
377
|
+
);
|
|
322
378
|
}
|
|
323
379
|
|
|
324
380
|
const displayContent = tool.summariseInput
|
|
@@ -339,7 +395,7 @@ export function useChat(repoPath: string) {
|
|
|
339
395
|
setCommitted((prev) => [...prev, toolMsg]);
|
|
340
396
|
|
|
341
397
|
if (approved && remainder && remainder.length > 0) {
|
|
342
|
-
processResponse(remainder, withTool, signal);
|
|
398
|
+
processResponse(remainder, withTool, signal, truncated, continueCount);
|
|
343
399
|
return;
|
|
344
400
|
}
|
|
345
401
|
|
|
@@ -349,31 +405,55 @@ export function useChat(repoPath: string) {
|
|
|
349
405
|
abortControllerRef.current = nextAbort;
|
|
350
406
|
setStage({ type: "thinking" });
|
|
351
407
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
408
|
+
const callWithAutoContinue = async (
|
|
409
|
+
messages: Message[],
|
|
410
|
+
maxRetries = 3,
|
|
411
|
+
): Promise<ChatResult> => {
|
|
412
|
+
let currentMessages = messages;
|
|
413
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
414
|
+
if (nextAbort.signal.aborted)
|
|
415
|
+
return { text: "", truncated: false };
|
|
416
|
+
const result = await callChat(
|
|
417
|
+
currentProvider,
|
|
418
|
+
currentSystemPrompt,
|
|
419
|
+
currentMessages,
|
|
420
|
+
nextAbort.signal,
|
|
421
|
+
);
|
|
422
|
+
if (result.text.trim()) return result;
|
|
423
|
+
const nudgeMsg: Message = {
|
|
424
|
+
role: "assistant",
|
|
425
|
+
content: `(model stalled — auto-continuing, attempt ${i + 1}/${maxRetries})`,
|
|
426
|
+
type: "text",
|
|
427
|
+
};
|
|
428
|
+
setCommitted((prev) => [...prev, nudgeMsg]);
|
|
429
|
+
setAllMessages((prev) => [...prev, nudgeMsg]);
|
|
430
|
+
currentMessages = [
|
|
431
|
+
...currentMessages,
|
|
432
|
+
{
|
|
433
|
+
role: "user",
|
|
434
|
+
content:
|
|
435
|
+
"Please continue. Provide your response to the previous tool output.",
|
|
436
|
+
type: "text",
|
|
437
|
+
},
|
|
438
|
+
];
|
|
439
|
+
}
|
|
440
|
+
return { text: "", truncated: false };
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
callWithAutoContinue(withTool)
|
|
444
|
+
.then((result: ChatResult) => {
|
|
370
445
|
if (nextAbort.signal.aborted) return;
|
|
371
|
-
processResponse(
|
|
446
|
+
processResponse(
|
|
447
|
+
result.text ?? "",
|
|
448
|
+
withTool,
|
|
449
|
+
nextAbort.signal,
|
|
450
|
+
result.truncated,
|
|
451
|
+
);
|
|
372
452
|
})
|
|
373
453
|
.catch(handleError(withTool));
|
|
374
454
|
};
|
|
375
455
|
|
|
376
|
-
if (forceApprove ||
|
|
456
|
+
if (forceApprove || isSafe || batchApprovedRef.current) {
|
|
377
457
|
executeAndContinue(true);
|
|
378
458
|
return;
|
|
379
459
|
}
|
|
@@ -421,9 +501,27 @@ export function useChat(repoPath: string) {
|
|
|
421
501
|
const abort = new AbortController();
|
|
422
502
|
abortControllerRef.current = abort;
|
|
423
503
|
|
|
504
|
+
const intent = classifyIntent(text);
|
|
505
|
+
const scopedToolsSection = registry.buildSystemPromptSection(intent);
|
|
506
|
+
const sessionSummary = getSessionToolSummary(repoPath);
|
|
507
|
+
|
|
508
|
+
let scopedSystemPrompt = currentSystemPrompt.replace(
|
|
509
|
+
/## TOOLS[\s\S]*?(?=\n## (?!TOOLS))/,
|
|
510
|
+
scopedToolsSection + "\n\n",
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
if (sessionSummary) {
|
|
514
|
+
scopedSystemPrompt = scopedSystemPrompt.replace(
|
|
515
|
+
/## CODEBASE/,
|
|
516
|
+
sessionSummary + "\n\n## CODEBASE",
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
|
|
424
520
|
setStage({ type: "thinking" });
|
|
425
|
-
callChat(currentProvider,
|
|
426
|
-
.then((
|
|
521
|
+
callChat(currentProvider, scopedSystemPrompt, nextAll, abort.signal)
|
|
522
|
+
.then((result: ChatResult) =>
|
|
523
|
+
processResponse(result.text, nextAll, abort.signal, result.truncated),
|
|
524
|
+
)
|
|
427
525
|
.catch(handleError(nextAll));
|
|
428
526
|
};
|
|
429
527
|
|
|
@@ -468,22 +566,24 @@ export function useChat(repoPath: string) {
|
|
|
468
566
|
const applyPatchesAndContinue = (patches: any[]) => {
|
|
469
567
|
try {
|
|
470
568
|
applyPatches(repoPath, patches);
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
569
|
+
logToolCall(
|
|
570
|
+
"changes",
|
|
571
|
+
patches.map((p) => p.path).join(", "),
|
|
572
|
+
`Applied changes to ${patches.length} file(s)`,
|
|
573
|
+
repoPath,
|
|
574
|
+
);
|
|
476
575
|
} catch {
|
|
477
576
|
/* non-fatal */
|
|
478
577
|
}
|
|
479
578
|
};
|
|
480
579
|
|
|
481
580
|
const skipPatches = (patches: any[]) => {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
581
|
+
logToolCall(
|
|
582
|
+
"changes-skipped",
|
|
583
|
+
patches.map((p: { path: string }) => p.path).join(", "),
|
|
584
|
+
`Skipped changes to ${patches.length} file(s)`,
|
|
585
|
+
repoPath,
|
|
586
|
+
);
|
|
487
587
|
};
|
|
488
588
|
|
|
489
589
|
return {
|
|
@@ -174,11 +174,11 @@ function CodebaseQA({
|
|
|
174
174
|
abortRef.current = abort;
|
|
175
175
|
|
|
176
176
|
callChat(provider, systemPrompt, nextAll, abort.signal)
|
|
177
|
-
.then((
|
|
177
|
+
.then((result) => {
|
|
178
178
|
const assistantMsg: Message = {
|
|
179
179
|
role: "assistant",
|
|
180
180
|
type: "text",
|
|
181
|
-
content:
|
|
181
|
+
content: result.text,
|
|
182
182
|
};
|
|
183
183
|
setCommitted((prev) => [...prev, assistantMsg]);
|
|
184
184
|
setAllMessages([...nextAll, assistantMsg]);
|
|
@@ -777,9 +777,9 @@ ${summarizeTimeline(commits)}`;
|
|
|
777
777
|
|
|
778
778
|
const runChat = async (history: Message[], signal: AbortSignal) => {
|
|
779
779
|
try {
|
|
780
|
-
const
|
|
780
|
+
const result = await callChat(provider, systemPrompt, history, signal);
|
|
781
781
|
if (signal.aborted) return;
|
|
782
|
-
processResponse(
|
|
782
|
+
processResponse(result.text, history, signal);
|
|
783
783
|
} catch (e: any) {
|
|
784
784
|
if (e?.name === "AbortError") return;
|
|
785
785
|
setMessages((prev) => [
|
|
@@ -602,12 +602,13 @@ ${lensFile.suggestions.length > 0 ? `\nProject suggestions:\n${lensFile.suggesti
|
|
|
602
602
|
|
|
603
603
|
let raw: string;
|
|
604
604
|
try {
|
|
605
|
-
|
|
605
|
+
const result = await callChat(
|
|
606
606
|
provider,
|
|
607
607
|
systemPromptRef.current,
|
|
608
608
|
messages,
|
|
609
609
|
combinedSignal,
|
|
610
610
|
);
|
|
611
|
+
raw = result.text;
|
|
611
612
|
} finally {
|
|
612
613
|
clearTimeout(timeoutId);
|
|
613
614
|
}
|
package/src/prompts/fewshot.ts
CHANGED
|
@@ -231,4 +231,22 @@ export const FEW_SHOT_MESSAGES: { role: string; content: string }[] = [
|
|
|
231
231
|
content:
|
|
232
232
|
"Done — addons/hello-world.js created using defineTool from @ridit/lens-sdk.",
|
|
233
233
|
},
|
|
234
|
+
{
|
|
235
|
+
role: "user",
|
|
236
|
+
content: "I ran the app and got this error:\n[ERROR] slice(None, 2, None)",
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
role: "assistant",
|
|
240
|
+
content: "<read-file>webfetch/parser.py</read-file>",
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
role: "user",
|
|
244
|
+
content:
|
|
245
|
+
"Here is the output from read-file of webfetch/parser.py:\n\n# file content here\n\nPlease continue your response based on this output.",
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
role: "assistant",
|
|
249
|
+
content:
|
|
250
|
+
'<write-file>\n{"path": "webfetch/parser.py", "content": "...complete fixed content..."}\n</write-file>',
|
|
251
|
+
},
|
|
234
252
|
];
|