clippy-test 1.0.8 → 2.0.0
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 +318 -122
- package/dist/logsManager.d.ts +31 -0
- package/dist/logsManager.js +90 -0
- package/dist/postinstall.js +20 -0
- package/dist/tools/ripgrep.d.ts +15 -0
- package/dist/tools/ripgrep.js +110 -0
- package/dist/useWebSocket.d.ts +14 -7
- package/dist/useWebSocket.js +291 -48
- 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 +19 -6
- 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/ui-graph.js.map +0 -1
- package/dist/useWebSocket.js.map +0 -1
- package/dist/utils.js.map +0 -1
package/dist/useWebSocket.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import WebSocket from "ws";
|
|
2
3
|
import { getContextLines } from "./utils.js";
|
|
3
4
|
import { toolFunctionCall } from "./api.js";
|
|
4
5
|
import fs from "fs";
|
|
5
6
|
import os from "os";
|
|
6
7
|
import path from "path";
|
|
7
|
-
|
|
8
|
+
import { mapChatMessagesToStoredMessages, mapStoredMessagesToChatMessages, SystemMessage, } from "@langchain/core/messages";
|
|
9
|
+
import { ripgrepSearch } from "./helpers/ripgrep-tool.js";
|
|
10
|
+
import { loadProjectMetadata, logd } from "./helpers/cli-helpers.js";
|
|
11
|
+
// Load OnCall config from ~/.oncall/config
|
|
8
12
|
const HOME_DIR = os.homedir();
|
|
9
|
-
const
|
|
10
|
-
const CONFIG_PATH = path.join(
|
|
13
|
+
const ONCALL_DIR = path.join(HOME_DIR, ".oncall");
|
|
14
|
+
const CONFIG_PATH = path.join(ONCALL_DIR, "config");
|
|
11
15
|
let API_KEY = "";
|
|
12
16
|
try {
|
|
13
17
|
const configText = fs.readFileSync(CONFIG_PATH, "utf8");
|
|
@@ -16,34 +20,54 @@ try {
|
|
|
16
20
|
API_KEY = match[1].trim();
|
|
17
21
|
}
|
|
18
22
|
else {
|
|
19
|
-
console.log("No API_KEY found in ~/.
|
|
23
|
+
console.log("No API_KEY found in ~/.oncall/config");
|
|
20
24
|
}
|
|
21
25
|
}
|
|
22
26
|
catch (err) {
|
|
23
|
-
console.log("Failed to read
|
|
27
|
+
console.log("Failed to read OnCall config:", err);
|
|
24
28
|
}
|
|
25
|
-
export function useWebSocket(url) {
|
|
29
|
+
export function useWebSocket(url, rawLogData) {
|
|
26
30
|
//refs
|
|
27
31
|
const socketRef = useRef(null);
|
|
28
32
|
const [socketId, setSocketId] = useState(null);
|
|
29
33
|
const [chatResponseMessages, setChatResponseMessages] = useState([]);
|
|
30
34
|
const [trimmedChats, setTrimmedChats] = useState([]);
|
|
31
|
-
const
|
|
35
|
+
const initialAssistantMessage = new SystemMessage("I’m watching your app run locally. You can ask me about errors, logs, performance,or anything else related to this run.");
|
|
36
|
+
const [visibleChats, setVisibleChats] = useState([
|
|
37
|
+
initialAssistantMessage,
|
|
38
|
+
]);
|
|
32
39
|
const [isConnected, setIsConnected] = useState(false);
|
|
33
40
|
const [connectionError, setConnectionError] = useState(null);
|
|
34
41
|
const [isLoading, setIsLoading] = useState(false);
|
|
35
|
-
const [isControlRPressed, setIsControlRPressed] = useState(false);
|
|
36
42
|
const [showControlR, setShowControlR] = useState(false);
|
|
43
|
+
const [customMessage, setCustomMessage] = useState(null);
|
|
44
|
+
const [graphState, setGraphState] = useState(null);
|
|
37
45
|
const authKey = API_KEY;
|
|
46
|
+
const initialProjectMetadata = loadProjectMetadata();
|
|
47
|
+
const getProjectMetadata = () => loadProjectMetadata() || initialProjectMetadata;
|
|
48
|
+
const getServiceId = () => getProjectMetadata()?.window_id;
|
|
49
|
+
const hasLogsAccess = () => {
|
|
50
|
+
const value = getProjectMetadata()?.logs_available;
|
|
51
|
+
return value === undefined ? true : value;
|
|
52
|
+
};
|
|
53
|
+
const hasCodeAccess = () => {
|
|
54
|
+
const value = getProjectMetadata()?.code_available;
|
|
55
|
+
return value === undefined ? true : value;
|
|
56
|
+
};
|
|
38
57
|
useEffect(() => {
|
|
39
58
|
if (API_KEY === "") {
|
|
40
|
-
setConnectionError("No API_KEY found in ~/.
|
|
41
|
-
console.log("No API_KEY found in ~/.
|
|
59
|
+
setConnectionError("No API_KEY found in ~/.oncall/config");
|
|
60
|
+
console.log("No API_KEY found in ~/.oncall/config");
|
|
42
61
|
process.exit();
|
|
43
62
|
}
|
|
44
63
|
}, [API_KEY]);
|
|
45
64
|
//web socket connection
|
|
46
65
|
const connectWebSocket = useCallback(() => {
|
|
66
|
+
// const d = fs.readFileSync('logs')
|
|
67
|
+
// fs.writeFileSync('logs', `${d} inside connectWS \n message: ${url} ${authKey} ${serviceId}`)
|
|
68
|
+
// if (!url || !authKey || serviceId.trim() === "") {
|
|
69
|
+
// return;
|
|
70
|
+
// }
|
|
47
71
|
if (socketRef.current) {
|
|
48
72
|
socketRef.current.close();
|
|
49
73
|
}
|
|
@@ -53,14 +77,24 @@ export function useWebSocket(url) {
|
|
|
53
77
|
setIsConnected(false);
|
|
54
78
|
socket.onopen = () => {
|
|
55
79
|
try {
|
|
56
|
-
|
|
80
|
+
const md = getProjectMetadata();
|
|
81
|
+
if (!md?.window_id) {
|
|
82
|
+
setConnectionError("Missing serviceId (window_id) in oncall.yaml");
|
|
83
|
+
console.log("Missing serviceId (window_id) in oncall.yaml");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
socket.send(JSON.stringify({
|
|
87
|
+
type: "recurring_connection",
|
|
88
|
+
authKey,
|
|
89
|
+
serviceId: md.window_id,
|
|
90
|
+
}));
|
|
57
91
|
}
|
|
58
92
|
catch (error) {
|
|
59
93
|
setConnectionError("error");
|
|
60
94
|
}
|
|
61
95
|
};
|
|
62
96
|
socket.onmessage = async (event) => {
|
|
63
|
-
setShowControlR(false);
|
|
97
|
+
// setShowControlR(false);
|
|
64
98
|
try {
|
|
65
99
|
const raw = event.data;
|
|
66
100
|
let text = "";
|
|
@@ -84,7 +118,8 @@ export function useWebSocket(url) {
|
|
|
84
118
|
return; // ignore non-JSON frames
|
|
85
119
|
}
|
|
86
120
|
const data = JSON.parse(text);
|
|
87
|
-
if (data.type === "user_assigned" && data.socketId) {
|
|
121
|
+
// if (data.type === "user_assigned" && data.socketId) {
|
|
122
|
+
if (data.type === "user_assigned") {
|
|
88
123
|
setIsConnected(true);
|
|
89
124
|
setSocketId(data.socketId);
|
|
90
125
|
}
|
|
@@ -100,46 +135,126 @@ export function useWebSocket(url) {
|
|
|
100
135
|
return;
|
|
101
136
|
}
|
|
102
137
|
if (data.type === "tool_function_call") {
|
|
103
|
-
if (data.function_name === "
|
|
138
|
+
if (data.function_name === "read_file") {
|
|
139
|
+
logd(`[read_file] 📥 Tool call received: tool_call_id=${data.tool_call_id}, args=${JSON.stringify(data.args)}`);
|
|
140
|
+
if (!hasCodeAccess()) {
|
|
141
|
+
logd(`[read_file] ⚠️ No code access, denying tool`);
|
|
142
|
+
await denyToolAccess("read_file", data);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
104
145
|
const argsData = {
|
|
105
|
-
|
|
146
|
+
filePath: data.args.filePath,
|
|
106
147
|
lineNumber: data.args.lineNumber,
|
|
107
|
-
before: data.args.before,
|
|
108
|
-
after: data.args.after,
|
|
148
|
+
before: data.args.before || 30,
|
|
149
|
+
after: data.args.after || 30,
|
|
109
150
|
};
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
151
|
+
logd(`[read_file] 📋 Extracted args: filePath=${argsData.filePath}, lineNumber=${argsData.lineNumber}, before=${argsData.before}, after=${argsData.after}`);
|
|
152
|
+
logd(`[read_file] 🔍 Calling getContextLines...`);
|
|
153
|
+
const result = getContextLines(argsData.filePath, argsData.lineNumber, argsData.before, argsData.after);
|
|
154
|
+
logd(`[read_file] ✅ File content extracted: resultLength=${result.length}, resultLines=${result.split("\n").length}, isEmpty=${result.trim().length === 0}`);
|
|
155
|
+
logd(`[read_file] 📄 Result preview (first 300 chars): ${result.substring(0, 300)}${result.length > 300 ? "..." : ""}`);
|
|
156
|
+
await postToolCallResult(data, result, setChatResponseMessages);
|
|
157
|
+
logd(`[read_file] 📤 Result sent to backend successfully`);
|
|
158
|
+
// await redisClient.set(data.tool_call_id, result, { EX: 120 });
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (data.function_name === "grep_search") {
|
|
162
|
+
if (!hasCodeAccess()) {
|
|
163
|
+
await denyToolAccess("grep_search", data);
|
|
164
|
+
return;
|
|
113
165
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
166
|
+
await grepSearch(data.args.searchTerm, data);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (data.function_name === "read_logs") {
|
|
170
|
+
if (!hasLogsAccess()) {
|
|
171
|
+
await denyToolAccess("read_logs", data);
|
|
172
|
+
return;
|
|
119
173
|
}
|
|
120
|
-
|
|
174
|
+
await readLogs(data.args.pageNumber, data);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (data.function_name === "tail_logs") {
|
|
178
|
+
if (!hasLogsAccess()) {
|
|
179
|
+
await denyToolAccess("tail_logs", data);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
await tailLogs(data.args.n, data);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (data.function_name === "grep_logs") {
|
|
186
|
+
if (!hasLogsAccess()) {
|
|
187
|
+
await denyToolAccess("grep_logs", data);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
await grepLogs(data.args.pattern, data.args.before, data.args.after, data);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (data.function_name === "get_recent_errors") {
|
|
194
|
+
if (!hasLogsAccess()) {
|
|
195
|
+
await denyToolAccess("get_recent_errors", data);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
await getRecentErrors(data.args.n, data);
|
|
199
|
+
return;
|
|
121
200
|
}
|
|
122
|
-
return;
|
|
123
201
|
}
|
|
124
|
-
|
|
125
|
-
|
|
202
|
+
// trimmed - imp msgs
|
|
203
|
+
// visible - shown
|
|
204
|
+
// chatresponses - all
|
|
126
205
|
// add non-progress responses to trimmedChats
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
206
|
+
// @todo this is where the messages get trimmed
|
|
207
|
+
// if (data.type !== "progress") {
|
|
208
|
+
// setTrimmedChats((prevTrimmed) => {
|
|
209
|
+
// const updated = [...prevTrimmed, data];
|
|
210
|
+
// // update visible chats with all trimmed messages
|
|
211
|
+
// setVisibleChats(updated);
|
|
212
|
+
// return updated;
|
|
213
|
+
// });
|
|
214
|
+
// }
|
|
215
|
+
if (data.type === "response") {
|
|
216
|
+
let messages = data.data.messages ? data.data.messages : [];
|
|
217
|
+
messages = mapStoredMessagesToChatMessages(messages);
|
|
218
|
+
switch (data.data.type) {
|
|
219
|
+
case "messages":
|
|
220
|
+
if (data.data.sender !== "toolNode") {
|
|
221
|
+
setVisibleChats(old => [...old, ...messages]);
|
|
222
|
+
}
|
|
223
|
+
// logd(`LOGGING RESPONSE SENDER: ${data.data.sender}`);
|
|
224
|
+
break;
|
|
225
|
+
case "values":
|
|
226
|
+
if (data.data.state) {
|
|
227
|
+
setGraphState(data.data.state);
|
|
228
|
+
}
|
|
229
|
+
break;
|
|
230
|
+
case "updates":
|
|
231
|
+
if (data.data.sender !== "toolNode" && data.data.sender !== "routerNode") {
|
|
232
|
+
setChatResponseMessages(old => [...old, ...messages]);
|
|
233
|
+
}
|
|
234
|
+
if (data.data.sender === "answerNode") {
|
|
235
|
+
setTrimmedChats(prev => {
|
|
236
|
+
setVisibleChats([...prev, ...messages]);
|
|
237
|
+
return [...prev, ...messages];
|
|
238
|
+
});
|
|
239
|
+
setIsLoading(false);
|
|
240
|
+
}
|
|
241
|
+
if (data.data.sender === "userNode") {
|
|
242
|
+
setIsLoading(false);
|
|
243
|
+
}
|
|
244
|
+
break;
|
|
245
|
+
default:
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
134
248
|
}
|
|
135
|
-
if (data.type === "
|
|
136
|
-
data.type === "error" ||
|
|
249
|
+
if (data.type === "error" ||
|
|
137
250
|
data.type === "ask_user") {
|
|
138
251
|
setIsLoading(false);
|
|
252
|
+
setCustomMessage(null);
|
|
139
253
|
}
|
|
140
254
|
}
|
|
141
255
|
catch (error) {
|
|
142
|
-
|
|
256
|
+
setIsLoading(false);
|
|
257
|
+
// console.warn("WebSocket message handling warning:", error);
|
|
143
258
|
return;
|
|
144
259
|
}
|
|
145
260
|
};
|
|
@@ -160,34 +275,160 @@ export function useWebSocket(url) {
|
|
|
160
275
|
// };
|
|
161
276
|
// }
|
|
162
277
|
}, [url, authKey]);
|
|
163
|
-
const sendQuery = useCallback((
|
|
278
|
+
const sendQuery = useCallback((messages, architecture, logs, planningDoc) => {
|
|
164
279
|
const socket = socketRef.current;
|
|
165
280
|
if (socket && socket.readyState === WebSocket.OPEN) {
|
|
166
281
|
setIsLoading(true);
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
282
|
+
setCustomMessage("🤔 Thinking...");
|
|
283
|
+
const md = getProjectMetadata();
|
|
284
|
+
const payload = {
|
|
285
|
+
type: "query",
|
|
286
|
+
authKey,
|
|
287
|
+
serviceId: md?.window_id,
|
|
288
|
+
userQuery: {
|
|
289
|
+
messages: graphState
|
|
290
|
+
? mapChatMessagesToStoredMessages([messages[messages.length - 1]])
|
|
291
|
+
: mapChatMessagesToStoredMessages(messages),
|
|
292
|
+
architecture: architecture,
|
|
293
|
+
logs,
|
|
294
|
+
planningDoc: "",
|
|
295
|
+
},
|
|
296
|
+
planningDoc,
|
|
297
|
+
graphState: graphState || undefined,
|
|
298
|
+
};
|
|
170
299
|
socket.send(JSON.stringify(payload));
|
|
171
300
|
}
|
|
172
301
|
else {
|
|
173
302
|
setConnectionError("WebSocket not connected");
|
|
174
303
|
throw new Error("Cannot send the message: Web socket not connected");
|
|
175
304
|
}
|
|
176
|
-
}, [socketId]);
|
|
305
|
+
}, [socketId, graphState]);
|
|
177
306
|
// Clean up on unmount
|
|
178
307
|
useEffect(() => {
|
|
179
308
|
return () => {
|
|
180
309
|
if (socketRef.current) {
|
|
181
|
-
socketRef.current.close();
|
|
310
|
+
// socketRef.current.close();
|
|
182
311
|
}
|
|
183
312
|
};
|
|
184
313
|
}, []);
|
|
314
|
+
async function grepSearch(searchTerm, data) {
|
|
315
|
+
logd(`[grepSearch] 🔍 Grep search invoked: searchTerm=${searchTerm}, tool_call_id=${data.tool_call_id}, args=${JSON.stringify(data.args)}`);
|
|
316
|
+
const projectMetadata = getProjectMetadata();
|
|
317
|
+
const workingDirectory = projectMetadata?.path || process.cwd();
|
|
318
|
+
logd(`[grepSearch] 📁 Working directory: workingDirectory=${workingDirectory}, projectPath=${projectMetadata?.path || 'N/A'}, processCwd=${process.cwd()}`);
|
|
319
|
+
const maxResults = data.args?.max_results || 20;
|
|
320
|
+
const caseSensitive = data.args?.case_sensitive || false;
|
|
321
|
+
const fileTypes = data.args?.file_types || [];
|
|
322
|
+
logd(`[grepSearch] ⚙️ Search options: maxResults=${maxResults}, caseSensitive=${caseSensitive}, fileTypes=${JSON.stringify(fileTypes)}`);
|
|
323
|
+
try {
|
|
324
|
+
const results = await ripgrepSearch(searchTerm, {
|
|
325
|
+
maxResults,
|
|
326
|
+
caseSensitive,
|
|
327
|
+
fileTypes,
|
|
328
|
+
workingDirectory,
|
|
329
|
+
});
|
|
330
|
+
logd(`[grepSearch] ✅ Search completed: resultCount=${results.length}, results=${JSON.stringify(results.map((r) => ({
|
|
331
|
+
filePath: r.filePath,
|
|
332
|
+
line: r.line,
|
|
333
|
+
previewLength: r.preview?.length || 0,
|
|
334
|
+
})))}`);
|
|
335
|
+
await postToolCallResult(data, results, setChatResponseMessages);
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
logd(`[grepSearch] ❌ Error during grep search: ${error instanceof Error ? error.message : String(error)}, stack=${error instanceof Error ? error.stack : "N/A"}`);
|
|
339
|
+
await postToolCallResult(data, [], setChatResponseMessages);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
async function readLogs(pageNumber, data) {
|
|
343
|
+
logd(`Triggered readLogs: ${pageNumber}`);
|
|
344
|
+
pageNumber = parseInt(pageNumber.toString());
|
|
345
|
+
const lines = rawLogData
|
|
346
|
+
.getLogs()
|
|
347
|
+
.split("\n")
|
|
348
|
+
.slice(-50 * pageNumber)
|
|
349
|
+
.join("\n");
|
|
350
|
+
logd(`Triggered readLogs - Posting result: ${lines.length}`);
|
|
351
|
+
await postToolCallResult(data, lines, setChatResponseMessages);
|
|
352
|
+
logd(`Triggered readLogs - done Posting result`);
|
|
353
|
+
}
|
|
354
|
+
async function tailLogs(n, data) {
|
|
355
|
+
try {
|
|
356
|
+
logd(`Triggered tailLogs: ${n}`);
|
|
357
|
+
n = parseInt(n.toString());
|
|
358
|
+
if (Number.isNaN(n) || n <= 0) {
|
|
359
|
+
await postToolCallResult(data, "Invalid value for n. Please provide a positive number of log lines to fetch.", setChatResponseMessages);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const tail = rawLogData.getTailLogs(n);
|
|
363
|
+
logd(`Triggered tailLogs - Posting result: ${tail.length}`);
|
|
364
|
+
await postToolCallResult(data, tail, setChatResponseMessages);
|
|
365
|
+
logd(`Triggered tailLogs - done Posting result`);
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
logd(`Triggered tailLogs - error: ${JSON.stringify(error)}`);
|
|
369
|
+
await postToolCallResult(data, "Failed to fetch tail logs from CLI.", setChatResponseMessages);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
async function grepLogs(pattern, before, after, data) {
|
|
373
|
+
try {
|
|
374
|
+
logd(`Triggered grepLogs: pattern=${pattern}, before=${before}, after=${after}`);
|
|
375
|
+
if (!pattern || !pattern.trim()) {
|
|
376
|
+
await postToolCallResult(data, "Invalid pattern for grep_logs. Please provide a non-empty pattern.", setChatResponseMessages);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const beforeCount = before !== undefined ? parseInt(before.toString()) : 5;
|
|
380
|
+
const afterCount = after !== undefined ? parseInt(after.toString()) : 5;
|
|
381
|
+
const result = rawLogData.getGrepLogs(pattern, beforeCount, afterCount);
|
|
382
|
+
await postToolCallResult(data, result, setChatResponseMessages);
|
|
383
|
+
logd(`Triggered grepLogs - done Posting result of length ${result.length}`);
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
logd(`Triggered grepLogs - error: ${JSON.stringify(error)}`);
|
|
387
|
+
await postToolCallResult(data, "Failed to search logs with grep_logs.", setChatResponseMessages);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
async function getRecentErrors(n, data) {
|
|
391
|
+
try {
|
|
392
|
+
logd(`Triggered getRecentErrors: n=${n}`);
|
|
393
|
+
n = parseInt(n.toString());
|
|
394
|
+
if (Number.isNaN(n) || n <= 0) {
|
|
395
|
+
await postToolCallResult(data, "Invalid value for n. Number Provided :" + n.toString(), setChatResponseMessages);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
const result = rawLogData.getRecentErrors(n);
|
|
399
|
+
await postToolCallResult(data, result, setChatResponseMessages);
|
|
400
|
+
logd(`Triggered getRecentErrors - done Posting result of length ${result.length}`);
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
logd(`Triggered getRecentErrors - error: ${JSON.stringify(error)}`);
|
|
404
|
+
await postToolCallResult(data, "Failed to fetch recent error logs.", setChatResponseMessages);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
async function postToolCallResult(data, result, setChatResponseMessages) {
|
|
408
|
+
try {
|
|
409
|
+
await toolFunctionCall(data?.tool_call_id, result, data?.args, "tool_function_call");
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
logd(`Triggered readLogs - failer podting: ${JSON.stringify(error)}`);
|
|
413
|
+
// setChatResponseMessages((prev) => [
|
|
414
|
+
// ...prev,
|
|
415
|
+
// error?.response?.message || "Error, please try again",
|
|
416
|
+
// ]);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
async function denyToolAccess(functionName, data) {
|
|
420
|
+
const serviceId = getServiceId();
|
|
421
|
+
const message = `No Access to execute ${functionName}. with the serviceId of ${serviceId ?? "unknown"}.`;
|
|
422
|
+
await postToolCallResult(data, message, setChatResponseMessages);
|
|
423
|
+
}
|
|
185
424
|
return {
|
|
186
425
|
connectWebSocket,
|
|
187
426
|
socketId,
|
|
188
427
|
sendQuery,
|
|
189
|
-
chatResponseMessages:
|
|
190
|
-
|
|
428
|
+
chatResponseMessages: chatResponseMessages, // Full history including ToolMessage (needed for backend)
|
|
429
|
+
visibleChats: visibleChats, // Filtered for UI display (excludes ToolMessage except reflections)
|
|
430
|
+
setVisibleChats: setVisibleChats,
|
|
431
|
+
setChatResponseMessages,
|
|
191
432
|
setTrimmedChats,
|
|
192
433
|
isConnected,
|
|
193
434
|
connectionError,
|
|
@@ -197,10 +438,12 @@ export function useWebSocket(url) {
|
|
|
197
438
|
setIsConnected,
|
|
198
439
|
socket: socketRef.current,
|
|
199
440
|
setIsLoading,
|
|
200
|
-
setIsControlRPressed,
|
|
201
441
|
setShowControlR,
|
|
202
442
|
showControlR,
|
|
203
443
|
setCompleteChatHistory: setChatResponseMessages,
|
|
444
|
+
customMessage,
|
|
445
|
+
graphState,
|
|
446
|
+
setGraphState,
|
|
204
447
|
};
|
|
205
448
|
}
|
|
206
449
|
//# sourceMappingURL=useWebSocket.js.map
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import axios from "axios";
|
|
6
|
+
const ONCALL_DIR = path.join(os.homedir(), ".oncall");
|
|
7
|
+
const VERSION_CACHE_FILE = path.join(ONCALL_DIR, "version-cache.json");
|
|
8
|
+
const API_BASE_URL = "http://api.oncall.build/v2/api";
|
|
9
|
+
const CACHE_DURATION_MS = 24 * 60 * 60 * 1000;
|
|
10
|
+
function compareVersions(v1, v2) {
|
|
11
|
+
const parts1 = v1.split(".").map(Number);
|
|
12
|
+
const parts2 = v2.split(".").map(Number);
|
|
13
|
+
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
|
14
|
+
const part1 = parts1[i] || 0;
|
|
15
|
+
const part2 = parts2[i] || 0;
|
|
16
|
+
if (part1 < part2)
|
|
17
|
+
return -1;
|
|
18
|
+
if (part1 > part2)
|
|
19
|
+
return 1;
|
|
20
|
+
}
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
function isVersionDeprecated(currentVersion, minimumVersion) {
|
|
24
|
+
return compareVersions(currentVersion, minimumVersion) < 0;
|
|
25
|
+
}
|
|
26
|
+
function readVersionCache() {
|
|
27
|
+
if (fs.existsSync(VERSION_CACHE_FILE)) {
|
|
28
|
+
const content = fs.readFileSync(VERSION_CACHE_FILE, "utf8");
|
|
29
|
+
return JSON.parse(content);
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
function ensureOnCallDir() {
|
|
34
|
+
if (!fs.existsSync(ONCALL_DIR)) {
|
|
35
|
+
fs.mkdirSync(ONCALL_DIR, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function writeVersionCache(cache) {
|
|
39
|
+
ensureOnCallDir();
|
|
40
|
+
fs.writeFileSync(VERSION_CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
41
|
+
}
|
|
42
|
+
function shouldCheckVersion() {
|
|
43
|
+
const cache = readVersionCache();
|
|
44
|
+
if (!cache || !cache.lastCheck) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
const timeSinceLastCheck = now - cache.lastCheck;
|
|
49
|
+
return timeSinceLastCheck >= CACHE_DURATION_MS;
|
|
50
|
+
}
|
|
51
|
+
async function fetchMinimumVersion() {
|
|
52
|
+
try {
|
|
53
|
+
const response = await axios.get(`${API_BASE_URL}/health/minimum-cli-version`);
|
|
54
|
+
if (response.data?.success && response.data?.minimumCliVersion) {
|
|
55
|
+
return response.data.minimumCliVersion;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
console.warn("Failed to check CLI version:", error instanceof Error ? error.message : "Unknown error");
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
function getCurrentVersion() {
|
|
64
|
+
try {
|
|
65
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
66
|
+
const __dirname = path.dirname(__filename);
|
|
67
|
+
const possiblePaths = [
|
|
68
|
+
path.resolve(__dirname, "../package.json"),
|
|
69
|
+
path.resolve(__dirname, "../../package.json"),
|
|
70
|
+
];
|
|
71
|
+
for (const pkgPath of possiblePaths) {
|
|
72
|
+
if (fs.existsSync(pkgPath)) {
|
|
73
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
74
|
+
return pkgJson.version || "0.0.0";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return "0.0.0";
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
return "0.0.0";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function formatDeprecationError(currentVersion, minimumVersion) {
|
|
84
|
+
return `
|
|
85
|
+
⚠️ CLI VERSION DEPRECATED
|
|
86
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
87
|
+
|
|
88
|
+
Your current version: ${currentVersion}
|
|
89
|
+
Minimum required version: ${minimumVersion}
|
|
90
|
+
|
|
91
|
+
Please update to the latest version to continue using OnCall CLI.
|
|
92
|
+
|
|
93
|
+
To update, run:
|
|
94
|
+
npm install -g oncall-cli@latest
|
|
95
|
+
|
|
96
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
99
|
+
export async function checkVersionCompatibility(forceCheck = false) {
|
|
100
|
+
const currentVersion = getCurrentVersion();
|
|
101
|
+
const cache = readVersionCache();
|
|
102
|
+
if (!forceCheck && cache?.lastCheck && !shouldCheckVersion()) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
const minimumVersion = await fetchMinimumVersion();
|
|
106
|
+
if (!minimumVersion) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
writeVersionCache({
|
|
110
|
+
lastCheck: Date.now(),
|
|
111
|
+
});
|
|
112
|
+
if (isVersionDeprecated(currentVersion, minimumVersion)) {
|
|
113
|
+
console.error(formatDeprecationError(currentVersion, minimumVersion));
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
export async function checkVersionAndExit() {
|
|
119
|
+
const isCompatible = await checkVersionCompatibility(true);
|
|
120
|
+
if (!isCompatible) {
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=version-check.js.map
|
package/dist/utils.d.ts
CHANGED
|
@@ -1 +1,17 @@
|
|
|
1
|
+
import { BaseMessage } from "langchain";
|
|
1
2
|
export declare function getContextLines(fileName: string, lineNumber: number, before?: number, after?: number): string;
|
|
3
|
+
export declare function extractMessageContent(messages: BaseMessage[]): string;
|
|
4
|
+
/**
|
|
5
|
+
* Get a unique key for a message for deduplication purposes
|
|
6
|
+
* Uses message.id if available (string), otherwise checks other locations, then creates a fallback key
|
|
7
|
+
*/
|
|
8
|
+
export declare function getMessageKey(message: BaseMessage): string;
|
|
9
|
+
/**
|
|
10
|
+
* Deduplicate messages array by message ID, keeping the last occurrence
|
|
11
|
+
* Preserves message order
|
|
12
|
+
*/
|
|
13
|
+
export declare function deduplicateMessages(messages: BaseMessage[]): BaseMessage[];
|
|
14
|
+
export declare function groupMessageChunks(messages: BaseMessage[]): {
|
|
15
|
+
lastId: string;
|
|
16
|
+
messages: BaseMessage[][];
|
|
17
|
+
};
|