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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +172 -0
  3. package/bin/oncall.js +396 -0
  4. package/dist/api.js +10 -1
  5. package/dist/cli.js +20 -20
  6. package/dist/config.js +7 -7
  7. package/dist/helpers/cli-helpers.d.ts +25 -0
  8. package/dist/helpers/cli-helpers.js +329 -0
  9. package/dist/helpers/config-helpers.js +189 -0
  10. package/dist/helpers/ripgrep-tool.d.ts +15 -0
  11. package/dist/helpers/ripgrep-tool.js +126 -0
  12. package/dist/index.js +225 -62
  13. package/dist/logsManager.d.ts +31 -0
  14. package/dist/logsManager.js +90 -0
  15. package/dist/postinstall.js +20 -0
  16. package/dist/tools/ripgrep.js +3 -3
  17. package/dist/useWebSocket.d.ts +14 -6
  18. package/dist/useWebSocket.js +290 -45
  19. package/dist/utils/version-check.d.ts +2 -0
  20. package/dist/utils/version-check.js +124 -0
  21. package/dist/utils.d.ts +16 -0
  22. package/dist/utils.js +125 -4
  23. package/dist/websocket-server.d.ts +24 -0
  24. package/dist/websocket-server.js +235 -0
  25. package/package.json +18 -5
  26. package/bin/clippy.js +0 -109
  27. package/dist/api.js.map +0 -1
  28. package/dist/cli.js.map +0 -1
  29. package/dist/code_hierarchy.js.map +0 -1
  30. package/dist/config.js.map +0 -1
  31. package/dist/index.js.map +0 -1
  32. package/dist/tools/code_hierarchy.d.ts +0 -8
  33. package/dist/tools/code_hierarchy.js +0 -78
  34. package/dist/tools/code_hierarchy.js.map +0 -1
  35. package/dist/tools/fetch_context.d.ts +0 -1
  36. package/dist/tools/fetch_context.js +0 -8
  37. package/dist/tools/fetch_context.js.map +0 -1
  38. package/dist/tools/ripgrep.js.map +0 -1
  39. package/dist/ui-graph.js.map +0 -1
  40. package/dist/useWebSocket.js.map +0 -1
  41. 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: false,
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, setChatResponseMessages, isConnected, isLoading, setIsLoading, setTrimmedChats, API_KEY, connectionError, setSocketId, setIsConnected, socket, setShowControlR, showControlR, setCompleteChatHistory, } = useWebSocket(config.websocket_url);
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
- if (chatResponseMessages.length === 0)
278
+ // Use visibleChats for UI-related checks
279
+ const messagesToCheck = visibleChats || chatResponseMessages;
280
+ if (messagesToCheck.length === 0)
262
281
  return;
263
- const lastMessage = chatResponseMessages[chatResponseMessages.length - 1];
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 (lastMessage?.type === "response" ||
266
- lastMessage?.type === "progress" ||
267
- lastMessage?.type === "ask_user" ||
268
- lastMessage?.type === "error") {
269
- setTimeout(() => setActivePane("chat"), 600);
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
- if (lastMessage && lastMessage.type && lastMessage?.type === "response") {
272
- setShowControlR(true);
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
- else {
275
- setShowControlR(false);
325
+ function extratMessageContent(messages) {
326
+ return messages.reduce((prev, message) => {
327
+ return prev + message.content;
328
+ }, "");
276
329
  }
277
- }, [chatResponseMessages]);
278
- const chatLinesChat = useMemo(() => {
279
- const availableWidth = Math.floor(terminalColsRef.current / 2) - 6;
280
- return chatResponseMessages.flatMap((msg, index) => {
281
- const prefix = `${msg.type === "user" ? "🧑" : "🤖"} `;
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
- const extracted = extractAIMessages(msg);
284
- const content = msg.type === "progress"
285
- ? Array.isArray(extracted)
286
- ? extracted.join("\n")
287
- : extracted || ""
288
- : msg.message ||
289
- msg?.data?.finalMessage ||
290
- msg?.data?.message ||
291
- JSON.stringify(msg, null, 2);
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
- const contentLines = content?.split("\n");
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
- if (msg.type === "user") {
304
- lines.push({
305
- key: `chat-${index}-spacer`,
306
- text: " ",
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
- }, [chatResponseMessages]);
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 || "bash";
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 userMessage = {
397
- type: "user",
398
- message: chatInput,
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
- JSON.parse(JSON.stringify(userMessage)),
570
+ userMessage
403
571
  ]);
404
572
  setTrimmedChats((prev) => [
405
573
  ...prev,
406
- JSON.parse(JSON.stringify(userMessage)),
574
+ userMessage
407
575
  ]);
408
- const lastMessage = chatResponseMessages[chatResponseMessages.length - 1];
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: "clippy 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 })) : (_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 })] }));
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: "clippy 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 })) : (_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 })] }));
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;