oncall-cli 3.0.4 → 3.0.5
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/bin/oncall.js +74 -13
- package/dist/api.d.ts +1 -0
- package/dist/api.js +11 -0
- package/dist/helpers/ripgrep-tool.js +148 -5
- package/dist/index.js +111 -80
- package/dist/tools/ripgrep-wrapper.d.ts +14 -0
- package/dist/tools/ripgrep-wrapper.js +67 -0
- package/dist/tools/ripgrep.js +3 -2
- package/dist/useWebSocket.js +91 -10
- package/dist/utils/version-check.d.ts +1 -0
- package/dist/utils/version-check.js +63 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +112 -2
- package/package.json +2 -2
- package/patches/ripgrep-node+1.0.0.patch +0 -13
package/bin/oncall.js
CHANGED
|
@@ -28,8 +28,9 @@ import {
|
|
|
28
28
|
import {
|
|
29
29
|
checkVersionAndExit,
|
|
30
30
|
checkVersionCompatibility,
|
|
31
|
+
checkForUpdates,
|
|
31
32
|
} from "../dist/utils/version-check.js";
|
|
32
|
-
|
|
33
|
+
//f7871b3a372bc5e23768ec80a8a5c3f86704788bb2b4eda0
|
|
33
34
|
const __filename = fileURLToPath(import.meta.url);
|
|
34
35
|
const __dirname = path.dirname(__filename);
|
|
35
36
|
|
|
@@ -128,7 +129,24 @@ Check directory permissions and try again.
|
|
|
128
129
|
}
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
|
|
132
|
+
//check for valid auth key
|
|
133
|
+
// const isValidAuthKey = async (authKey) => {
|
|
134
|
+
// try {
|
|
135
|
+
// const isValid = await axios.post(`${BASE_URL}/auth/login`, { authKey });
|
|
136
|
+
// return isValid.data;
|
|
137
|
+
// } catch (error) {
|
|
138
|
+
// if (error.response.message === "Invalid auth key") {
|
|
139
|
+
// console.error(`Authentication failed.
|
|
140
|
+
// The provided auth key is invalid or has expired.
|
|
141
|
+
// Get a new auth key from your OnCall account and try again.
|
|
142
|
+
// `);
|
|
143
|
+
// } else {
|
|
144
|
+
// console.error(" Failed to save auth key:", error);
|
|
145
|
+
// }
|
|
146
|
+
// }
|
|
147
|
+
// };
|
|
148
|
+
|
|
149
|
+
async function handleLogin(authKey) {
|
|
132
150
|
if (!authKey || typeof authKey !== "string" || !authKey.trim()) {
|
|
133
151
|
// console.error("\n❌ Usage: oncall login <your-auth-key>");
|
|
134
152
|
console.error(`Missing auth key.
|
|
@@ -139,20 +157,39 @@ Usage:
|
|
|
139
157
|
process.exit(1);
|
|
140
158
|
}
|
|
141
159
|
try {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
160
|
+
// const isValid = await isValidAuthKey(authKey);
|
|
161
|
+
const isValid = await axios.post(
|
|
162
|
+
`https://api.oncall.build/v2/api/auth/login`,
|
|
163
|
+
{ authKey }
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
if (isValid) {
|
|
167
|
+
ensureConfigDir();
|
|
168
|
+
let contents = "";
|
|
169
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
170
|
+
contents = fs.readFileSync(CONFIG_PATH, "utf8");
|
|
171
|
+
}
|
|
172
|
+
const updatedContents = upsertConfigValue(contents, "API_KEY", authKey);
|
|
173
|
+
fs.writeFileSync(CONFIG_PATH, updatedContents, "utf8");
|
|
174
|
+
console.log("✅ Auth key saved.\n");
|
|
175
|
+
console.log(`Next:
|
|
151
176
|
Initialize a project with:
|
|
152
177
|
oncall init -id "name this project" -m "describe this service"`);
|
|
153
|
-
|
|
178
|
+
process.exit(0);
|
|
179
|
+
} else {
|
|
180
|
+
console.error(" Invalid Key");
|
|
181
|
+
}
|
|
154
182
|
} catch (err) {
|
|
155
|
-
|
|
183
|
+
if (err.response.data.message === "Invalid auth key") {
|
|
184
|
+
console.error(`Authentication failed.
|
|
185
|
+
|
|
186
|
+
The provided auth key is invalid or has expired.
|
|
187
|
+
|
|
188
|
+
Get a new auth key from your OnCall account and try again.
|
|
189
|
+
`);
|
|
190
|
+
} else {
|
|
191
|
+
console.error("❌❌ Failed to save auth key:", err);
|
|
192
|
+
}
|
|
156
193
|
process.exit(1);
|
|
157
194
|
}
|
|
158
195
|
}
|
|
@@ -331,11 +368,13 @@ async function main() {
|
|
|
331
368
|
checkVersionCompatibility();
|
|
332
369
|
process.exit(0);
|
|
333
370
|
}
|
|
371
|
+
|
|
334
372
|
if (args[0] === "init" && (args[1] === "--help" || args[1] === "-h")) {
|
|
335
373
|
printInitHelp();
|
|
336
374
|
checkVersionCompatibility();
|
|
337
375
|
process.exit(0);
|
|
338
376
|
}
|
|
377
|
+
|
|
339
378
|
if (args[0] === "cluster" && (args[1] === "--help" || args[1] === "-h")) {
|
|
340
379
|
printClusterHelp();
|
|
341
380
|
checkVersionCompatibility();
|
|
@@ -349,22 +388,31 @@ async function main() {
|
|
|
349
388
|
|
|
350
389
|
await checkVersionAndExit();
|
|
351
390
|
|
|
391
|
+
// Check for updates asynchronously (non-blocking)
|
|
392
|
+
// This runs in the background and won't block the CLI execution
|
|
393
|
+
checkForUpdates(pkgJson.name, false).catch(() => {
|
|
394
|
+
// Silently fail - we don't want to interrupt the user's workflow
|
|
395
|
+
});
|
|
396
|
+
|
|
352
397
|
const command = args[0];
|
|
353
398
|
if (command === "init") {
|
|
354
399
|
projectRegistrationPromise.catch(() => null);
|
|
355
400
|
await handleInit(args);
|
|
356
401
|
return;
|
|
357
402
|
}
|
|
403
|
+
|
|
358
404
|
if (command === "login") {
|
|
359
405
|
projectRegistrationPromise.catch(() => null);
|
|
360
406
|
handleLogin(args[1]);
|
|
361
407
|
return;
|
|
362
408
|
}
|
|
409
|
+
|
|
363
410
|
if (command === "cluster") {
|
|
364
411
|
projectRegistrationPromise.catch(() => null);
|
|
365
412
|
await handleClusterCommand(args.slice(1));
|
|
366
413
|
return;
|
|
367
414
|
}
|
|
415
|
+
|
|
368
416
|
if (command === "config") {
|
|
369
417
|
projectRegistrationPromise.catch(() => null);
|
|
370
418
|
await handleConfig(args);
|
|
@@ -372,6 +420,7 @@ async function main() {
|
|
|
372
420
|
}
|
|
373
421
|
|
|
374
422
|
const clusterReady = await ensureClusterReady();
|
|
423
|
+
const yamlPath = path.join(process.cwd(), "oncall.yaml");
|
|
375
424
|
if (!clusterReady) {
|
|
376
425
|
console.error(
|
|
377
426
|
`\nOnCall server is not running.
|
|
@@ -383,6 +432,18 @@ Start it in another terminal:
|
|
|
383
432
|
process.exit(1);
|
|
384
433
|
}
|
|
385
434
|
|
|
435
|
+
if (!fs.existsSync(yamlPath)) {
|
|
436
|
+
console.error(
|
|
437
|
+
`\nThis process is not registered with an OnCall project.
|
|
438
|
+
|
|
439
|
+
Register it with:
|
|
440
|
+
oncall init --id <project-id> -m "<process description>"
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
\n`
|
|
444
|
+
);
|
|
445
|
+
process.exit(1);
|
|
446
|
+
}
|
|
386
447
|
try {
|
|
387
448
|
await import("../dist/index.js");
|
|
388
449
|
} catch (err) {
|
package/dist/api.d.ts
CHANGED
package/dist/api.js
CHANGED
|
@@ -3,6 +3,12 @@ import { config } from "./config.js";
|
|
|
3
3
|
import { checkVersionCompatibility } from "./utils/version-check.js";
|
|
4
4
|
const BASE_URL = config.api_base_url;
|
|
5
5
|
axios.interceptors.response.use((response) => response, async (error) => {
|
|
6
|
+
// Network error (no response received)
|
|
7
|
+
if (!error.response) {
|
|
8
|
+
const networkError = new Error("Network error: No internet connection");
|
|
9
|
+
networkError.code = "NETWORK_ERROR";
|
|
10
|
+
return Promise.reject(networkError);
|
|
11
|
+
}
|
|
6
12
|
if (error.response) {
|
|
7
13
|
const isCompatible = await checkVersionCompatibility(true);
|
|
8
14
|
if (!isCompatible) {
|
|
@@ -11,6 +17,11 @@ axios.interceptors.response.use((response) => response, async (error) => {
|
|
|
11
17
|
}
|
|
12
18
|
return Promise.reject(error);
|
|
13
19
|
});
|
|
20
|
+
//check for valid api key
|
|
21
|
+
export const isValidAuthKey = async (authKey) => {
|
|
22
|
+
const isValid = await axios.post(`${BASE_URL}/auth/login`, { authKey });
|
|
23
|
+
return isValid.data;
|
|
24
|
+
};
|
|
14
25
|
export const toolFunctionCall = async (tool_call_id, resultArgs, args, function_name) => {
|
|
15
26
|
const result = await axios.post(`${BASE_URL}/tool/toolFunctionCall`, {
|
|
16
27
|
tool_call_id,
|
|
@@ -1,5 +1,112 @@
|
|
|
1
|
-
import { RipGrep } from "ripgrep-
|
|
1
|
+
import { RipGrep } from "../tools/ripgrep-wrapper.js";
|
|
2
2
|
import { logd } from "./cli-helpers.js";
|
|
3
|
+
import path from "path";
|
|
4
|
+
function looksLikeFilename(query) {
|
|
5
|
+
const trimmed = query.trim();
|
|
6
|
+
if (!trimmed || trimmed.length === 0)
|
|
7
|
+
return false;
|
|
8
|
+
if (/\.\w{1,10}$/.test(trimmed))
|
|
9
|
+
return true;
|
|
10
|
+
if (trimmed.includes("/") || trimmed.includes("\\"))
|
|
11
|
+
return true;
|
|
12
|
+
if (trimmed.startsWith("."))
|
|
13
|
+
return true;
|
|
14
|
+
if (!trimmed.includes(" ") && trimmed.length >= 3 && trimmed.length <= 50) {
|
|
15
|
+
if (/^[a-zA-Z0-9_-]+$/.test(trimmed)) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
async function searchFilesByName(query, searchDir, options) {
|
|
22
|
+
const { maxResults = 20, caseSensitive = false, excludePatterns = [], } = options;
|
|
23
|
+
const defaultExcludePatterns = [
|
|
24
|
+
"node_modules",
|
|
25
|
+
".git",
|
|
26
|
+
"dist",
|
|
27
|
+
"build",
|
|
28
|
+
".next",
|
|
29
|
+
".cache",
|
|
30
|
+
"coverage",
|
|
31
|
+
".nyc_output",
|
|
32
|
+
".vscode",
|
|
33
|
+
".idea",
|
|
34
|
+
"*.log",
|
|
35
|
+
"*.lock",
|
|
36
|
+
"package-lock.json",
|
|
37
|
+
"yarn.lock",
|
|
38
|
+
"pnpm-lock.yaml",
|
|
39
|
+
".env",
|
|
40
|
+
".env.*",
|
|
41
|
+
"*.min.js",
|
|
42
|
+
"*.min.css",
|
|
43
|
+
".DS_Store",
|
|
44
|
+
"Thumbs.db",
|
|
45
|
+
];
|
|
46
|
+
const allExcludePatterns = [...defaultExcludePatterns, ...excludePatterns];
|
|
47
|
+
try {
|
|
48
|
+
const escapedQuery = query.replace(/[\[\]{}()]/g, "\\$&");
|
|
49
|
+
const globPattern = `**/*${escapedQuery}*`;
|
|
50
|
+
logd(`[filenameSearch] Using ripgrep to search for files matching pattern: ${globPattern}`);
|
|
51
|
+
let rg = new RipGrep(".", searchDir);
|
|
52
|
+
rg.glob(globPattern);
|
|
53
|
+
for (const pattern of allExcludePatterns) {
|
|
54
|
+
const hasFileExtension = /\.(json|lock|yaml|js|css|log)$/.test(pattern) ||
|
|
55
|
+
pattern.startsWith("*.") ||
|
|
56
|
+
pattern === ".env" ||
|
|
57
|
+
pattern.startsWith(".env.") ||
|
|
58
|
+
pattern === ".DS_Store" ||
|
|
59
|
+
pattern === "Thumbs.db";
|
|
60
|
+
const isFilePattern = pattern.includes("*") || hasFileExtension;
|
|
61
|
+
const excludePattern = isFilePattern ? `!${pattern}` : `!${pattern}/**`;
|
|
62
|
+
rg.glob(excludePattern);
|
|
63
|
+
}
|
|
64
|
+
rg.withFilename().lineNumber();
|
|
65
|
+
rg.fixedStrings();
|
|
66
|
+
if (!caseSensitive) {
|
|
67
|
+
rg.ignoreCase();
|
|
68
|
+
}
|
|
69
|
+
const runResult = await rg.run();
|
|
70
|
+
const output = await runResult.asString();
|
|
71
|
+
if (!output || output.trim().length === 0) {
|
|
72
|
+
logd(`[filenameSearch] No files found matching pattern: ${globPattern}`);
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
const lines = output.trim().split("\n");
|
|
76
|
+
const filePaths = new Set();
|
|
77
|
+
const results = [];
|
|
78
|
+
for (const line of lines) {
|
|
79
|
+
if (results.length >= maxResults)
|
|
80
|
+
break;
|
|
81
|
+
const match = line.match(/^(.+?):(\d+):(.+)$/);
|
|
82
|
+
if (match) {
|
|
83
|
+
const filePath = match[1].trim();
|
|
84
|
+
const relativePath = path.relative(searchDir, filePath);
|
|
85
|
+
if (!filePaths.has(relativePath)) {
|
|
86
|
+
filePaths.add(relativePath);
|
|
87
|
+
const lineNumber = parseInt(match[2], 10) || 1;
|
|
88
|
+
const preview = match[3]?.trim().slice(0, 200) ||
|
|
89
|
+
`File: ${path.basename(filePath)}`;
|
|
90
|
+
results.push({
|
|
91
|
+
filePath: relativePath,
|
|
92
|
+
line: lineNumber,
|
|
93
|
+
preview: preview,
|
|
94
|
+
score: 1.0,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
logd(`[filenameSearch] Found ${results.length} files matching pattern: ${globPattern}`);
|
|
100
|
+
return results;
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
logd(`[filenameSearch] Error searching for files: ${error.message}, code: ${error.code}`);
|
|
104
|
+
if (error.code === 1 || error.message?.includes("No matches")) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
3
110
|
export async function ripgrepSearch(query, options = {}) {
|
|
4
111
|
const defaultExcludePatterns = [
|
|
5
112
|
"node_modules",
|
|
@@ -28,7 +135,8 @@ export async function ripgrepSearch(query, options = {}) {
|
|
|
28
135
|
if (!query || query.trim().length === 0) {
|
|
29
136
|
return [];
|
|
30
137
|
}
|
|
31
|
-
const searchDir = workingDirectory ||
|
|
138
|
+
const searchDir = workingDirectory ||
|
|
139
|
+
(typeof process !== "undefined" ? process.cwd() : undefined);
|
|
32
140
|
if (!searchDir) {
|
|
33
141
|
logd("[ripgrep] ERROR: workingDirectory is required for client-side usage");
|
|
34
142
|
return [];
|
|
@@ -57,7 +165,8 @@ export async function ripgrepSearch(query, options = {}) {
|
|
|
57
165
|
const excludePattern = isFilePattern ? `!${pattern}` : `!${pattern}/**`;
|
|
58
166
|
rg.glob(excludePattern);
|
|
59
167
|
}
|
|
60
|
-
const
|
|
168
|
+
const runResult = await rg.run();
|
|
169
|
+
const output = await runResult.asString();
|
|
61
170
|
logd(`[ripgrep] Raw output: outputLength=${output?.length || 0}, hasOutput=${!!(output && output.trim().length > 0)}, outputPreview=${output?.substring(0, 200) || "N/A"}`);
|
|
62
171
|
if (!output || output.trim().length === 0) {
|
|
63
172
|
logd("[ripgrep] ⚠️ No output from ripgrep - returning empty results");
|
|
@@ -83,19 +192,53 @@ export async function ripgrepSearch(query, options = {}) {
|
|
|
83
192
|
logd(`[ripgrep] ⚠️ Line didn't match expected format: line=${line.substring(0, 100)}, lineLength=${line.length}`);
|
|
84
193
|
}
|
|
85
194
|
}
|
|
86
|
-
logd(`[ripgrep] ✅ Parsed results: totalResults=${results.length}, results=${JSON.stringify(results.map(r => ({
|
|
195
|
+
logd(`[ripgrep] ✅ Parsed results: totalResults=${results.length}, results=${JSON.stringify(results.map((r) => ({
|
|
87
196
|
filePath: r.filePath,
|
|
88
197
|
line: r.line,
|
|
89
198
|
previewLength: r.preview?.length || 0,
|
|
90
199
|
})))}`);
|
|
200
|
+
const hasExactFilenameMatch = results.some((r) => {
|
|
201
|
+
const fileName = path.basename(r.filePath);
|
|
202
|
+
return fileName.toLowerCase() === query.toLowerCase();
|
|
203
|
+
});
|
|
204
|
+
if (looksLikeFilename(query) && (!hasExactFilenameMatch || results.length === 0)) {
|
|
205
|
+
logd(`[ripgrep] 🔄 Content search ${results.length === 0 ? 'found no results' : `found ${results.length} results but no exact filename match`}, but query looks like filename. Trying filename search...`);
|
|
206
|
+
const filenameResults = await searchFilesByName(query, searchDir, options);
|
|
207
|
+
if (filenameResults.length > 0) {
|
|
208
|
+
logd(`[ripgrep] ✅ Filename search found ${filenameResults.length} matches`);
|
|
209
|
+
if (results.length > 0 && !hasExactFilenameMatch) {
|
|
210
|
+
const combined = [...filenameResults, ...results];
|
|
211
|
+
const unique = combined.filter((r, index, self) => index === self.findIndex((t) => t.filePath === r.filePath));
|
|
212
|
+
return unique.slice(0, maxResults);
|
|
213
|
+
}
|
|
214
|
+
return filenameResults;
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
logd(`[ripgrep] ⚠️ Filename search also found no matches`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
91
220
|
return results;
|
|
92
221
|
}
|
|
93
222
|
catch (error) {
|
|
94
|
-
logd(`[ripgrep] ❌ Exception caught: error=${error.message}, code=${error.code}, stack=${error.stack?.substring(0, 200) ||
|
|
223
|
+
logd(`[ripgrep] ❌ Exception caught: error=${error.message}, code=${error.code}, stack=${error.stack?.substring(0, 200) || "N/A"}`);
|
|
95
224
|
if (error.message?.includes("No matches") ||
|
|
96
225
|
error.message?.includes("not found") ||
|
|
97
226
|
error.code === 1) {
|
|
98
227
|
logd("[ripgrep] ℹ️ No matches found (this is normal if search term doesn't exist)");
|
|
228
|
+
// Fallback to filename search if it looks like a filename
|
|
229
|
+
if (looksLikeFilename(query)) {
|
|
230
|
+
logd(`[ripgrep] 🔄 Error occurred, but query looks like filename. Trying filename search as fallback...`);
|
|
231
|
+
try {
|
|
232
|
+
const filenameResults = await searchFilesByName(query, searchDir, options);
|
|
233
|
+
if (filenameResults.length > 0) {
|
|
234
|
+
logd(`[ripgrep] ✅ Filename search fallback found ${filenameResults.length} matches`);
|
|
235
|
+
return filenameResults;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch (fallbackError) {
|
|
239
|
+
logd(`[ripgrep] ❌ Filename search fallback also failed: ${fallbackError}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
99
242
|
return [];
|
|
100
243
|
}
|
|
101
244
|
logd(`[ripgrep] Error executing ripgrep: ${error.message}`);
|
package/dist/index.js
CHANGED
|
@@ -12,9 +12,9 @@ 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";
|
|
15
|
+
import { HumanMessage, } from "langchain";
|
|
16
16
|
import { SystemMessage } from "@langchain/core/messages";
|
|
17
|
-
import { loadProjectMetadata, fetchProjectsFromCluster, logd } from "./helpers/cli-helpers.js";
|
|
17
|
+
import { loadProjectMetadata, fetchProjectsFromCluster, logd, } from "./helpers/cli-helpers.js";
|
|
18
18
|
import logsManager from "./logsManager.js";
|
|
19
19
|
import { extractMessageContent } from "./utils.js";
|
|
20
20
|
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.");
|
|
@@ -29,15 +29,15 @@ marked.use(markedTerminal({
|
|
|
29
29
|
reflowText: false,
|
|
30
30
|
showSectionPrefix: false,
|
|
31
31
|
unescape: true,
|
|
32
|
-
emoji: true
|
|
32
|
+
emoji: true,
|
|
33
33
|
}));
|
|
34
|
-
const COLON_REPLACER =
|
|
34
|
+
const COLON_REPLACER = "*#COLON|*";
|
|
35
35
|
function escapeRegExp(str) {
|
|
36
|
-
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,
|
|
36
|
+
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
|
|
37
37
|
}
|
|
38
|
-
const COLON_REPLACER_REGEXP = new RegExp(escapeRegExp(COLON_REPLACER),
|
|
38
|
+
const COLON_REPLACER_REGEXP = new RegExp(escapeRegExp(COLON_REPLACER), "g");
|
|
39
39
|
function undoColon(str) {
|
|
40
|
-
return str.replace(COLON_REPLACER_REGEXP,
|
|
40
|
+
return str.replace(COLON_REPLACER_REGEXP, ":");
|
|
41
41
|
}
|
|
42
42
|
// Override just the 'text' renderer to handle inline tokens:
|
|
43
43
|
marked.use({
|
|
@@ -84,8 +84,19 @@ function wrapText(text, maxWidth) {
|
|
|
84
84
|
}
|
|
85
85
|
return lines.length > 0 ? lines : [""];
|
|
86
86
|
}
|
|
87
|
-
//
|
|
88
|
-
|
|
87
|
+
//truncate the line depending on the width available
|
|
88
|
+
function getProcessedLine(text, maxWidth) {
|
|
89
|
+
if (maxWidth <= 0)
|
|
90
|
+
return text;
|
|
91
|
+
const expanded = text.replace(/\t/g, " ".repeat(8));
|
|
92
|
+
const width = stringWidth(expanded);
|
|
93
|
+
if (width > maxWidth && maxWidth > 3) {
|
|
94
|
+
return sliceAnsi(expanded, 0, Math.max(0, maxWidth - 3)) + "...";
|
|
95
|
+
}
|
|
96
|
+
return expanded;
|
|
97
|
+
}
|
|
98
|
+
//scrollable content for logs
|
|
99
|
+
const ScrollableContent = ({ lines, maxHeight, isFocused, onScrollChange, scrollOffset, availableWidth, }) => {
|
|
89
100
|
const totalLines = lines.length;
|
|
90
101
|
const visibleLines = Math.max(0, Math.min(maxHeight, totalLines));
|
|
91
102
|
const maxOffset = Math.max(0, totalLines - visibleLines);
|
|
@@ -109,7 +120,8 @@ const ScrollableContent = ({ lines, maxHeight, isFocused, onScrollChange, scroll
|
|
|
109
120
|
}
|
|
110
121
|
const scrollBarIndicator = totalLines > visibleLines ? `[${scrollPosition}%]` : "";
|
|
111
122
|
return (_jsxs(Box, { flexDirection: "column", height: maxHeight, overflow: "hidden", children: [displayedLines.map((line) => {
|
|
112
|
-
const
|
|
123
|
+
const truncated = getProcessedLine(line.text, availableWidth);
|
|
124
|
+
const rendered = marked.parseInline(truncated);
|
|
113
125
|
return _jsx(Text, { children: rendered }, line.key);
|
|
114
126
|
}), _jsx(Box, { position: "absolute", children: _jsx(Text, { color: "yellowBright", bold: true, children: scrollBarIndicator }) })] }));
|
|
115
127
|
};
|
|
@@ -208,6 +220,9 @@ export const App = () => {
|
|
|
208
220
|
const [planningDoc, setPlanningDoc] = useState("");
|
|
209
221
|
const partialLine = useRef("");
|
|
210
222
|
const logKeyCounter = useRef(0);
|
|
223
|
+
//auto truncate logs depending on width
|
|
224
|
+
const ptyRef = useRef(null);
|
|
225
|
+
const ptyAliveRef = useRef(false);
|
|
211
226
|
const [activePane, setActivePane] = useState("input");
|
|
212
227
|
const [logScroll, setLogScroll] = useState(0);
|
|
213
228
|
const [chatScroll, setChatScroll] = useState(0);
|
|
@@ -231,20 +246,27 @@ export const App = () => {
|
|
|
231
246
|
//websocket hook
|
|
232
247
|
const { connectWebSocket, sendQuery, chatResponseMessages, // Full history including ToolMessage (for backend queries)
|
|
233
248
|
visibleChats, // Filtered for UI display (excludes ToolMessage except reflections)
|
|
234
|
-
setChatResponseMessages, isConnected, isLoading, setIsLoading, setTrimmedChats, API_KEY, connectionError, setSocketId, setIsConnected, socket, setShowControlR, showControlR, setCompleteChatHistory, customMessage, setVisibleChats, graphState, setGraphState } = useWebSocket(config.websocket_url, logsManager);
|
|
249
|
+
setChatResponseMessages, isConnected, isLoading, setIsLoading, setTrimmedChats, API_KEY, connectionError, setSocketId, setIsConnected, socket, setShowControlR, showControlR, setCompleteChatHistory, customMessage, setVisibleChats, graphState, setGraphState, } = useWebSocket(config.websocket_url, logsManager);
|
|
235
250
|
const SCROLL_HEIGHT = terminalRows - 6;
|
|
236
251
|
const LOGS_HEIGHT = SCROLL_HEIGHT;
|
|
237
252
|
const INPUT_BOX_HEIGHT = 5; // Border (2) + marginTop (1) + content (1) + padding (~1)
|
|
238
253
|
const CHAT_HISTORY_HEIGHT = SCROLL_HEIGHT - INPUT_BOX_HEIGHT;
|
|
239
254
|
useEffect(() => {
|
|
240
255
|
const handleResize = () => {
|
|
241
|
-
if (stdout?.rows)
|
|
256
|
+
if (stdout?.rows)
|
|
242
257
|
setTerminalRows(stdout.rows);
|
|
243
|
-
|
|
244
|
-
if (stdout?.columns) {
|
|
258
|
+
if (stdout?.columns)
|
|
245
259
|
setTerminalCols(stdout.columns);
|
|
260
|
+
const cols = Math.max(10, Math.floor((stdout?.columns || 80) / 2) - 6);
|
|
261
|
+
const rows = Math.max(1, logsHeightRef.current - 2);
|
|
262
|
+
if (ptyAliveRef.current && ptyRef.current) {
|
|
263
|
+
try {
|
|
264
|
+
ptyRef.current.resize(cols, rows);
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
//ignore
|
|
268
|
+
}
|
|
246
269
|
}
|
|
247
|
-
// DO NOT re-run or re-process logs here
|
|
248
270
|
};
|
|
249
271
|
process.stdout.on("resize", handleResize);
|
|
250
272
|
return () => {
|
|
@@ -256,25 +278,22 @@ export const App = () => {
|
|
|
256
278
|
connectWebSocket();
|
|
257
279
|
}, []);
|
|
258
280
|
//get the AIMessage content inside the progress event
|
|
259
|
-
let lastAIMessage = "";
|
|
260
|
-
function extractAIMessages(obj) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
return undefined;
|
|
276
|
-
return content;
|
|
277
|
-
}
|
|
281
|
+
// let lastAIMessage = "";
|
|
282
|
+
// function extractAIMessages(obj: ProgressObject): string | undefined {
|
|
283
|
+
// if (obj?.type !== "progress") return undefined;
|
|
284
|
+
// const messages = (obj.data && obj.data?.messages) ?? [];
|
|
285
|
+
// const latestAI = [...messages]
|
|
286
|
+
// .reverse()
|
|
287
|
+
// .find((m) => m.id?.includes("AIMessage"));
|
|
288
|
+
// const content = latestAI?.kwargs?.content?.trim();
|
|
289
|
+
// if (!content) return undefined;
|
|
290
|
+
// if (content === lastAIMessage) {
|
|
291
|
+
// return undefined;
|
|
292
|
+
// }
|
|
293
|
+
// lastAIMessage = content;
|
|
294
|
+
// if (content === undefined) return undefined;
|
|
295
|
+
// return content;
|
|
296
|
+
// }
|
|
278
297
|
// Auto-switch to chat pane when server finishes responding
|
|
279
298
|
useEffect(() => {
|
|
280
299
|
// Use visibleChats for UI-related checks
|
|
@@ -311,7 +330,7 @@ export const App = () => {
|
|
|
311
330
|
if (!prev)
|
|
312
331
|
prev = { lastId: "", messages: [] };
|
|
313
332
|
if (!message.id)
|
|
314
|
-
message.id =
|
|
333
|
+
message.id = new Date().getTime() + "";
|
|
315
334
|
if (message.id !== prev.lastId) {
|
|
316
335
|
prev.messages.push([message]);
|
|
317
336
|
prev.lastId = message.id;
|
|
@@ -324,16 +343,18 @@ export const App = () => {
|
|
|
324
343
|
return prev;
|
|
325
344
|
}, { lastId: "", messages: [] });
|
|
326
345
|
}
|
|
327
|
-
function
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
346
|
+
// function extractMessageContent(messages: BaseMessage[]) {
|
|
347
|
+
// return messages.reduce((prev, message) => {
|
|
348
|
+
// return prev + message.content
|
|
349
|
+
// }, "")
|
|
350
|
+
// }
|
|
332
351
|
function extractThinkMessage(messages) {
|
|
333
352
|
if (messages.length < 5)
|
|
334
353
|
return "";
|
|
335
354
|
return messages.slice(4, -3).reduce((prev, message) => {
|
|
336
|
-
if (message.tool_call_chunks &&
|
|
355
|
+
if (message.tool_call_chunks &&
|
|
356
|
+
message.tool_call_chunks[0] &&
|
|
357
|
+
message.tool_call_chunks[0].args)
|
|
337
358
|
return prev + message.tool_call_chunks[0].args;
|
|
338
359
|
return prev;
|
|
339
360
|
}, "");
|
|
@@ -351,6 +372,16 @@ export const App = () => {
|
|
|
351
372
|
const prefixWidth = stringWidth(prefix);
|
|
352
373
|
// let content = `${index}: ` + extractMessageContent(msgs)
|
|
353
374
|
let content = extractMessageContent(msgs);
|
|
375
|
+
// Debug: Log if content looks like JSON
|
|
376
|
+
if (content.trim().startsWith("{") && content.trim().endsWith("}")) {
|
|
377
|
+
logd(`[DEBUG] Found JSON-like content in message group ${index}: ${content.substring(0, 100)}`);
|
|
378
|
+
logd(`[DEBUG] Message details: ${JSON.stringify(msgs.map((m) => ({
|
|
379
|
+
type: m.constructor.name,
|
|
380
|
+
content: typeof m.content === "string"
|
|
381
|
+
? m.content.substring(0, 100)
|
|
382
|
+
: typeof m.content,
|
|
383
|
+
})), null, 2)}`);
|
|
384
|
+
}
|
|
354
385
|
const fMessage = msgs[0];
|
|
355
386
|
if (fMessage.tool_calls && fMessage.tool_calls.length > 0) {
|
|
356
387
|
logd("---------------- tool calls received --------------");
|
|
@@ -392,7 +423,7 @@ export const App = () => {
|
|
|
392
423
|
// return groupMessageChunks(messagesToRender).messages.flatMap((msgs, index) => {
|
|
393
424
|
// // Get message type from BaseMessage (LangChain messages use getType() or constructor name)
|
|
394
425
|
// const msgAny = msgs as any;
|
|
395
|
-
// const msgType =
|
|
426
|
+
// const msgType =
|
|
396
427
|
// (typeof msgAny[0].getType === "function" && msgAny[0].getType()) ||
|
|
397
428
|
// msgAny[0]._type ||
|
|
398
429
|
// msgs.constructor.name ||
|
|
@@ -418,21 +449,25 @@ export const App = () => {
|
|
|
418
449
|
// }
|
|
419
450
|
// });
|
|
420
451
|
}, [visibleChats, chatResponseMessages]);
|
|
421
|
-
const currentLogDataString = useMemo(
|
|
452
|
+
// const currentLogDataString = useMemo(
|
|
453
|
+
// () => rawLogData.map((l) => l.text).join("\n"),
|
|
454
|
+
// [rawLogData]
|
|
455
|
+
// );
|
|
422
456
|
// Process truncation once when inserting logs (so resize won't re-process)
|
|
423
|
-
const getProcessedLine = (text) => {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
457
|
+
// const getProcessedLine = (text: string) => {
|
|
458
|
+
// const availableWidth = Math.floor(terminalColsRef.current / 2) - 6;
|
|
459
|
+
// const expandedText = text.replace(/\t/g, " ".repeat(8));
|
|
460
|
+
// const width = stringWidth(expandedText);
|
|
461
|
+
// if (width > availableWidth && availableWidth > 3) {
|
|
462
|
+
// const truncated = sliceAnsi(
|
|
463
|
+
// expandedText,
|
|
464
|
+
// 0,
|
|
465
|
+
// Math.max(0, availableWidth - 3)
|
|
466
|
+
// );
|
|
467
|
+
// return truncated + "...";
|
|
468
|
+
// }
|
|
469
|
+
// return text;
|
|
470
|
+
// };
|
|
436
471
|
// Keep logLines purely tied to stored processed lines
|
|
437
472
|
const logLines = useMemo(() => rawLogData, [rawLogData]);
|
|
438
473
|
// Prefer a known-good shell over a potentially broken $SHELL on some machines
|
|
@@ -465,12 +500,14 @@ export const App = () => {
|
|
|
465
500
|
const shell = process.env.SHELL || "/bin/zsh";
|
|
466
501
|
const cols = Math.max(10, Math.floor(terminalColsRef.current / 2) - 6);
|
|
467
502
|
const rows = Math.max(1, logsHeightRef.current - 2);
|
|
468
|
-
|
|
503
|
+
ptyRef.current = ptySpawn(shell, ["-c", command], {
|
|
469
504
|
cwd: process.cwd(),
|
|
470
505
|
env: process.env,
|
|
471
|
-
cols
|
|
472
|
-
rows
|
|
506
|
+
cols,
|
|
507
|
+
rows,
|
|
473
508
|
});
|
|
509
|
+
ptyAliveRef.current = true;
|
|
510
|
+
const ptyProcess = ptyRef.current;
|
|
474
511
|
ptyProcess.onData((chunk) => {
|
|
475
512
|
setUnTamperedLogs((oldLines) => oldLines + chunk);
|
|
476
513
|
logsManager.addChunk(chunk);
|
|
@@ -480,17 +517,19 @@ export const App = () => {
|
|
|
480
517
|
if (lines.length > 0) {
|
|
481
518
|
const newLines = lines.map((line) => ({
|
|
482
519
|
key: `log-${logKeyCounter.current++}`,
|
|
483
|
-
text:
|
|
520
|
+
text: line,
|
|
484
521
|
}));
|
|
485
522
|
// Append in single update
|
|
486
523
|
setRawLogData((prevLines) => [...prevLines, ...newLines]);
|
|
487
524
|
}
|
|
488
525
|
});
|
|
489
526
|
ptyProcess.onExit(({ exitCode }) => {
|
|
527
|
+
ptyAliveRef.current = false;
|
|
528
|
+
ptyRef.current = null;
|
|
490
529
|
if (partialLine.current.length > 0) {
|
|
491
530
|
const remainingLine = {
|
|
492
531
|
key: `log-${logKeyCounter.current++}`,
|
|
493
|
-
text:
|
|
532
|
+
text: partialLine.current,
|
|
494
533
|
};
|
|
495
534
|
setRawLogData((prevLines) => [...prevLines, remainingLine]);
|
|
496
535
|
partialLine.current = "";
|
|
@@ -522,7 +561,7 @@ export const App = () => {
|
|
|
522
561
|
if (partialLine.current.length > 0) {
|
|
523
562
|
const remainingLine = {
|
|
524
563
|
key: `log-${logKeyCounter.current++}`,
|
|
525
|
-
text:
|
|
564
|
+
text: partialLine.current,
|
|
526
565
|
};
|
|
527
566
|
setRawLogData((prev) => [...prev, remainingLine]);
|
|
528
567
|
partialLine.current = "";
|
|
@@ -567,15 +606,9 @@ export const App = () => {
|
|
|
567
606
|
const userMessage = new HumanMessage(chatInput);
|
|
568
607
|
const projects = await fetchProjectsFromCluster(loadProjectMetadata());
|
|
569
608
|
sendQuery([...chatResponseMessages, userMessage], generateArchitecture(projects.projects), logs, planningDoc);
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
]);
|
|
574
|
-
setTrimmedChats((prev) => [
|
|
575
|
-
...prev,
|
|
576
|
-
userMessage
|
|
577
|
-
]);
|
|
578
|
-
setVisibleChats(prev => [...prev, userMessage]);
|
|
609
|
+
setVisibleChats((prev) => [...prev, userMessage]);
|
|
610
|
+
setChatResponseMessages((prev) => [...prev, userMessage]);
|
|
611
|
+
setTrimmedChats((prev) => [...prev, userMessage]);
|
|
579
612
|
setChatInput("");
|
|
580
613
|
}
|
|
581
614
|
useInput((inputStr, key) => {
|
|
@@ -617,13 +650,11 @@ export const App = () => {
|
|
|
617
650
|
return;
|
|
618
651
|
}
|
|
619
652
|
if (key.ctrl && inputStr === "d") {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
return "NORMAL";
|
|
626
|
-
});
|
|
653
|
+
const modes = ["NORMAL", "COPY", "LOGS"];
|
|
654
|
+
setMode((prev) => modes[(modes.indexOf(prev) + 1) % modes.length]);
|
|
655
|
+
setActivePane("logs");
|
|
656
|
+
setLogScroll((s) => s); // keep the scroll position
|
|
657
|
+
return;
|
|
627
658
|
}
|
|
628
659
|
if (key.tab) {
|
|
629
660
|
if (activePane === "input")
|
|
@@ -665,10 +696,10 @@ export const App = () => {
|
|
|
665
696
|
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 })] }));
|
|
666
697
|
}
|
|
667
698
|
else if (mode === "LOGS") {
|
|
668
|
-
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 === "
|
|
699
|
+
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 === "logs", width: "100%", children: _jsx(ScrollableContent, { lines: logLines, maxHeight: LOGS_HEIGHT - 2, isFocused: activePane === "logs", scrollOffset: logScroll, onScrollChange: setLogScroll, availableWidth: Math.floor(terminalCols - 6) }) }) }), _jsx(ShortcutsFooter, { shortcuts: shortcuts })] }));
|
|
669
700
|
}
|
|
670
701
|
else {
|
|
671
|
-
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 })] }));
|
|
702
|
+
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, availableWidth: Math.floor(terminalCols / 2) - 6 }) }), _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 })] }));
|
|
672
703
|
}
|
|
673
704
|
};
|
|
674
705
|
render(_jsx(App, {}));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare class RipGrep {
|
|
2
|
+
private query;
|
|
3
|
+
private searchDir;
|
|
4
|
+
private args;
|
|
5
|
+
constructor(query: string, searchDir: string);
|
|
6
|
+
withFilename(): this;
|
|
7
|
+
lineNumber(): this;
|
|
8
|
+
ignoreCase(): this;
|
|
9
|
+
glob(pattern: string): this;
|
|
10
|
+
fixedStrings(): this;
|
|
11
|
+
run(): Promise<{
|
|
12
|
+
asString: () => Promise<string>;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Wrapper for @vscode/ripgrep to match ripgrep-node API
|
|
2
|
+
import { rgPath } from "@vscode/ripgrep";
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
export class RipGrep {
|
|
5
|
+
constructor(query, searchDir) {
|
|
6
|
+
this.args = [];
|
|
7
|
+
this.query = query;
|
|
8
|
+
this.searchDir = searchDir;
|
|
9
|
+
}
|
|
10
|
+
withFilename() {
|
|
11
|
+
this.args.push("--with-filename");
|
|
12
|
+
return this;
|
|
13
|
+
}
|
|
14
|
+
lineNumber() {
|
|
15
|
+
this.args.push("--line-number");
|
|
16
|
+
return this;
|
|
17
|
+
}
|
|
18
|
+
ignoreCase() {
|
|
19
|
+
this.args.push("--ignore-case");
|
|
20
|
+
return this;
|
|
21
|
+
}
|
|
22
|
+
glob(pattern) {
|
|
23
|
+
this.args.push("--glob", pattern);
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
fixedStrings() {
|
|
27
|
+
this.args.push("--fixed-strings");
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
async run() {
|
|
31
|
+
const allArgs = [
|
|
32
|
+
...this.args,
|
|
33
|
+
this.query,
|
|
34
|
+
this.searchDir,
|
|
35
|
+
];
|
|
36
|
+
return {
|
|
37
|
+
asString: async () => {
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
const rgProcess = spawn(rgPath, allArgs, {
|
|
40
|
+
cwd: this.searchDir,
|
|
41
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
42
|
+
});
|
|
43
|
+
let stdout = "";
|
|
44
|
+
let stderr = "";
|
|
45
|
+
rgProcess.stdout.on("data", (data) => {
|
|
46
|
+
stdout += data.toString();
|
|
47
|
+
});
|
|
48
|
+
rgProcess.stderr.on("data", (data) => {
|
|
49
|
+
stderr += data.toString();
|
|
50
|
+
});
|
|
51
|
+
rgProcess.on("close", (code) => {
|
|
52
|
+
if (code === 0 || code === 1) {
|
|
53
|
+
resolve(stdout);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
reject(new Error(`ripgrep exited with code ${code}: ${stderr || stdout}`));
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
rgProcess.on("error", (error) => {
|
|
60
|
+
reject(error);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=ripgrep-wrapper.js.map
|
package/dist/tools/ripgrep.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RipGrep } from "ripgrep-
|
|
1
|
+
import { RipGrep } from "./ripgrep-wrapper.js";
|
|
2
2
|
export async function ripgrepSearch(query, options = {}) {
|
|
3
3
|
const defaultExcludePatterns = [
|
|
4
4
|
"node_modules",
|
|
@@ -54,7 +54,8 @@ export async function ripgrepSearch(query, options = {}) {
|
|
|
54
54
|
const excludePattern = isFilePattern ? `!${pattern}` : `!${pattern}/**`;
|
|
55
55
|
rg.glob(excludePattern);
|
|
56
56
|
}
|
|
57
|
-
const
|
|
57
|
+
const runResult = await rg.run();
|
|
58
|
+
const output = await runResult.asString();
|
|
58
59
|
if (!output || output.trim().length === 0) {
|
|
59
60
|
return [];
|
|
60
61
|
}
|
package/dist/useWebSocket.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
2
|
import WebSocket from "ws";
|
|
3
|
-
import { getContextLines } from "./utils.js";
|
|
3
|
+
import { getContextLines, cleanMessagesContent } from "./utils.js";
|
|
4
|
+
import { config } from "./config.js";
|
|
4
5
|
import { toolFunctionCall } from "./api.js";
|
|
5
6
|
import fs from "fs";
|
|
6
7
|
import os from "os";
|
|
7
8
|
import path from "path";
|
|
8
|
-
import { mapChatMessagesToStoredMessages, mapStoredMessagesToChatMessages, SystemMessage, } from "@langchain/core/messages";
|
|
9
|
+
import { mapChatMessagesToStoredMessages, mapStoredMessagesToChatMessages, SystemMessage, AIMessage } from "@langchain/core/messages";
|
|
10
|
+
import axios from "axios";
|
|
9
11
|
import { ripgrepSearch } from "./helpers/ripgrep-tool.js";
|
|
10
12
|
import { loadProjectMetadata, logd } from "./helpers/cli-helpers.js";
|
|
11
13
|
// Load OnCall config from ~/.oncall/config
|
|
@@ -29,10 +31,12 @@ catch (err) {
|
|
|
29
31
|
export function useWebSocket(url, rawLogData) {
|
|
30
32
|
//refs
|
|
31
33
|
const socketRef = useRef(null);
|
|
34
|
+
const summarizeInProgressRef = useRef(false);
|
|
35
|
+
const graphStateRef = useRef(null);
|
|
32
36
|
const [socketId, setSocketId] = useState(null);
|
|
33
37
|
const [chatResponseMessages, setChatResponseMessages] = useState([]);
|
|
34
38
|
const [trimmedChats, setTrimmedChats] = useState([]);
|
|
35
|
-
const initialAssistantMessage = new SystemMessage("I
|
|
39
|
+
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
40
|
const [visibleChats, setVisibleChats] = useState([
|
|
37
41
|
initialAssistantMessage,
|
|
38
42
|
]);
|
|
@@ -43,6 +47,9 @@ export function useWebSocket(url, rawLogData) {
|
|
|
43
47
|
const [customMessage, setCustomMessage] = useState(null);
|
|
44
48
|
const [graphState, setGraphState] = useState(null);
|
|
45
49
|
const authKey = API_KEY;
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
graphStateRef.current = graphState;
|
|
52
|
+
}, [graphState]);
|
|
46
53
|
const initialProjectMetadata = loadProjectMetadata();
|
|
47
54
|
const getProjectMetadata = () => loadProjectMetadata() || initialProjectMetadata;
|
|
48
55
|
const getServiceId = () => getProjectMetadata()?.window_id;
|
|
@@ -61,6 +68,70 @@ export function useWebSocket(url, rawLogData) {
|
|
|
61
68
|
process.exit();
|
|
62
69
|
}
|
|
63
70
|
}, [API_KEY]);
|
|
71
|
+
const callSummarizeAPI = useCallback(async (currentGraphState) => {
|
|
72
|
+
if (summarizeInProgressRef.current) {
|
|
73
|
+
logd("[summarize] Summarize API call already in progress.");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (!currentGraphState || typeof currentGraphState !== "object") {
|
|
77
|
+
logd("[summarize] No graphState available");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
summarizeInProgressRef.current = true;
|
|
81
|
+
logd("[summarize] Calling summarize API with graphState");
|
|
82
|
+
try {
|
|
83
|
+
const response = await axios.post(`${config.api_base_url}/graph/summarize`, {
|
|
84
|
+
authKey: authKey,
|
|
85
|
+
graphState: currentGraphState,
|
|
86
|
+
});
|
|
87
|
+
if (response.data && response.data.success && response.data.summary) {
|
|
88
|
+
const summaryText = response.data.summary;
|
|
89
|
+
const summaryMessage = new AIMessage(`Current Approach Summary:\n${summaryText}`);
|
|
90
|
+
setVisibleChats((old) => [...old, summaryMessage]);
|
|
91
|
+
setTrimmedChats((old) => [...old, summaryMessage]);
|
|
92
|
+
setChatResponseMessages((old) => [...old, summaryMessage]);
|
|
93
|
+
setGraphState((prevState) => {
|
|
94
|
+
if (!prevState)
|
|
95
|
+
return prevState;
|
|
96
|
+
const storedMessages = mapChatMessagesToStoredMessages([summaryMessage]);
|
|
97
|
+
const storedSummaryMessage = storedMessages && storedMessages.length > 0 ? storedMessages[0] : null;
|
|
98
|
+
if (!storedSummaryMessage) {
|
|
99
|
+
return prevState;
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
...prevState,
|
|
103
|
+
messages: [...(prevState.messages || []), storedSummaryMessage],
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
throw new Error("Invalid response from summarize API");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
logd(`[summarize] ❌ Error calling summarize API: ${error instanceof Error ? error.message : String(error)}`);
|
|
113
|
+
const errorMessage = new AIMessage("Something went wrong. Please try asking the question again." + error.message);
|
|
114
|
+
setVisibleChats((old) => [...old, errorMessage]);
|
|
115
|
+
setTrimmedChats((old) => [...old, errorMessage]);
|
|
116
|
+
setChatResponseMessages((old) => [...old, errorMessage]);
|
|
117
|
+
setGraphState((prevState) => {
|
|
118
|
+
if (!prevState)
|
|
119
|
+
return prevState;
|
|
120
|
+
const storedMessages = mapChatMessagesToStoredMessages([errorMessage]);
|
|
121
|
+
const storedErrorMessage = storedMessages && storedMessages.length > 0 ? storedMessages[0] : null;
|
|
122
|
+
if (!storedErrorMessage) {
|
|
123
|
+
return prevState;
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
...prevState,
|
|
127
|
+
messages: [...(prevState.messages || []), storedErrorMessage],
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
summarizeInProgressRef.current = false;
|
|
133
|
+
}
|
|
134
|
+
}, [authKey]);
|
|
64
135
|
//web socket connection
|
|
65
136
|
const connectWebSocket = useCallback(() => {
|
|
66
137
|
// const d = fs.readFileSync('logs')
|
|
@@ -217,8 +288,9 @@ export function useWebSocket(url, rawLogData) {
|
|
|
217
288
|
messages = mapStoredMessagesToChatMessages(messages);
|
|
218
289
|
switch (data.data.type) {
|
|
219
290
|
case "messages":
|
|
220
|
-
if (data.data.sender !== "toolNode") {
|
|
221
|
-
|
|
291
|
+
if (data.data.sender !== "toolNode" && data.data.sender !== "routerNode") {
|
|
292
|
+
const cleanedMessages = cleanMessagesContent(messages);
|
|
293
|
+
setVisibleChats(old => [...old, ...cleanedMessages]);
|
|
222
294
|
}
|
|
223
295
|
// logd(`LOGGING RESPONSE SENDER: ${data.data.sender}`);
|
|
224
296
|
break;
|
|
@@ -229,12 +301,14 @@ export function useWebSocket(url, rawLogData) {
|
|
|
229
301
|
break;
|
|
230
302
|
case "updates":
|
|
231
303
|
if (data.data.sender !== "toolNode" && data.data.sender !== "routerNode") {
|
|
232
|
-
|
|
304
|
+
const cleanedMessages = cleanMessagesContent(messages);
|
|
305
|
+
setChatResponseMessages(old => [...old, ...cleanedMessages]);
|
|
233
306
|
}
|
|
234
307
|
if (data.data.sender === "answerNode") {
|
|
308
|
+
const cleanedMessages = cleanMessagesContent(messages);
|
|
235
309
|
setTrimmedChats(prev => {
|
|
236
|
-
setVisibleChats([...prev, ...
|
|
237
|
-
return [...prev, ...
|
|
310
|
+
setVisibleChats([...prev, ...cleanedMessages]);
|
|
311
|
+
return [...prev, ...cleanedMessages];
|
|
238
312
|
});
|
|
239
313
|
setIsLoading(false);
|
|
240
314
|
}
|
|
@@ -250,11 +324,18 @@ export function useWebSocket(url, rawLogData) {
|
|
|
250
324
|
data.type === "ask_user") {
|
|
251
325
|
setIsLoading(false);
|
|
252
326
|
setCustomMessage(null);
|
|
327
|
+
const currentGraphState = graphStateRef.current;
|
|
328
|
+
if (currentGraphState) {
|
|
329
|
+
callSummarizeAPI(currentGraphState);
|
|
330
|
+
}
|
|
253
331
|
}
|
|
254
332
|
}
|
|
255
333
|
catch (error) {
|
|
256
334
|
setIsLoading(false);
|
|
257
|
-
|
|
335
|
+
const currentGraphState = graphStateRef.current;
|
|
336
|
+
if (currentGraphState) {
|
|
337
|
+
callSummarizeAPI(currentGraphState);
|
|
338
|
+
}
|
|
258
339
|
return;
|
|
259
340
|
}
|
|
260
341
|
};
|
|
@@ -274,7 +355,7 @@ export function useWebSocket(url, rawLogData) {
|
|
|
274
355
|
// process.exit();
|
|
275
356
|
// };
|
|
276
357
|
// }
|
|
277
|
-
}, [url, authKey]);
|
|
358
|
+
}, [url, authKey, callSummarizeAPI]);
|
|
278
359
|
const sendQuery = useCallback((messages, architecture, logs, planningDoc) => {
|
|
279
360
|
const socket = socketRef.current;
|
|
280
361
|
if (socket && socket.readyState === WebSocket.OPEN) {
|
|
@@ -121,4 +121,67 @@ export async function checkVersionAndExit() {
|
|
|
121
121
|
process.exit(1);
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
|
+
async function fetchLatestVersionFromNpm(packageName) {
|
|
125
|
+
try {
|
|
126
|
+
const response = await axios.get(`https://registry.npmjs.org/${packageName}/latest`, {
|
|
127
|
+
timeout: 5000, // 5 second timeout
|
|
128
|
+
});
|
|
129
|
+
return response.data?.version || null;
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function formatUpdateNotification(currentVersion, latestVersion, packageName) {
|
|
136
|
+
return `
|
|
137
|
+
📦 Update Available
|
|
138
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
139
|
+
|
|
140
|
+
Your version: ${currentVersion}
|
|
141
|
+
Latest version: ${latestVersion}
|
|
142
|
+
|
|
143
|
+
To update, run:
|
|
144
|
+
npm install -g ${packageName}@latest
|
|
145
|
+
|
|
146
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
147
|
+
`;
|
|
148
|
+
}
|
|
149
|
+
export async function checkForUpdates(packageName = "oncall-cli", forceCheck = false) {
|
|
150
|
+
if (!forceCheck) {
|
|
151
|
+
const cache = readVersionCache();
|
|
152
|
+
if (cache?.lastUpdateCheck) {
|
|
153
|
+
const now = Date.now();
|
|
154
|
+
const timeSinceLastCheck = now - cache.lastUpdateCheck;
|
|
155
|
+
if (timeSinceLastCheck < CACHE_DURATION_MS) {
|
|
156
|
+
if (cache.lastKnownVersion) {
|
|
157
|
+
const currentVersion = getCurrentVersion();
|
|
158
|
+
if (compareVersions(currentVersion, cache.lastKnownVersion) >= 0) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const currentVersion = getCurrentVersion();
|
|
170
|
+
const latestVersion = await fetchLatestVersionFromNpm(packageName);
|
|
171
|
+
if (!latestVersion) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const cache = readVersionCache() || {};
|
|
175
|
+
writeVersionCache({
|
|
176
|
+
...cache,
|
|
177
|
+
lastUpdateCheck: Date.now(),
|
|
178
|
+
lastKnownVersion: latestVersion,
|
|
179
|
+
});
|
|
180
|
+
if (compareVersions(currentVersion, latestVersion) < 0) {
|
|
181
|
+
console.warn(formatUpdateNotification(currentVersion, latestVersion, packageName));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
}
|
|
186
|
+
}
|
|
124
187
|
//# sourceMappingURL=version-check.js.map
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BaseMessage } from "langchain";
|
|
2
2
|
export declare function getContextLines(fileName: string, lineNumber: number, before?: number, after?: number): string;
|
|
3
|
+
export declare function cleanMessagesContent(messages: BaseMessage[]): BaseMessage[];
|
|
3
4
|
export declare function extractMessageContent(messages: BaseMessage[]): string;
|
|
4
5
|
/**
|
|
5
6
|
* Get a unique key for a message for deduplication purposes
|
package/dist/utils.js
CHANGED
|
@@ -59,10 +59,120 @@ export function getContextLines(fileName, lineNumber, before = 30, after = 30) {
|
|
|
59
59
|
// });
|
|
60
60
|
// redisClient.connect();
|
|
61
61
|
// export default redisClient;
|
|
62
|
+
function isIncompleteJSONChunk(content) {
|
|
63
|
+
if (typeof content !== "string")
|
|
64
|
+
return false;
|
|
65
|
+
const trimmed = content.trim();
|
|
66
|
+
const hasJSONFields = trimmed.includes('"next"') || trimmed.includes('"message"');
|
|
67
|
+
if (!hasJSONFields) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
if (trimmed.startsWith("{") && !trimmed.endsWith("}")) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
if (!trimmed.startsWith("{") && hasJSONFields) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
if (trimmed.endsWith("}") && !trimmed.startsWith("{")) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
function cleanMessageContent(content) {
|
|
82
|
+
if (typeof content !== "string")
|
|
83
|
+
return content;
|
|
84
|
+
const trimmed = content.trim();
|
|
85
|
+
if (isIncompleteJSONChunk(trimmed)) {
|
|
86
|
+
return "";
|
|
87
|
+
}
|
|
88
|
+
if ((trimmed.includes('"next"') || trimmed.includes('"message"')) && !trimmed.startsWith("{")) {
|
|
89
|
+
if (trimmed.endsWith("}")) {
|
|
90
|
+
return "";
|
|
91
|
+
}
|
|
92
|
+
if (trimmed.includes('":"') && (trimmed.includes('"next"') || trimmed.includes('"message"'))) {
|
|
93
|
+
return "";
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
|
|
97
|
+
try {
|
|
98
|
+
const parsed = JSON.parse(trimmed);
|
|
99
|
+
if (parsed && typeof parsed === "object" && typeof parsed.message === "string") {
|
|
100
|
+
return parsed.message;
|
|
101
|
+
}
|
|
102
|
+
else if (parsed && typeof parsed === "object") {
|
|
103
|
+
return "";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const jsonMatch = trimmed.match(/\{[\s\S]*?"message"[\s\S]*?\}/);
|
|
110
|
+
if (jsonMatch) {
|
|
111
|
+
try {
|
|
112
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
113
|
+
if (parsed && typeof parsed === "object" && typeof parsed.message === "string") {
|
|
114
|
+
return parsed.message;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return content;
|
|
121
|
+
}
|
|
122
|
+
export function cleanMessagesContent(messages) {
|
|
123
|
+
return messages.map(msg => {
|
|
124
|
+
if (typeof msg.content === "string") {
|
|
125
|
+
const cleaned = cleanMessageContent(msg.content);
|
|
126
|
+
const cleanedMsg = { ...msg };
|
|
127
|
+
cleanedMsg.content = cleaned;
|
|
128
|
+
return cleanedMsg;
|
|
129
|
+
}
|
|
130
|
+
return msg;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
62
133
|
export function extractMessageContent(messages) {
|
|
63
|
-
|
|
64
|
-
|
|
134
|
+
const fullContent = messages.reduce((prev, message) => {
|
|
135
|
+
let content = message.content;
|
|
136
|
+
if (typeof content === "string") {
|
|
137
|
+
return prev + content;
|
|
138
|
+
}
|
|
139
|
+
return prev;
|
|
65
140
|
}, "");
|
|
141
|
+
if (typeof fullContent === "string") {
|
|
142
|
+
const trimmed = fullContent.trim();
|
|
143
|
+
if (isIncompleteJSONChunk(trimmed)) {
|
|
144
|
+
return "";
|
|
145
|
+
}
|
|
146
|
+
if (trimmed.includes('"next"') || trimmed.includes('"message"')) {
|
|
147
|
+
const jsonMatch = trimmed.match(/\{[\s\S]*?"message"[\s\S]*?\}/);
|
|
148
|
+
if (jsonMatch) {
|
|
149
|
+
try {
|
|
150
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
151
|
+
if (parsed && typeof parsed === "object" && typeof parsed.message === "string") {
|
|
152
|
+
return parsed.message;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
|
|
159
|
+
try {
|
|
160
|
+
const parsed = JSON.parse(trimmed);
|
|
161
|
+
if (parsed && typeof parsed === "object" && typeof parsed.message === "string") {
|
|
162
|
+
return parsed.message;
|
|
163
|
+
}
|
|
164
|
+
else if (parsed && typeof parsed === "object") {
|
|
165
|
+
return "";
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// Not valid JSON
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return fullContent;
|
|
174
|
+
}
|
|
175
|
+
return fullContent;
|
|
66
176
|
}
|
|
67
177
|
/**
|
|
68
178
|
* Get a unique key for a message for deduplication purposes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oncall-cli",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"oncall": "bin/oncall.js"
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"@langchain/core": "^1.1.0",
|
|
28
28
|
"@langchain/openai": "^1.2.0",
|
|
29
29
|
"@types/ws": "^8.18.1",
|
|
30
|
+
"@vscode/ripgrep": "^1.17.0",
|
|
30
31
|
"axios": "^1.12.2",
|
|
31
32
|
"dotenv": "^17.2.3",
|
|
32
33
|
"ignore": "^5.3.2",
|
|
@@ -40,7 +41,6 @@
|
|
|
40
41
|
"node-pty": "0.10.1",
|
|
41
42
|
"patch-package": "^8.0.1",
|
|
42
43
|
"react": "^19.2.0",
|
|
43
|
-
"ripgrep-node": "^1.0.0",
|
|
44
44
|
"ws": "^8.18.3",
|
|
45
45
|
"yaml": "^2.8.2"
|
|
46
46
|
},
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
diff --git a/node_modules/ripgrep-node/dist/index.js b/node_modules/ripgrep-node/dist/index.js
|
|
2
|
-
index 215d5e7..6c96596 100644
|
|
3
|
-
--- a/node_modules/ripgrep-node/dist/index.js
|
|
4
|
-
+++ b/node_modules/ripgrep-node/dist/index.js
|
|
5
|
-
@@ -71,7 +71,7 @@ class RipGrep {
|
|
6
|
-
this.output = child_process_1.execSync(rgCommand).toString();
|
|
7
|
-
}
|
|
8
|
-
catch (error) {
|
|
9
|
-
- console.log(error.stdout);
|
|
10
|
-
+ // console.log(error.stdout);
|
|
11
|
-
}
|
|
12
|
-
return this;
|
|
13
|
-
};
|