mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-10 01:09:43 +02:00
fix: Ralph wizard uses file-based prompt to avoid screen escaping issues
- Add /api/sessions/:id/ralph-prompt/write endpoint to write prompt to @ralph_prompt.md - Wizard now writes full prompt to file, then sends simple read command to Claude - Fix session readiness check to wait for prompt character instead of notWorking flag - Add E2E test infrastructure for ralph-loop workflow (port 3190) - Add ralph-wizard-prod.mjs script for production testing Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
/**
|
||||
* Cleanup fixture for E2E tests
|
||||
* Tracks and cleans up all resources (sessions, cases, screens)
|
||||
*/
|
||||
|
||||
import { execSync } from 'node:child_process';
|
||||
import { existsSync, rmSync } from 'node:fs';
|
||||
import { homedir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
|
||||
export class CleanupTracker {
|
||||
private sessions: Set<string> = new Set();
|
||||
private cases: Set<string> = new Set();
|
||||
private screens: Set<string> = new Set();
|
||||
private baseUrl: string;
|
||||
private casesDir: string;
|
||||
|
||||
constructor(baseUrl: string) {
|
||||
this.baseUrl = baseUrl;
|
||||
this.casesDir = join(homedir(), 'claudeman-cases');
|
||||
}
|
||||
|
||||
/**
|
||||
* Track a session for cleanup
|
||||
*/
|
||||
trackSession(sessionId: string): void {
|
||||
this.sessions.add(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Track a case for cleanup
|
||||
*/
|
||||
trackCase(caseName: string): void {
|
||||
this.cases.add(caseName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Track a screen for cleanup
|
||||
*/
|
||||
trackScreen(screenName: string): void {
|
||||
this.screens.add(screenName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all tracked resources
|
||||
*/
|
||||
async cleanup(): Promise<void> {
|
||||
// Delete sessions via API
|
||||
for (const sessionId of this.sessions) {
|
||||
try {
|
||||
await fetch(`${this.baseUrl}/api/sessions/${sessionId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
} catch {
|
||||
// Ignore errors, session may already be deleted
|
||||
}
|
||||
}
|
||||
this.sessions.clear();
|
||||
|
||||
// Delete case directories
|
||||
for (const caseName of this.cases) {
|
||||
try {
|
||||
const casePath = join(this.casesDir, caseName);
|
||||
if (existsSync(casePath)) {
|
||||
rmSync(casePath, { recursive: true, force: true });
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
this.cases.clear();
|
||||
|
||||
// Kill tracked screens
|
||||
for (const screenName of this.screens) {
|
||||
this.killScreen(screenName);
|
||||
}
|
||||
this.screens.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill a specific screen session
|
||||
*/
|
||||
private killScreen(screenName: string): void {
|
||||
try {
|
||||
execSync(`screen -S ${screenName} -X quit 2>/dev/null`, {
|
||||
stdio: 'pipe',
|
||||
timeout: 5000,
|
||||
});
|
||||
} catch {
|
||||
// Screen may not exist or already be dead
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force cleanup ALL e2e-test-* cases and claudeman-* screens
|
||||
* Use this in afterAll to ensure no orphans remain
|
||||
*/
|
||||
async forceCleanupAll(): Promise<void> {
|
||||
// First, clean tracked resources
|
||||
await this.cleanup();
|
||||
|
||||
// Force kill ALL e2e-test sessions via API
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}/api/sessions`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.sessions) {
|
||||
for (const session of data.sessions) {
|
||||
if (session.name?.startsWith('e2e-test-') || session.workingDir?.includes('e2e-test-')) {
|
||||
try {
|
||||
await fetch(`${this.baseUrl}/api/sessions/${session.id}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Server may be down
|
||||
}
|
||||
|
||||
// Clean up e2e-test-* case directories
|
||||
try {
|
||||
if (existsSync(this.casesDir)) {
|
||||
const entries = execSync(`ls -1 "${this.casesDir}" 2>/dev/null || true`, {
|
||||
encoding: 'utf-8',
|
||||
timeout: 5000,
|
||||
}).trim().split('\n').filter(Boolean);
|
||||
|
||||
for (const entry of entries) {
|
||||
if (entry.startsWith('e2e-test-')) {
|
||||
const casePath = join(this.casesDir, entry);
|
||||
rmSync(casePath, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
|
||||
// Kill all e2e-test claudeman screens
|
||||
try {
|
||||
const screenList = execSync('screen -ls 2>/dev/null || true', {
|
||||
encoding: 'utf-8',
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
const screenLines = screenList.split('\n');
|
||||
for (const line of screenLines) {
|
||||
// Match claudeman-e2e-test-* screens
|
||||
const match = line.match(/\d+\.(claudeman-e2e-test-[^\s]+)/);
|
||||
if (match) {
|
||||
const screenName = match[1];
|
||||
this.killScreen(screenName);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore screen cleanup errors
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of screens that match a pattern
|
||||
*/
|
||||
getScreensByPattern(pattern: string): string[] {
|
||||
try {
|
||||
const screenList = execSync('screen -ls 2>/dev/null || true', {
|
||||
encoding: 'utf-8',
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
const screens: string[] = [];
|
||||
const screenLines = screenList.split('\n');
|
||||
for (const line of screenLines) {
|
||||
if (line.includes(pattern)) {
|
||||
const match = line.match(/\d+\.([^\s]+)/);
|
||||
if (match) {
|
||||
screens.push(match[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return screens;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a screen exists
|
||||
*/
|
||||
screenExists(screenName: string): boolean {
|
||||
try {
|
||||
const screenList = execSync('screen -ls 2>/dev/null || true', {
|
||||
encoding: 'utf-8',
|
||||
timeout: 5000,
|
||||
});
|
||||
return screenList.includes(screenName);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user