@ridit/lens 0.3.6 → 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 +1342 -1036
- package/package.json +1 -1
- 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 +151 -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 +16 -2
- package/src/utils/chat.ts +16 -4
- package/src/utils/memory.ts +103 -26
- package/src/utils/tools/builtins.ts +31 -5
package/package.json
CHANGED
|
@@ -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
|
+
}
|
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
buildMemorySummary,
|
|
14
14
|
addMemory,
|
|
15
15
|
deleteMemory,
|
|
16
|
+
getSessionToolSummary,
|
|
17
|
+
logToolCall,
|
|
16
18
|
} from "../../../utils/memory";
|
|
17
19
|
import { fetchFileTree, readImportantFiles } from "../../../utils/files";
|
|
18
20
|
import { readLensFile } from "../../../utils/lensfile";
|
|
@@ -26,6 +28,7 @@ import {
|
|
|
26
28
|
buildSystemPrompt,
|
|
27
29
|
parseResponse,
|
|
28
30
|
callChat,
|
|
31
|
+
type ChatResult,
|
|
29
32
|
} from "../../../utils/chat";
|
|
30
33
|
|
|
31
34
|
export function useChat(repoPath: string) {
|
|
@@ -90,32 +93,26 @@ export function useChat(repoPath: string) {
|
|
|
90
93
|
setStage({ type: "idle" });
|
|
91
94
|
};
|
|
92
95
|
|
|
93
|
-
const
|
|
94
|
-
"shell",
|
|
95
|
-
"fetch",
|
|
96
|
-
"read-file",
|
|
97
|
-
"read-folder",
|
|
98
|
-
"grep",
|
|
99
|
-
"write-file",
|
|
100
|
-
"delete-file",
|
|
101
|
-
"delete-folder",
|
|
102
|
-
"open-url",
|
|
103
|
-
"generate-pdf",
|
|
104
|
-
"search",
|
|
105
|
-
"clone",
|
|
106
|
-
"changes",
|
|
107
|
-
];
|
|
96
|
+
const MAX_AUTO_CONTINUES = 3;
|
|
108
97
|
|
|
109
98
|
function isLikelyTruncated(text: string): boolean {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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;
|
|
113
108
|
}
|
|
114
109
|
|
|
115
110
|
const processResponse = (
|
|
116
111
|
raw: string,
|
|
117
112
|
currentAll: Message[],
|
|
118
113
|
signal: AbortSignal,
|
|
114
|
+
truncated = false,
|
|
115
|
+
continueCount = 0,
|
|
119
116
|
) => {
|
|
120
117
|
if (signal.aborted) {
|
|
121
118
|
batchApprovedRef.current = false;
|
|
@@ -123,16 +120,73 @@ export function useChat(repoPath: string) {
|
|
|
123
120
|
return;
|
|
124
121
|
}
|
|
125
122
|
|
|
126
|
-
if (isLikelyTruncated(raw)) {
|
|
127
|
-
|
|
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 = {
|
|
128
142
|
role: "assistant",
|
|
143
|
+
content: raw,
|
|
144
|
+
type: "text",
|
|
145
|
+
};
|
|
146
|
+
const nudgeMsg: Message = {
|
|
147
|
+
role: "user",
|
|
129
148
|
content:
|
|
130
|
-
"
|
|
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}…)`,
|
|
131
157
|
type: "text",
|
|
132
158
|
};
|
|
133
159
|
setAllMessages([...currentAll, truncMsg]);
|
|
134
160
|
setCommitted((prev) => [...prev, truncMsg]);
|
|
135
|
-
|
|
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));
|
|
136
190
|
return;
|
|
137
191
|
}
|
|
138
192
|
|
|
@@ -313,13 +367,14 @@ export function useChat(repoPath: string) {
|
|
|
313
367
|
}
|
|
314
368
|
|
|
315
369
|
if (approved && !result.startsWith("Error:")) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
370
|
+
logToolCall(
|
|
371
|
+
parsed.toolName,
|
|
372
|
+
tool.summariseInput
|
|
319
373
|
? String(tool.summariseInput(parsed.input))
|
|
320
374
|
: parsed.rawInput,
|
|
321
|
-
|
|
322
|
-
|
|
375
|
+
result,
|
|
376
|
+
repoPath,
|
|
377
|
+
);
|
|
323
378
|
}
|
|
324
379
|
|
|
325
380
|
const displayContent = tool.summariseInput
|
|
@@ -340,7 +395,7 @@ export function useChat(repoPath: string) {
|
|
|
340
395
|
setCommitted((prev) => [...prev, toolMsg]);
|
|
341
396
|
|
|
342
397
|
if (approved && remainder && remainder.length > 0) {
|
|
343
|
-
processResponse(remainder, withTool, signal);
|
|
398
|
+
processResponse(remainder, withTool, signal, truncated, continueCount);
|
|
344
399
|
return;
|
|
345
400
|
}
|
|
346
401
|
|
|
@@ -350,31 +405,55 @@ export function useChat(repoPath: string) {
|
|
|
350
405
|
abortControllerRef.current = nextAbort;
|
|
351
406
|
setStage({ type: "thinking" });
|
|
352
407
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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) => {
|
|
371
445
|
if (nextAbort.signal.aborted) return;
|
|
372
|
-
processResponse(
|
|
446
|
+
processResponse(
|
|
447
|
+
result.text ?? "",
|
|
448
|
+
withTool,
|
|
449
|
+
nextAbort.signal,
|
|
450
|
+
result.truncated,
|
|
451
|
+
);
|
|
373
452
|
})
|
|
374
453
|
.catch(handleError(withTool));
|
|
375
454
|
};
|
|
376
455
|
|
|
377
|
-
if (forceApprove ||
|
|
456
|
+
if (forceApprove || isSafe || batchApprovedRef.current) {
|
|
378
457
|
executeAndContinue(true);
|
|
379
458
|
return;
|
|
380
459
|
}
|
|
@@ -424,15 +503,25 @@ export function useChat(repoPath: string) {
|
|
|
424
503
|
|
|
425
504
|
const intent = classifyIntent(text);
|
|
426
505
|
const scopedToolsSection = registry.buildSystemPromptSection(intent);
|
|
506
|
+
const sessionSummary = getSessionToolSummary(repoPath);
|
|
427
507
|
|
|
428
|
-
|
|
508
|
+
let scopedSystemPrompt = currentSystemPrompt.replace(
|
|
429
509
|
/## TOOLS[\s\S]*?(?=\n## (?!TOOLS))/,
|
|
430
510
|
scopedToolsSection + "\n\n",
|
|
431
511
|
);
|
|
432
512
|
|
|
513
|
+
if (sessionSummary) {
|
|
514
|
+
scopedSystemPrompt = scopedSystemPrompt.replace(
|
|
515
|
+
/## CODEBASE/,
|
|
516
|
+
sessionSummary + "\n\n## CODEBASE",
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
|
|
433
520
|
setStage({ type: "thinking" });
|
|
434
521
|
callChat(currentProvider, scopedSystemPrompt, nextAll, abort.signal)
|
|
435
|
-
.then((
|
|
522
|
+
.then((result: ChatResult) =>
|
|
523
|
+
processResponse(result.text, nextAll, abort.signal, result.truncated),
|
|
524
|
+
)
|
|
436
525
|
.catch(handleError(nextAll));
|
|
437
526
|
};
|
|
438
527
|
|
|
@@ -477,22 +566,24 @@ export function useChat(repoPath: string) {
|
|
|
477
566
|
const applyPatchesAndContinue = (patches: any[]) => {
|
|
478
567
|
try {
|
|
479
568
|
applyPatches(repoPath, patches);
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
569
|
+
logToolCall(
|
|
570
|
+
"changes",
|
|
571
|
+
patches.map((p) => p.path).join(", "),
|
|
572
|
+
`Applied changes to ${patches.length} file(s)`,
|
|
573
|
+
repoPath,
|
|
574
|
+
);
|
|
485
575
|
} catch {
|
|
486
576
|
/* non-fatal */
|
|
487
577
|
}
|
|
488
578
|
};
|
|
489
579
|
|
|
490
580
|
const skipPatches = (patches: any[]) => {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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
|
+
);
|
|
496
587
|
};
|
|
497
588
|
|
|
498
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
|
];
|
package/src/prompts/system.ts
CHANGED
|
@@ -21,13 +21,25 @@ ${tools}
|
|
|
21
21
|
You can save and delete memories at any time by emitting these tags alongside your normal response.
|
|
22
22
|
They are stripped before display — the user will not see the raw tags.
|
|
23
23
|
|
|
24
|
-
### memory-add — save something important to long-term memory
|
|
24
|
+
### memory-add — save something important to long-term memory
|
|
25
25
|
<memory-add>User prefers TypeScript strict mode in all new files</memory-add>
|
|
26
26
|
|
|
27
|
+
Use [global] prefix for things that apply across ALL repos (user preferences, name, coding style):
|
|
28
|
+
<memory-add>[global] User prefers bun over npm for all projects</memory-add>
|
|
29
|
+
|
|
30
|
+
Omit [global] for repo-specific memories (architecture decisions, patterns, agreed conventions):
|
|
31
|
+
<memory-add>This repo uses path aliases defined in tsconfig.json</memory-add>
|
|
32
|
+
|
|
27
33
|
### memory-delete — delete a memory by its ID (shown in brackets like [abc123])
|
|
28
34
|
<memory-delete>abc123</memory-delete>
|
|
29
35
|
|
|
30
|
-
Use memory-add
|
|
36
|
+
Use memory-add ONLY for information that cannot be inferred by reading the codebase:
|
|
37
|
+
- User preferences and coding conventions
|
|
38
|
+
- Decisions made during the session (e.g. "user chose bun over npm")
|
|
39
|
+
- Things the user explicitly asked you to remember
|
|
40
|
+
- Cross-session context that would otherwise be lost
|
|
41
|
+
|
|
42
|
+
NEVER save memories that just describe what files exist or what the project does — that can be read directly from the codebase.
|
|
31
43
|
Use memory-delete when the user asks you to forget something or a memory is outdated.
|
|
32
44
|
|
|
33
45
|
## RULES
|
|
@@ -49,6 +61,8 @@ Use memory-delete when the user asks you to forget something or a memory is outd
|
|
|
49
61
|
15. When explaining how to use a tool in text, use [tag] bracket notation — NEVER emit a real XML tool tag as part of an explanation.
|
|
50
62
|
16. NEVER use markdown formatting in plain text responses — no bold, no headings, no bullet points. Only use fenced code blocks when showing actual code.
|
|
51
63
|
17. When scaffolding multiple files, emit ONE write-file tag per response and wait for the result before writing the next file.
|
|
64
|
+
18. When you identify a bug or error, ALWAYS write the fix immediately using write-file or changes. Never describe the fix without writing it.
|
|
65
|
+
19. NEVER use shell for filesystem inspection or searching — always use grep, read-file, or read-folder instead.
|
|
52
66
|
|
|
53
67
|
## ADDON FORMAT
|
|
54
68
|
|