mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
Fix server crash when Claude status contains regex special characters (#398)
This commit is contained in:
parent
9dd282e8ce
commit
412aa3c035
2 changed files with 63 additions and 17 deletions
|
|
@ -24,6 +24,13 @@ function superDebug(message: string, ...args: unknown[]): void {
|
||||||
// biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape codes need control characters
|
// biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape codes need control characters
|
||||||
const ANSI_REGEX = /\x1b\[[0-9;]*[a-zA-Z]/g;
|
const ANSI_REGEX = /\x1b\[[0-9;]*[a-zA-Z]/g;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escape special regex characters in a string
|
||||||
|
*/
|
||||||
|
function escapeRegex(str: string): string {
|
||||||
|
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity status returned by app-specific parsers
|
* Activity status returned by app-specific parsers
|
||||||
*/
|
*/
|
||||||
|
|
@ -169,7 +176,10 @@ function parseClaudeStatus(data: string): ActivityStatus | null {
|
||||||
originalPos + fullMatch.length + 50
|
originalPos + fullMatch.length + 50
|
||||||
);
|
);
|
||||||
// Look for the status pattern in the middle section
|
// Look for the status pattern in the middle section
|
||||||
const statusPattern = new RegExp(`[^\n]*${indicator}[^\n]*to\\s+interrupt[^\n]*`, 'gi');
|
const statusPattern = new RegExp(
|
||||||
|
`[^\n]*${escapeRegex(indicator)}[^\n]*to\\s+interrupt[^\n]*`,
|
||||||
|
'gi'
|
||||||
|
);
|
||||||
const cleanedMiddle = middle.replace(statusPattern, '');
|
const cleanedMiddle = middle.replace(statusPattern, '');
|
||||||
filteredData = before + cleanedMiddle + after;
|
filteredData = before + cleanedMiddle + after;
|
||||||
}
|
}
|
||||||
|
|
@ -271,23 +281,28 @@ export class ActivityDetector {
|
||||||
|
|
||||||
// Try app-specific detection first
|
// Try app-specific detection first
|
||||||
if (this.detector) {
|
if (this.detector) {
|
||||||
const status = this.detector.parseStatus(data);
|
try {
|
||||||
if (status) {
|
const status = this.detector.parseStatus(data);
|
||||||
this.currentStatus = status;
|
if (status) {
|
||||||
this.lastStatusTime = Date.now();
|
this.currentStatus = status;
|
||||||
// Always update activity time for app-specific status
|
this.lastStatusTime = Date.now();
|
||||||
this.lastActivityTime = Date.now();
|
// Always update activity time for app-specific status
|
||||||
return {
|
this.lastActivityTime = Date.now();
|
||||||
filteredData: status.filteredData,
|
return {
|
||||||
activity: {
|
filteredData: status.filteredData,
|
||||||
isActive: true,
|
activity: {
|
||||||
lastActivityTime: this.lastActivityTime,
|
isActive: true,
|
||||||
specificStatus: {
|
lastActivityTime: this.lastActivityTime,
|
||||||
app: this.detector.name,
|
specificStatus: {
|
||||||
status: status.displayText,
|
app: this.detector.name,
|
||||||
|
status: status.displayText,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
};
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error in ${this.detector.name} status parser:`, error);
|
||||||
|
// Continue with unfiltered data if parsing fails
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,37 @@ describe('Activity Detector', () => {
|
||||||
expect(result.filteredData).toBe(claudeOutput);
|
expect(result.filteredData).toBe(claudeOutput);
|
||||||
expect(result.activity.specificStatus).toBeUndefined();
|
expect(result.activity.specificStatus).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle regex special characters in status indicators', () => {
|
||||||
|
const detector = new ActivityDetector(['claude']);
|
||||||
|
|
||||||
|
// Test with * which is a regex special character that was causing crashes
|
||||||
|
const statusWithStar = '* Processing… (42s · ↑ 1.2k tokens · esc to interrupt)\n';
|
||||||
|
const result1 = detector.processOutput(statusWithStar);
|
||||||
|
expect(result1.activity.specificStatus?.status).toBe('Processing (42s, ↑1.2k)');
|
||||||
|
expect(result1.filteredData).toBe('\n');
|
||||||
|
|
||||||
|
// Test with other regex special characters
|
||||||
|
const specialChars = ['*', '+', '?', '.', '^', '$', '|', '(', ')', '[', ']', '{', '}', '\\'];
|
||||||
|
for (const char of specialChars) {
|
||||||
|
const statusWithSpecialChar = `${char} Testing… (10s · ↑ 1.0k tokens · esc to interrupt)\n`;
|
||||||
|
const result = detector.processOutput(statusWithSpecialChar);
|
||||||
|
expect(result.activity.specificStatus?.status).toBe('Testing (10s, ↑1.0k)');
|
||||||
|
expect(result.filteredData).toBe('\n');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not crash when parsing fails', () => {
|
||||||
|
const detector = new ActivityDetector(['claude']);
|
||||||
|
|
||||||
|
// Even if something unexpected happens, it should not crash
|
||||||
|
const malformedOutput = 'Some output that might cause issues\n';
|
||||||
|
expect(() => {
|
||||||
|
const result = detector.processOutput(malformedOutput);
|
||||||
|
expect(result.filteredData).toBe(malformedOutput);
|
||||||
|
expect(result.activity.specificStatus).toBeUndefined();
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('registerDetector', () => {
|
describe('registerDetector', () => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue