clippy-test 1.0.9 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +172 -0
- package/bin/oncall.js +396 -0
- package/dist/api.js +10 -1
- package/dist/cli.js +20 -20
- package/dist/config.js +7 -7
- package/dist/helpers/cli-helpers.d.ts +25 -0
- package/dist/helpers/cli-helpers.js +329 -0
- package/dist/helpers/config-helpers.js +189 -0
- package/dist/helpers/ripgrep-tool.d.ts +15 -0
- package/dist/helpers/ripgrep-tool.js +126 -0
- package/dist/index.js +225 -62
- package/dist/logsManager.d.ts +31 -0
- package/dist/logsManager.js +90 -0
- package/dist/postinstall.js +20 -0
- package/dist/tools/ripgrep.js +3 -3
- package/dist/useWebSocket.d.ts +14 -6
- package/dist/useWebSocket.js +290 -45
- package/dist/utils/version-check.d.ts +2 -0
- package/dist/utils/version-check.js +124 -0
- package/dist/utils.d.ts +16 -0
- package/dist/utils.js +125 -4
- package/dist/websocket-server.d.ts +24 -0
- package/dist/websocket-server.js +235 -0
- package/package.json +18 -5
- package/bin/clippy.js +0 -109
- package/dist/api.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/code_hierarchy.js.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/tools/code_hierarchy.d.ts +0 -8
- package/dist/tools/code_hierarchy.js +0 -78
- package/dist/tools/code_hierarchy.js.map +0 -1
- package/dist/tools/fetch_context.d.ts +0 -1
- package/dist/tools/fetch_context.js +0 -8
- package/dist/tools/fetch_context.js.map +0 -1
- package/dist/tools/ripgrep.js.map +0 -1
- package/dist/ui-graph.js.map +0 -1
- package/dist/useWebSocket.js.map +0 -1
- package/dist/utils.js.map +0 -1
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { RipGrep } from "ripgrep-node";
|
|
2
|
+
import { logd } from "./cli-helpers.js";
|
|
3
|
+
export async function ripgrepSearch(query, options = {}) {
|
|
4
|
+
const defaultExcludePatterns = [
|
|
5
|
+
"node_modules",
|
|
6
|
+
".git",
|
|
7
|
+
"dist",
|
|
8
|
+
"build",
|
|
9
|
+
".next",
|
|
10
|
+
".cache",
|
|
11
|
+
"coverage",
|
|
12
|
+
".nyc_output",
|
|
13
|
+
".vscode",
|
|
14
|
+
".idea",
|
|
15
|
+
"*.log",
|
|
16
|
+
"*.lock",
|
|
17
|
+
"package-lock.json",
|
|
18
|
+
"yarn.lock",
|
|
19
|
+
"pnpm-lock.yaml",
|
|
20
|
+
".env",
|
|
21
|
+
".env.*",
|
|
22
|
+
"*.min.js",
|
|
23
|
+
"*.min.css",
|
|
24
|
+
".DS_Store",
|
|
25
|
+
"Thumbs.db",
|
|
26
|
+
];
|
|
27
|
+
const { maxResults = 20, caseSensitive = false, fileTypes = [], excludePatterns = defaultExcludePatterns, workingDirectory, } = options;
|
|
28
|
+
if (!query || query.trim().length === 0) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
const searchDir = workingDirectory || (typeof process !== "undefined" ? process.cwd() : undefined);
|
|
32
|
+
if (!searchDir) {
|
|
33
|
+
logd("[ripgrep] ERROR: workingDirectory is required for client-side usage");
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
logd(`[ripgrep] Starting search: query=${query}, searchDir=${searchDir}, maxResults=${maxResults}, caseSensitive=${caseSensitive}, fileTypes=${JSON.stringify(fileTypes)}, excludePatterns=${JSON.stringify(excludePatterns.slice(0, 5))}`);
|
|
37
|
+
try {
|
|
38
|
+
let rg = new RipGrep(query, searchDir);
|
|
39
|
+
rg.withFilename().lineNumber();
|
|
40
|
+
rg.fixedStrings();
|
|
41
|
+
if (!caseSensitive) {
|
|
42
|
+
rg.ignoreCase();
|
|
43
|
+
}
|
|
44
|
+
if (fileTypes.length > 0) {
|
|
45
|
+
for (const ext of fileTypes) {
|
|
46
|
+
rg.glob(`*.${ext}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
for (const pattern of excludePatterns) {
|
|
50
|
+
const hasFileExtension = /\.(json|lock|yaml|js|css|log)$/.test(pattern) ||
|
|
51
|
+
pattern.startsWith("*.") ||
|
|
52
|
+
pattern === ".env" ||
|
|
53
|
+
pattern.startsWith(".env.") ||
|
|
54
|
+
pattern === ".DS_Store" ||
|
|
55
|
+
pattern === "Thumbs.db";
|
|
56
|
+
const isFilePattern = pattern.includes("*") || hasFileExtension;
|
|
57
|
+
const excludePattern = isFilePattern ? `!${pattern}` : `!${pattern}/**`;
|
|
58
|
+
rg.glob(excludePattern);
|
|
59
|
+
}
|
|
60
|
+
const output = await rg.run().asString();
|
|
61
|
+
logd(`[ripgrep] Raw output: outputLength=${output?.length || 0}, hasOutput=${!!(output && output.trim().length > 0)}, outputPreview=${output?.substring(0, 200) || "N/A"}`);
|
|
62
|
+
if (!output || output.trim().length === 0) {
|
|
63
|
+
logd("[ripgrep] ⚠️ No output from ripgrep - returning empty results");
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
const lines = output.trim().split("\n");
|
|
67
|
+
logd(`[ripgrep] Parsing output: totalLines=${lines.length}, firstLine=${lines[0]?.substring(0, 100) || "N/A"}`);
|
|
68
|
+
const results = [];
|
|
69
|
+
for (const line of lines.slice(0, maxResults)) {
|
|
70
|
+
const match = line.match(/^(.+?):(\d+):(.+)$/);
|
|
71
|
+
if (match) {
|
|
72
|
+
const [, filePath, lineNum, content] = match;
|
|
73
|
+
const lineNumber = lineNum ? parseInt(lineNum, 10) : null;
|
|
74
|
+
const preview = content ? content.trim().slice(0, 200) : undefined;
|
|
75
|
+
results.push({
|
|
76
|
+
filePath: filePath.trim(),
|
|
77
|
+
line: lineNumber ?? null,
|
|
78
|
+
preview: preview || undefined,
|
|
79
|
+
score: 1.0,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
logd(`[ripgrep] ⚠️ Line didn't match expected format: line=${line.substring(0, 100)}, lineLength=${line.length}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
logd(`[ripgrep] ✅ Parsed results: totalResults=${results.length}, results=${JSON.stringify(results.map(r => ({
|
|
87
|
+
filePath: r.filePath,
|
|
88
|
+
line: r.line,
|
|
89
|
+
previewLength: r.preview?.length || 0,
|
|
90
|
+
})))}`);
|
|
91
|
+
return results;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
logd(`[ripgrep] ❌ Exception caught: error=${error.message}, code=${error.code}, stack=${error.stack?.substring(0, 200) || 'N/A'}`);
|
|
95
|
+
if (error.message?.includes("No matches") ||
|
|
96
|
+
error.message?.includes("not found") ||
|
|
97
|
+
error.code === 1) {
|
|
98
|
+
logd("[ripgrep] ℹ️ No matches found (this is normal if search term doesn't exist)");
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
logd(`[ripgrep] Error executing ripgrep: ${error.message}`);
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export async function ripgrepSearchMultiple(queries, options = {}) {
|
|
106
|
+
const allResults = [];
|
|
107
|
+
for (const query of queries) {
|
|
108
|
+
const results = await ripgrepSearch(query, {
|
|
109
|
+
...options,
|
|
110
|
+
maxResults: Math.ceil((options.maxResults || 20) / queries.length),
|
|
111
|
+
});
|
|
112
|
+
allResults.push(...results);
|
|
113
|
+
}
|
|
114
|
+
// Deduplicate by filePath:line
|
|
115
|
+
const seen = new Set();
|
|
116
|
+
const unique = [];
|
|
117
|
+
for (const result of allResults) {
|
|
118
|
+
const key = `${result.filePath}:${result.line}`;
|
|
119
|
+
if (!seen.has(key)) {
|
|
120
|
+
seen.add(key);
|
|
121
|
+
unique.push(result);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return unique.slice(0, options.maxResults || 20);
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=ripgrep-tool.js.map
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,10 @@ import { markedTerminal } from "marked-terminal";
|
|
|
12
12
|
import { useWebSocket } from "./useWebSocket.js";
|
|
13
13
|
import { config } from "./config.js";
|
|
14
14
|
import Spinner from "ink-spinner";
|
|
15
|
+
import { HumanMessage } from "langchain";
|
|
16
|
+
import { loadProjectMetadata, fetchProjectsFromCluster, logd } from "./helpers/cli-helpers.js";
|
|
17
|
+
import logsManager from "./logsManager.js";
|
|
18
|
+
import { extractMessageContent } from "./utils.js";
|
|
15
19
|
dotenv.config();
|
|
16
20
|
if (typeof process !== "undefined" && process.on) {
|
|
17
21
|
process.on("SIGINT", () => {
|
|
@@ -22,15 +26,24 @@ if (typeof process !== "undefined" && process.on) {
|
|
|
22
26
|
marked.use(markedTerminal({
|
|
23
27
|
reflowText: false,
|
|
24
28
|
showSectionPrefix: false,
|
|
25
|
-
unescape:
|
|
29
|
+
unescape: true,
|
|
30
|
+
emoji: true
|
|
26
31
|
}));
|
|
32
|
+
const COLON_REPLACER = '*#COLON|*';
|
|
33
|
+
function escapeRegExp(str) {
|
|
34
|
+
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
|
|
35
|
+
}
|
|
36
|
+
const COLON_REPLACER_REGEXP = new RegExp(escapeRegExp(COLON_REPLACER), 'g');
|
|
37
|
+
function undoColon(str) {
|
|
38
|
+
return str.replace(COLON_REPLACER_REGEXP, ':');
|
|
39
|
+
}
|
|
27
40
|
// Override just the 'text' renderer to handle inline tokens:
|
|
28
41
|
marked.use({
|
|
29
42
|
renderer: {
|
|
30
43
|
text(tokenOrString) {
|
|
31
44
|
if (typeof tokenOrString === "object" && tokenOrString?.tokens) {
|
|
32
45
|
// @ts-ignore - 'this' is the renderer context with a parser
|
|
33
|
-
return this.parser.parseInline(tokenOrString.tokens);
|
|
46
|
+
return undoColon(this.parser.parseInline(tokenOrString.tokens));
|
|
34
47
|
}
|
|
35
48
|
return typeof tokenOrString === "string"
|
|
36
49
|
? tokenOrString
|
|
@@ -94,12 +107,12 @@ const ScrollableContent = ({ lines, maxHeight, isFocused, onScrollChange, scroll
|
|
|
94
107
|
}
|
|
95
108
|
const scrollBarIndicator = totalLines > visibleLines ? `[${scrollPosition}%]` : "";
|
|
96
109
|
return (_jsxs(Box, { flexDirection: "column", height: maxHeight, overflow: "hidden", children: [displayedLines.map((line) => {
|
|
97
|
-
const rendered = marked.parseInline(line.text);
|
|
110
|
+
const rendered = undoColon(marked.parseInline(line.text));
|
|
98
111
|
return _jsx(Text, { children: rendered }, line.key);
|
|
99
112
|
}), _jsx(Box, { position: "absolute", children: _jsx(Text, { color: "yellowBright", bold: true, children: scrollBarIndicator }) })] }));
|
|
100
113
|
};
|
|
101
114
|
//AI chat
|
|
102
|
-
const ScrollableContentChat = ({ lines, maxHeight, isFocused, onScrollChange, scrollOffset, isLoading, showControlR, }) => {
|
|
115
|
+
const ScrollableContentChat = ({ lines, maxHeight, isFocused, onScrollChange, scrollOffset, isLoading, showControlR, customMessage, }) => {
|
|
103
116
|
const totalLines = lines.length;
|
|
104
117
|
const visibleLines = Math.max(0, Math.min(maxHeight, totalLines));
|
|
105
118
|
const maxOffset = Math.max(0, totalLines - visibleLines);
|
|
@@ -154,15 +167,15 @@ const ScrollableContentChat = ({ lines, maxHeight, isFocused, onScrollChange, sc
|
|
|
154
167
|
return (_jsxs(Box, { flexDirection: "column", height: maxHeight, overflow: "hidden", children: [displayedLines.map((line) => {
|
|
155
168
|
if (line?.text === "" || line?.text === undefined)
|
|
156
169
|
return null;
|
|
157
|
-
const rendered = marked.parseInline(line?.text);
|
|
170
|
+
const rendered = undoColon(marked.parseInline(line?.text));
|
|
158
171
|
return _jsx(Text, { children: rendered }, line.key);
|
|
159
|
-
}), isLoading && (_jsx(_Fragment, { children: _jsxs(Text, { color: "grey", children: [_jsx(Spinner, { type: "dots" }), "\u00A0 Analyzing..."] }) })), showControlR && (_jsxs(Text, { color: "#949494", children: ["\n\n", " If the issue is resolved hit [Ctrl-r] to start new issue. If not continue chatting."] })), _jsx(Box, { position: "absolute", children: _jsx(Text, { color: "yellowBright", bold: true, children: scrollBarIndicator }) })] }));
|
|
172
|
+
}), isLoading && (_jsx(_Fragment, { children: _jsxs(Text, { color: "grey", children: [_jsx(Spinner, { type: "dots" }), "\u00A0 ", customMessage || "Analyzing..."] }) })), showControlR && (_jsxs(Text, { color: "#949494", children: ["\n\n", " If the issue is resolved hit [Ctrl-r] to start new issue. If not continue chatting."] })), _jsx(Box, { position: "absolute", children: _jsx(Text, { color: "yellowBright", bold: true, children: scrollBarIndicator }) })] }));
|
|
160
173
|
};
|
|
161
174
|
//border for the content
|
|
162
175
|
const BorderBox = ({ title, children, isFocused, width, }) => (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: isFocused ? "greenBright" : "gray", paddingX: 1, paddingY: 0, marginRight: 1, width: width, overflow: "hidden", children: [_jsx(Box, { marginBottom: 1, borderBottom: isFocused ? true : undefined, borderBottomColor: isFocused ? "greenBright" : "gray", children: _jsxs(Text, { color: "cyan", bold: isFocused, children: [title, " ", isFocused ? " (FOCUSED)" : ""] }) }), children] }));
|
|
163
176
|
const BorderBoxNoBorder = ({ title, children, isFocused, width, }) => (_jsxs(Box, { flexDirection: "column", borderColor: isFocused ? "greenBright" : "gray", paddingX: 1, paddingY: 0, marginRight: 1, width: width, overflow: "hidden", children: [_jsx(Box, { marginBottom: 1, borderBottom: isFocused ? true : undefined, borderBottomColor: isFocused ? "greenBright" : "gray", children: _jsxs(Text, { color: "cyan", bold: isFocused, children: [title, " ", isFocused ? " (FOCUSED)" : ""] }) }), children] }));
|
|
164
177
|
const ShortcutBadge = ({ label }) => (_jsxs(Text, { backgroundColor: "#1f2937", color: "#f8fafc", bold: true, children: [" ", label, " "] }));
|
|
165
|
-
const ShortcutItem = ({ shortcut, description, showDivider }) => (_jsxs(Box, { alignItems: "center", marginBottom: 0, marginX: 1, children: [showDivider && (_jsxs(Text, { color: "#4b5563", dimColor: true, children: ["\u2502", " "] })), _jsx(ShortcutBadge, { label: shortcut }), _jsx(Text, { color: "#b0b0b0", children: ` ${description}` })] }));
|
|
178
|
+
const ShortcutItem = ({ shortcut, description, showDivider, }) => (_jsxs(Box, { alignItems: "center", marginBottom: 0, marginX: 1, children: [showDivider && (_jsxs(Text, { color: "#4b5563", dimColor: true, children: ["\u2502", " "] })), _jsx(ShortcutBadge, { label: shortcut }), _jsx(Text, { color: "#b0b0b0", children: ` ${description}` })] }));
|
|
166
179
|
const ShortcutsFooter = ({ shortcuts, }) => {
|
|
167
180
|
const firstRow = shortcuts.slice(0, 3);
|
|
168
181
|
const secondRow = shortcuts.slice(3);
|
|
@@ -190,6 +203,7 @@ export const App = () => {
|
|
|
190
203
|
const { exit } = useApp();
|
|
191
204
|
const [chatInput, setChatInput] = useState("");
|
|
192
205
|
const [rawLogData, setRawLogData] = useState([]);
|
|
206
|
+
const [planningDoc, setPlanningDoc] = useState("");
|
|
193
207
|
const partialLine = useRef("");
|
|
194
208
|
const logKeyCounter = useRef(0);
|
|
195
209
|
const [activePane, setActivePane] = useState("input");
|
|
@@ -200,6 +214,7 @@ export const App = () => {
|
|
|
200
214
|
const [mode, setMode] = useState("NORMAL");
|
|
201
215
|
const ctrlPressedRef = useRef(false);
|
|
202
216
|
const shortcuts = useMemo(() => getShortcutsForMode(mode), [mode]);
|
|
217
|
+
const [unTamperedLogs, setUnTamperedLogs] = useState("");
|
|
203
218
|
// refs for current dims (used by stable callbacks)
|
|
204
219
|
const terminalColsRef = useRef(terminalCols);
|
|
205
220
|
const terminalRowsRef = useRef(terminalRows);
|
|
@@ -212,7 +227,9 @@ export const App = () => {
|
|
|
212
227
|
logsHeightRef.current = Math.max(5, terminalRows - 6);
|
|
213
228
|
}, [terminalRows]);
|
|
214
229
|
//websocket hook
|
|
215
|
-
const { connectWebSocket, sendQuery, chatResponseMessages,
|
|
230
|
+
const { connectWebSocket, sendQuery, chatResponseMessages, // Full history including ToolMessage (for backend queries)
|
|
231
|
+
visibleChats, // Filtered for UI display (excludes ToolMessage except reflections)
|
|
232
|
+
setChatResponseMessages, isConnected, isLoading, setIsLoading, setTrimmedChats, API_KEY, connectionError, setSocketId, setIsConnected, socket, setShowControlR, showControlR, setCompleteChatHistory, customMessage, setVisibleChats, graphState, setGraphState } = useWebSocket(config.websocket_url, logsManager);
|
|
216
233
|
const SCROLL_HEIGHT = terminalRows - 6;
|
|
217
234
|
const LOGS_HEIGHT = SCROLL_HEIGHT;
|
|
218
235
|
const INPUT_BOX_HEIGHT = 5; // Border (2) + marginTop (1) + content (1) + padding (~1)
|
|
@@ -258,40 +275,96 @@ export const App = () => {
|
|
|
258
275
|
}
|
|
259
276
|
// Auto-switch to chat pane when server finishes responding
|
|
260
277
|
useEffect(() => {
|
|
261
|
-
|
|
278
|
+
// Use visibleChats for UI-related checks
|
|
279
|
+
const messagesToCheck = visibleChats || chatResponseMessages;
|
|
280
|
+
if (messagesToCheck.length === 0)
|
|
262
281
|
return;
|
|
263
|
-
const lastMessage =
|
|
282
|
+
const lastMessage = messagesToCheck[messagesToCheck.length - 1];
|
|
264
283
|
// Switch focus when we get a final message type (response, ask_user, or error)
|
|
265
|
-
if (
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
284
|
+
// if (
|
|
285
|
+
// lastMessage?.type === "response" ||
|
|
286
|
+
// lastMessage?.type === "progress" ||
|
|
287
|
+
// lastMessage?.type === "ask_user" ||
|
|
288
|
+
// lastMessage?.type === "error"
|
|
289
|
+
// ) {
|
|
290
|
+
// setTimeout(() => setActivePane("chat"), 600);
|
|
291
|
+
// }
|
|
292
|
+
// if (lastMessage && lastMessage.type && lastMessage?.type === "response") {
|
|
293
|
+
// setShowControlR(true);
|
|
294
|
+
// } else {
|
|
295
|
+
// setShowControlR(false);
|
|
296
|
+
// }
|
|
297
|
+
// const d = fs.readFileSync('logs')
|
|
298
|
+
// fs.writeFileSync('logs', `${d} \n ${JSON.stringify(chatResponseMessages)}`)
|
|
299
|
+
}, [visibleChats, chatResponseMessages]);
|
|
300
|
+
const chatLinesChat = useMemo(() => {
|
|
301
|
+
const availableWidth = Math.floor(terminalColsRef.current / 2) - 10;
|
|
302
|
+
// Use visibleChats for UI rendering (filtered, excludes ToolMessage except reflections)
|
|
303
|
+
const messagesToRender = visibleChats || chatResponseMessages;
|
|
304
|
+
if (!messagesToRender || messagesToRender.length <= 0) {
|
|
305
|
+
return [];
|
|
270
306
|
}
|
|
271
|
-
|
|
272
|
-
|
|
307
|
+
function groupMessageChunks(messages) {
|
|
308
|
+
return messages.reduce((prev, message) => {
|
|
309
|
+
if (!prev)
|
|
310
|
+
prev = { lastId: "", messages: [] };
|
|
311
|
+
if (!message.id)
|
|
312
|
+
message.id = (new Date()).getTime() + "";
|
|
313
|
+
if (message.id !== prev.lastId) {
|
|
314
|
+
prev.messages.push([message]);
|
|
315
|
+
prev.lastId = message.id;
|
|
316
|
+
return prev;
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
}
|
|
320
|
+
const lastMessageIndex = prev.messages.length - 1;
|
|
321
|
+
prev.messages[lastMessageIndex].push(message);
|
|
322
|
+
return prev;
|
|
323
|
+
}, { lastId: "", messages: [] });
|
|
273
324
|
}
|
|
274
|
-
|
|
275
|
-
|
|
325
|
+
function extratMessageContent(messages) {
|
|
326
|
+
return messages.reduce((prev, message) => {
|
|
327
|
+
return prev + message.content;
|
|
328
|
+
}, "");
|
|
276
329
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
330
|
+
function extractThinkMessage(messages) {
|
|
331
|
+
if (messages.length < 5)
|
|
332
|
+
return "";
|
|
333
|
+
return messages.slice(4, -3).reduce((prev, message) => {
|
|
334
|
+
if (message.tool_call_chunks && message.tool_call_chunks[0] && message.tool_call_chunks[0].args)
|
|
335
|
+
return prev + message.tool_call_chunks[0].args;
|
|
336
|
+
return prev;
|
|
337
|
+
}, "");
|
|
338
|
+
}
|
|
339
|
+
return groupMessageChunks(messagesToRender).messages.flatMap((msgs, index) => {
|
|
340
|
+
// Get message type from BaseMessage (LangChain messages use getType() or constructor name)
|
|
341
|
+
const msgAny = msgs;
|
|
342
|
+
const msgType = (typeof msgAny[0].getType === "function" && msgAny[0].getType()) ||
|
|
343
|
+
msgAny[0]._type ||
|
|
344
|
+
msgs.constructor.name ||
|
|
345
|
+
"";
|
|
346
|
+
const isHuman = msgType === "human" || msgs.constructor.name === "HumanMessage";
|
|
347
|
+
// const prefix = `${isHuman ? "🧑" : "🤖"} `;
|
|
348
|
+
const prefix = `${isHuman ? ">" : "⏺"} `;
|
|
282
349
|
const prefixWidth = stringWidth(prefix);
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
350
|
+
// let content = `${index}: ` + extractMessageContent(msgs)
|
|
351
|
+
let content = extractMessageContent(msgs);
|
|
352
|
+
const fMessage = msgs[0];
|
|
353
|
+
if (fMessage.tool_calls && fMessage.tool_calls.length > 0) {
|
|
354
|
+
logd("---------------- tool calls received --------------");
|
|
355
|
+
logd(JSON.stringify(msgs));
|
|
356
|
+
if (fMessage.tool_calls[0].name === "thinkTool") {
|
|
357
|
+
content = extractThinkMessage(msgs);
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
content = `Calling: ${fMessage.tool_calls[0].name}`;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
292
363
|
if (content === "")
|
|
293
364
|
return [];
|
|
294
|
-
|
|
365
|
+
let contentLines = content?.split("\n");
|
|
366
|
+
if (!contentLines || contentLines.length <= 0)
|
|
367
|
+
contentLines = [];
|
|
295
368
|
const lines = contentLines.flatMap((line, lineIndex) => {
|
|
296
369
|
const fullLine = (lineIndex === 0 ? prefix : " ".repeat(prefixWidth)) + line;
|
|
297
370
|
const wrappedLines = wrapText(fullLine, availableWidth);
|
|
@@ -300,32 +373,94 @@ export const App = () => {
|
|
|
300
373
|
text: wrappedLine,
|
|
301
374
|
}));
|
|
302
375
|
});
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
376
|
+
// Add spacing after each message for better readability
|
|
377
|
+
lines.push({
|
|
378
|
+
key: `chat-${index}-spacer`,
|
|
379
|
+
text: " ",
|
|
380
|
+
});
|
|
381
|
+
// Add an extra blank line after human messages for clearer separation
|
|
382
|
+
// if (isHuman || msgType === "user") {
|
|
383
|
+
// lines.push({
|
|
384
|
+
// key: `chat-${index}-spacer-extra`,
|
|
385
|
+
// text: " ",
|
|
386
|
+
// });
|
|
387
|
+
// }
|
|
309
388
|
return lines;
|
|
310
389
|
});
|
|
311
|
-
|
|
390
|
+
// return groupMessageChunks(messagesToRender).messages.flatMap((msgs, index) => {
|
|
391
|
+
// // Get message type from BaseMessage (LangChain messages use getType() or constructor name)
|
|
392
|
+
// const msgAny = msgs as any;
|
|
393
|
+
// const msgType =
|
|
394
|
+
// (typeof msgAny[0].getType === "function" && msgAny[0].getType()) ||
|
|
395
|
+
// msgAny[0]._type ||
|
|
396
|
+
// msgs.constructor.name ||
|
|
397
|
+
// "";
|
|
398
|
+
// const isHuman = msgType === "human" || msgs.constructor.name === "HumanMessage";
|
|
399
|
+
// // const prefix = `${isHuman ? "🧑" : "🤖"} `;
|
|
400
|
+
// const prefix = `${isHuman ? "> " : "⏺ "} `;
|
|
401
|
+
// const prefixWidth = stringWidth(prefix);
|
|
402
|
+
// let content = extractMessageContent(msgs)
|
|
403
|
+
// const fMessage = msgs[0] as AIMessage
|
|
404
|
+
// if (fMessage.tool_calls && fMessage.tool_calls.length > 0) {
|
|
405
|
+
// logd("---------------- tool calls received --------------");
|
|
406
|
+
// logd(JSON.stringify(msgs));
|
|
407
|
+
// if (fMessage.tool_calls[0].name === "thinkTool") {
|
|
408
|
+
// content = extractThinkMessage(msgs as AIMessageChunk[])
|
|
409
|
+
// } else {
|
|
410
|
+
// content = `Calling: ${fMessage.tool_calls[0].name}`
|
|
411
|
+
// }
|
|
412
|
+
// }
|
|
413
|
+
// return {
|
|
414
|
+
// key: `chat-${index}`,
|
|
415
|
+
// text: prefix + content,
|
|
416
|
+
// }
|
|
417
|
+
// });
|
|
418
|
+
}, [visibleChats, chatResponseMessages]);
|
|
312
419
|
const currentLogDataString = useMemo(() => rawLogData.map((l) => l.text).join("\n"), [rawLogData]);
|
|
313
420
|
// Process truncation once when inserting logs (so resize won't re-process)
|
|
314
421
|
const getProcessedLine = (text) => {
|
|
315
422
|
const availableWidth = Math.floor(terminalColsRef.current / 2) - 6;
|
|
316
|
-
const expandedText = text.replace(/\t/g, " ".repeat(8));
|
|
423
|
+
const expandedText = text.replace(/\t/g, " ".repeat(8)).replace(/\x1B\[1G/g, '').replace(/\x1B\[0K/g, '');
|
|
317
424
|
const width = stringWidth(expandedText);
|
|
318
425
|
if (width > availableWidth && availableWidth > 3) {
|
|
319
426
|
const truncated = sliceAnsi(expandedText, 0, Math.max(0, availableWidth - 3));
|
|
320
427
|
return truncated + "...";
|
|
321
428
|
}
|
|
429
|
+
// fs.appendFileSync("./temp-logs", text)
|
|
430
|
+
// fs.appendFileSync("./temp-logs", "\n")
|
|
431
|
+
// fs.appendFileSync("./temp-logs", expandedText)
|
|
322
432
|
return expandedText;
|
|
323
433
|
};
|
|
324
434
|
// Keep logLines purely tied to stored processed lines
|
|
325
435
|
const logLines = useMemo(() => rawLogData, [rawLogData]);
|
|
436
|
+
// Prefer a known-good shell over a potentially broken $SHELL on some machines
|
|
437
|
+
function getSafeShell() {
|
|
438
|
+
const candidates = [
|
|
439
|
+
process.env.SHELL, // user-configured shell
|
|
440
|
+
"/bin/zsh",
|
|
441
|
+
"/bin/bash",
|
|
442
|
+
].filter(Boolean);
|
|
443
|
+
for (const shell of candidates) {
|
|
444
|
+
try {
|
|
445
|
+
// Synchronously test if the shell is executable by spawning a no-op command.
|
|
446
|
+
// Use child_process directly to avoid triggering node-pty in this probe.
|
|
447
|
+
const { spawnSync } = require("child_process");
|
|
448
|
+
const res = spawnSync(shell, ["-c", "echo"], { stdio: "ignore" });
|
|
449
|
+
if (res.status === 0) {
|
|
450
|
+
return shell;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
catch {
|
|
454
|
+
// ignore and try next candidate
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
// Fallback to a generic bash lookup; node-pty will still error if it's truly missing,
|
|
458
|
+
// but this avoids depending on a bad $SHELL value.
|
|
459
|
+
return "bash";
|
|
460
|
+
}
|
|
326
461
|
// Stable function to run bash command: does NOT depend on terminalCols or LOGS_HEIGHT
|
|
327
462
|
const runBashCommandWithPipe = useCallback((command) => {
|
|
328
|
-
const shell = process.env.SHELL || "
|
|
463
|
+
const shell = process.env.SHELL || "/bin/zsh";
|
|
329
464
|
const cols = Math.max(10, Math.floor(terminalColsRef.current / 2) - 6);
|
|
330
465
|
const rows = Math.max(1, logsHeightRef.current - 2);
|
|
331
466
|
const ptyProcess = ptySpawn(shell, ["-c", command], {
|
|
@@ -335,6 +470,8 @@ export const App = () => {
|
|
|
335
470
|
rows: rows,
|
|
336
471
|
});
|
|
337
472
|
ptyProcess.onData((chunk) => {
|
|
473
|
+
setUnTamperedLogs((oldLines) => oldLines + chunk);
|
|
474
|
+
logsManager.addChunk(chunk);
|
|
338
475
|
let data = partialLine.current + chunk;
|
|
339
476
|
const lines = data.split("\n");
|
|
340
477
|
partialLine.current = lines.pop() || "";
|
|
@@ -390,32 +527,53 @@ export const App = () => {
|
|
|
390
527
|
}
|
|
391
528
|
};
|
|
392
529
|
}, [runBashCommandWithPipe]);
|
|
530
|
+
function generateArchitecture(projects) {
|
|
531
|
+
let res = "";
|
|
532
|
+
function avaiableData(project) {
|
|
533
|
+
if (project.code_available && project.logs_available) {
|
|
534
|
+
return `- codebase
|
|
535
|
+
- logs`;
|
|
536
|
+
}
|
|
537
|
+
if (project.code_available)
|
|
538
|
+
return `- codebase`;
|
|
539
|
+
if (project.logs_available)
|
|
540
|
+
return `- logs`;
|
|
541
|
+
}
|
|
542
|
+
projects.forEach((project) => {
|
|
543
|
+
res += `
|
|
544
|
+
id: ${project.window_id}
|
|
545
|
+
service_name: ${project.name}
|
|
546
|
+
service_description: ${project.description}
|
|
547
|
+
available_data:
|
|
548
|
+
${avaiableData(project)}
|
|
549
|
+
`;
|
|
550
|
+
});
|
|
551
|
+
return res;
|
|
552
|
+
}
|
|
393
553
|
async function userMessageSubmitted() {
|
|
394
554
|
if (!chatInput.trim())
|
|
395
555
|
return;
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
556
|
+
// const lastMessage = chatResponseMessages[chatResponseMessages.length - 1];
|
|
557
|
+
const logs = chatResponseMessages.length <= 0 ? getLast50Lines(unTamperedLogs) : "";
|
|
558
|
+
// if (lastMessage?.type === "response" && lastMessage?.data?.state) {
|
|
559
|
+
// sendQuery(chatInput, logs, lastMessage.data.state);
|
|
560
|
+
// } else if (lastMessage?.type === "ask_user" && lastMessage?.data?.state) {
|
|
561
|
+
// sendQuery(chatInput, logs, lastMessage?.data?.state);
|
|
562
|
+
// } else {
|
|
563
|
+
// sendQuery(chatInput, logs);
|
|
564
|
+
// }
|
|
565
|
+
const userMessage = new HumanMessage(chatInput);
|
|
566
|
+
const projects = await fetchProjectsFromCluster(loadProjectMetadata());
|
|
567
|
+
sendQuery([...chatResponseMessages, userMessage], generateArchitecture(projects.projects), logs, planningDoc);
|
|
400
568
|
setChatResponseMessages((prev) => [
|
|
401
569
|
...prev,
|
|
402
|
-
|
|
570
|
+
userMessage
|
|
403
571
|
]);
|
|
404
572
|
setTrimmedChats((prev) => [
|
|
405
573
|
...prev,
|
|
406
|
-
|
|
574
|
+
userMessage
|
|
407
575
|
]);
|
|
408
|
-
|
|
409
|
-
const logs = getLast50Lines(currentLogDataString);
|
|
410
|
-
if (lastMessage?.type === "response" && lastMessage?.data?.state) {
|
|
411
|
-
sendQuery(chatInput, logs, lastMessage.data.state);
|
|
412
|
-
}
|
|
413
|
-
else if (lastMessage?.type === "ask_user" && lastMessage?.data?.state) {
|
|
414
|
-
sendQuery(chatInput, logs, lastMessage?.data?.state);
|
|
415
|
-
}
|
|
416
|
-
else {
|
|
417
|
-
sendQuery(chatInput, logs);
|
|
418
|
-
}
|
|
576
|
+
setVisibleChats(prev => [...prev, userMessage]);
|
|
419
577
|
setChatInput("");
|
|
420
578
|
}
|
|
421
579
|
useInput((inputStr, key) => {
|
|
@@ -431,6 +589,10 @@ export const App = () => {
|
|
|
431
589
|
setLogScroll(0);
|
|
432
590
|
return;
|
|
433
591
|
}
|
|
592
|
+
if (key.ctrl && inputStr === "a") {
|
|
593
|
+
setVisibleChats(chatResponseMessages);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
434
596
|
if (key.ctrl && inputStr === "k") {
|
|
435
597
|
setChatResponseMessages([]);
|
|
436
598
|
setChatScroll(0);
|
|
@@ -441,6 +603,7 @@ export const App = () => {
|
|
|
441
603
|
setChatResponseMessages(() => []);
|
|
442
604
|
setTrimmedChats(() => []);
|
|
443
605
|
setCompleteChatHistory(() => []);
|
|
606
|
+
setGraphState(null);
|
|
444
607
|
setSocketId[""];
|
|
445
608
|
socket?.close();
|
|
446
609
|
setIsConnected(false);
|
|
@@ -495,13 +658,13 @@ export const App = () => {
|
|
|
495
658
|
setChatInput(input);
|
|
496
659
|
}, []);
|
|
497
660
|
if (mode === "COPY") {
|
|
498
|
-
return (_jsxs(Box, { flexDirection: "column", height: terminalRows, width: "100%", padding: 0, overflow: "hidden", children: [_jsx(Box, { flexDirection: "row", width: "100%", height: LOGS_HEIGHT, padding: 0, overflow: "hidden", children: _jsxs(BorderBoxNoBorder, { title: "AI Chat", isFocused: activePane === "chat", width: "100%", children: [!API_KEY ? (_jsxs(Text, { children: ["Auth key not found.", "\n", "Login using:", " ", _jsx(Text, { color: "blue", children: "
|
|
661
|
+
return (_jsxs(Box, { flexDirection: "column", height: terminalRows, width: "100%", padding: 0, overflow: "hidden", children: [_jsx(Box, { flexDirection: "row", width: "100%", height: LOGS_HEIGHT, padding: 0, overflow: "hidden", children: _jsxs(BorderBoxNoBorder, { title: "AI Chat", isFocused: activePane === "chat", width: "100%", children: [!API_KEY ? (_jsxs(Text, { children: ["Auth key not found.", "\n", "Login using:", " ", _jsx(Text, { color: "blue", children: "oncall login <your-auth-key>" })] })) : isConnected ? (_jsx(ScrollableContentChat, { lines: chatLinesChat, maxHeight: CHAT_HISTORY_HEIGHT - 1, isFocused: activePane === "chat", scrollOffset: chatScroll, onScrollChange: setChatScroll, isLoading: isLoading, showControlR: showControlR, customMessage: customMessage })) : (_jsx(Text, { children: "AI Chat not connected" })), isConnected && (_jsxs(Box, { borderStyle: "round", borderColor: activePane === "input" ? "greenBright" : "white", paddingX: 1, width: "100%", overflow: "hidden", children: [_jsx(Text, { color: "white", bold: activePane === "input", children: "Input:" }), _jsx(TextInput, { placeholder: "What's bugging you today?", value: chatInput, onChange: handleInputChange, onSubmit: userMessageSubmitted, focus: activePane === "input" })] }))] }) }), _jsx(ShortcutsFooter, { shortcuts: shortcuts })] }));
|
|
499
662
|
}
|
|
500
663
|
else if (mode === "LOGS") {
|
|
501
664
|
return (_jsxs(Box, { flexDirection: "column", height: terminalRows, width: "100%", padding: 0, overflow: "hidden", children: [_jsx(Box, { flexDirection: "row", width: "100%", height: LOGS_HEIGHT, padding: 0, overflow: "hidden", children: _jsx(BorderBoxNoBorder, { title: "Command Logs", isFocused: activePane === "chat", width: "100%", children: _jsx(ScrollableContent, { lines: logLines, maxHeight: LOGS_HEIGHT - 2, isFocused: activePane === "logs", scrollOffset: logScroll, onScrollChange: setLogScroll }) }) }), _jsx(ShortcutsFooter, { shortcuts: shortcuts })] }));
|
|
502
665
|
}
|
|
503
666
|
else {
|
|
504
|
-
return (_jsxs(Box, { flexDirection: "column", height: terminalRows, width: "100%", padding: 0, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "row", width: "100%", height: LOGS_HEIGHT, padding: 0, overflow: "hidden", children: [_jsx(BorderBox, { title: "Command Logs", isFocused: activePane === "logs", width: "50%", children: _jsx(ScrollableContent, { lines: logLines, maxHeight: LOGS_HEIGHT - 2, isFocused: activePane === "logs", scrollOffset: logScroll, onScrollChange: setLogScroll }) }), _jsxs(BorderBox, { title: "AI Chat", isFocused: activePane === "chat", width: "50%", children: [!API_KEY ? (_jsxs(Text, { children: ["Auth key not found.", "\n", "Login using:", " ", _jsx(Text, { color: "blue", children: "
|
|
667
|
+
return (_jsxs(Box, { flexDirection: "column", height: terminalRows, width: "100%", padding: 0, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "row", width: "100%", height: LOGS_HEIGHT, padding: 0, overflow: "hidden", children: [_jsx(BorderBox, { title: "Command Logs", isFocused: activePane === "logs", width: "50%", children: _jsx(ScrollableContent, { lines: logLines, maxHeight: LOGS_HEIGHT - 2, isFocused: activePane === "logs", scrollOffset: logScroll, onScrollChange: setLogScroll }) }), _jsxs(BorderBox, { title: "AI Chat", isFocused: activePane === "chat", width: "50%", children: [!API_KEY ? (_jsxs(Text, { children: ["Auth key not found.", "\n", "Login using:", " ", _jsx(Text, { color: "blue", children: "oncall login <your-auth-key>" })] })) : isConnected ? (_jsx(ScrollableContentChat, { lines: chatLinesChat, maxHeight: CHAT_HISTORY_HEIGHT - 1, isFocused: activePane === "chat", scrollOffset: chatScroll, onScrollChange: setChatScroll, isLoading: isLoading, showControlR: showControlR, customMessage: customMessage })) : (_jsx(Text, { children: "AI Chat not connected" })), isConnected && (_jsxs(Box, { borderStyle: "round", borderColor: activePane === "input" ? "greenBright" : "white", paddingX: 1, width: "100%", overflow: "hidden", children: [_jsx(Text, { color: "white", bold: activePane === "input", children: "Input:" }), _jsx(TextInput, { placeholder: "What's bugging you today?", value: chatInput, onChange: handleInputChange, onSubmit: userMessageSubmitted, focus: activePane === "input" })] }))] })] }), _jsx(ShortcutsFooter, { shortcuts: shortcuts })] }));
|
|
505
668
|
}
|
|
506
669
|
};
|
|
507
670
|
render(_jsx(App, {}));
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Singleton Logs Manager
|
|
3
|
+
* Maintains logs data
|
|
4
|
+
*/
|
|
5
|
+
export declare class LogsManager {
|
|
6
|
+
private static instance;
|
|
7
|
+
private logs;
|
|
8
|
+
private constructor();
|
|
9
|
+
/**
|
|
10
|
+
* Get the singleton instance
|
|
11
|
+
*/
|
|
12
|
+
static getInstance(): LogsManager;
|
|
13
|
+
/**
|
|
14
|
+
* Append Logs
|
|
15
|
+
* @param chunk
|
|
16
|
+
*/
|
|
17
|
+
addChunk(chunk: string): void;
|
|
18
|
+
/**
|
|
19
|
+
* Retrieves logs
|
|
20
|
+
*/
|
|
21
|
+
getLogs(): string;
|
|
22
|
+
getTailLogs(n: number): string;
|
|
23
|
+
getGrepLogs(pattern: string, before?: number, after?: number): string;
|
|
24
|
+
getRecentErrors(n: number): string;
|
|
25
|
+
/**
|
|
26
|
+
* Clear all logs
|
|
27
|
+
*/
|
|
28
|
+
clearAll(): void;
|
|
29
|
+
}
|
|
30
|
+
declare const _default: LogsManager;
|
|
31
|
+
export default _default;
|