chore: bump version to 0.1605
@@ -35,7 +35,7 @@ When user says "COM":
|
||||
1. Increment version in BOTH `package.json` AND `CLAUDE.md` (verify they match with `grep version package.json && grep Version CLAUDE.md`)
|
||||
2. Run: `git add -A && git commit -m "chore: bump version to X.XXXX" && git push && npm run build && systemctl --user restart claudeman-web`
|
||||
|
||||
**Version**: 0.1604 (must match `package.json` for npm publish)
|
||||
**Version**: 0.1605 (must match `package.json` for npm publish)
|
||||
|
||||
## Project Overview
|
||||
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
const { chromium } = require("playwright");
|
||||
|
||||
(async () => {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const page = await browser.newPage();
|
||||
page.setDefaultTimeout(10000);
|
||||
|
||||
await page.goto("http://localhost:3199", { waitUntil: "domcontentloaded" });
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// Click first session tab
|
||||
await page.locator(".session-tab").first().click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Open session options
|
||||
await page.locator(".session-tab.active .tab-gear").click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Function to check VISIBILITY of summary elements
|
||||
async function checkSummaryVisibility(tabName) {
|
||||
console.log("\n=== " + tabName.toUpperCase() + " TAB ===");
|
||||
|
||||
// Check if summary-specific elements are VISIBLE (not just in DOM)
|
||||
|
||||
const summarySection = page.locator(".modal:visible #summary-section, .modal:visible [id*='summary']");
|
||||
const summaryVisible = await summarySection.first().isVisible().catch(() => false);
|
||||
|
||||
const timelineSection = page.locator(".modal:visible .run-timeline, .modal:visible [class*='timeline']");
|
||||
const timelineVisible = await timelineSection.first().isVisible().catch(() => false);
|
||||
|
||||
const filterButtons = page.locator(".modal:visible .filter-btn, .modal:visible [class*='filter']");
|
||||
const filterVisible = await filterButtons.first().isVisible().catch(() => false);
|
||||
|
||||
const exportBtns = page.locator(".modal:visible .export-btn, .modal:visible button:has-text('Copy'), .modal:visible button:has-text('Download')");
|
||||
const exportVisible = await exportBtns.first().isVisible().catch(() => false);
|
||||
|
||||
console.log(" Summary section visible:", summaryVisible);
|
||||
console.log(" Timeline visible:", timelineVisible);
|
||||
console.log(" Filter buttons visible:", filterVisible);
|
||||
console.log(" Export buttons visible:", exportVisible);
|
||||
|
||||
return { summaryVisible, timelineVisible, filterVisible, exportVisible };
|
||||
}
|
||||
|
||||
// Check Respawn tab (default)
|
||||
const respawn = await checkSummaryVisibility("Respawn");
|
||||
|
||||
// Check Context tab
|
||||
await page.locator(".modal:visible .modal-tab-btn[data-tab='context']").click();
|
||||
await page.waitForTimeout(200);
|
||||
const context = await checkSummaryVisibility("Context");
|
||||
|
||||
// Check Ralph tab
|
||||
await page.locator(".modal:visible .modal-tab-btn[data-tab='ralph']").click();
|
||||
await page.waitForTimeout(200);
|
||||
const ralph = await checkSummaryVisibility("Ralph");
|
||||
|
||||
// Check Summary tab
|
||||
await page.locator(".modal:visible .modal-tab-btn[data-tab='summary']").click();
|
||||
await page.waitForTimeout(200);
|
||||
const summary = await checkSummaryVisibility("Summary");
|
||||
|
||||
await browser.close();
|
||||
|
||||
console.log("\n=== FINAL BUG ASSESSMENT ===");
|
||||
const hasBugRespawn = respawn.summaryVisible || respawn.timelineVisible;
|
||||
const hasBugContext = context.summaryVisible || context.timelineVisible;
|
||||
const hasBugRalph = ralph.summaryVisible || ralph.timelineVisible;
|
||||
const summaryWorking = summary.summaryVisible || summary.timelineVisible;
|
||||
|
||||
console.log("Respawn tab leaking summary content:", hasBugRespawn ? "YES - BUG EXISTS" : "No - Fixed");
|
||||
console.log("Context tab leaking summary content:", hasBugContext ? "YES - BUG EXISTS" : "No - Fixed");
|
||||
console.log("Ralph tab leaking summary content:", hasBugRalph ? "YES - BUG EXISTS" : "No - Fixed");
|
||||
console.log("Summary tab showing its content:", summaryWorking ? "Yes - Working correctly" : "No - Check if empty or hidden");
|
||||
|
||||
const bugFixed = (hasBugRespawn === false) && (hasBugContext === false) && (hasBugRalph === false);
|
||||
if (bugFixed) {
|
||||
console.log("\n*** BUG IS FIXED - Summary content only appears in Summary tab ***");
|
||||
} else {
|
||||
console.log("\n*** BUG STILL EXISTS - Summary content bleeding to other tabs ***");
|
||||
}
|
||||
|
||||
})().catch(e => { console.error(e); process.exit(1); });
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claudeman",
|
||||
"version": "0.1604",
|
||||
"version": "0.1605",
|
||||
"description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
|
||||
@@ -1,320 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Respawn Controller Monitor for w3-reddit-analyse
|
||||
# Monitors for 12 hours, logs issues and suggested fixes
|
||||
|
||||
SESSION_ID="5ecdf859-3605-42d8-b0a2-f8f0a1b5ff98"
|
||||
SESSION_NAME="w3-reddit-analyse"
|
||||
ISSUES_FILE="/home/arkon/default/claudeman/respawn-issues.md"
|
||||
FIXES_FILE="/home/arkon/default/claudeman/respawn-fixes.md"
|
||||
LOG_FILE="/home/arkon/default/claudeman/respawn-monitor.log"
|
||||
API_BASE="https://localhost:3000"
|
||||
DURATION_HOURS=12
|
||||
POLL_INTERVAL=30 # seconds
|
||||
|
||||
# Initialize files
|
||||
cat > "$ISSUES_FILE" << 'EOF'
|
||||
# Respawn Controller Issues - w3-reddit-analyse
|
||||
|
||||
Monitoring started: $(date)
|
||||
Session ID: 5ecdf859-3605-42d8-b0a2-f8f0a1b5ff98
|
||||
|
||||
## Issues Detected
|
||||
|
||||
EOF
|
||||
sed -i "s/\$(date)/$(date)/" "$ISSUES_FILE"
|
||||
|
||||
cat > "$FIXES_FILE" << 'EOF'
|
||||
# Suggested Fixes for Respawn Controller Issues
|
||||
|
||||
**DO NOT EXECUTE** - Waiting for user review
|
||||
|
||||
## Fixes
|
||||
|
||||
EOF
|
||||
|
||||
echo "=== Respawn Monitor Started: $(date) ===" > "$LOG_FILE"
|
||||
echo "Session: $SESSION_NAME ($SESSION_ID)" >> "$LOG_FILE"
|
||||
echo "Duration: $DURATION_HOURS hours" >> "$LOG_FILE"
|
||||
echo "Poll interval: ${POLL_INTERVAL}s" >> "$LOG_FILE"
|
||||
echo "" >> "$LOG_FILE"
|
||||
|
||||
# Track state for anomaly detection
|
||||
PREV_STATE=""
|
||||
PREV_TOKENS=0
|
||||
STATE_STUCK_COUNT=0
|
||||
LAST_ACTIVITY_TIME=$(date +%s)
|
||||
ISSUE_COUNT=0
|
||||
|
||||
log_issue() {
|
||||
local severity="$1"
|
||||
local title="$2"
|
||||
local details="$3"
|
||||
ISSUE_COUNT=$((ISSUE_COUNT + 1))
|
||||
|
||||
echo "" >> "$ISSUES_FILE"
|
||||
echo "### Issue #$ISSUE_COUNT [$severity] - $(date '+%Y-%m-%d %H:%M:%S')" >> "$ISSUES_FILE"
|
||||
echo "**$title**" >> "$ISSUES_FILE"
|
||||
echo "" >> "$ISSUES_FILE"
|
||||
echo "$details" >> "$ISSUES_FILE"
|
||||
echo "" >> "$ISSUES_FILE"
|
||||
|
||||
echo "[$(date '+%H:%M:%S')] ISSUE #$ISSUE_COUNT [$severity]: $title" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_fix() {
|
||||
local issue_num="$1"
|
||||
local fix_title="$2"
|
||||
local fix_details="$3"
|
||||
|
||||
echo "" >> "$FIXES_FILE"
|
||||
echo "### Fix for Issue #$issue_num" >> "$FIXES_FILE"
|
||||
echo "**$fix_title**" >> "$FIXES_FILE"
|
||||
echo "" >> "$FIXES_FILE"
|
||||
echo "$fix_details" >> "$FIXES_FILE"
|
||||
echo "" >> "$FIXES_FILE"
|
||||
}
|
||||
|
||||
END_TIME=$(($(date +%s) + DURATION_HOURS * 3600))
|
||||
|
||||
while [ $(date +%s) -lt $END_TIME ]; do
|
||||
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# Fetch session status
|
||||
RESPONSE=$(curl -sk "$API_BASE/api/sessions/$SESSION_ID" 2>/dev/null)
|
||||
|
||||
if [ -z "$RESPONSE" ] || [ "$RESPONSE" = "null" ]; then
|
||||
log_issue "CRITICAL" "API request failed or session not found" "Could not fetch session data. Server may be down or session deleted."
|
||||
log_fix "$ISSUE_COUNT" "Check server status" "1. Check if claudeman-web is running: \`systemctl --user status claudeman-web\`
|
||||
2. If down, restart: \`systemctl --user restart claudeman-web\`
|
||||
3. Check server logs: \`journalctl --user -u claudeman-web -n 50\`"
|
||||
sleep $POLL_INTERVAL
|
||||
continue
|
||||
fi
|
||||
|
||||
# Parse response
|
||||
STATUS=$(echo "$RESPONSE" | jq -r '.status // "unknown"')
|
||||
INPUT_TOKENS=$(echo "$RESPONSE" | jq -r '.inputTokens // 0')
|
||||
OUTPUT_TOKENS=$(echo "$RESPONSE" | jq -r '.outputTokens // 0')
|
||||
TOTAL_TOKENS=$((INPUT_TOKENS + OUTPUT_TOKENS))
|
||||
|
||||
# Always fetch respawn controller details from dedicated endpoint
|
||||
RESPAWN_DETAILS=$(curl -sk "$API_BASE/api/sessions/$SESSION_ID/respawn" 2>/dev/null)
|
||||
RESPAWN_ENABLED=$(echo "$RESPAWN_DETAILS" | jq -r '.enabled // false')
|
||||
RESPAWN_STATE=$(echo "$RESPAWN_DETAILS" | jq -r '.state // "null"')
|
||||
|
||||
# Fetch detailed detection status if enabled
|
||||
if [ "$RESPAWN_ENABLED" = "true" ]; then
|
||||
DETECTION_STATUS=$(echo "$RESPAWN_DETAILS" | jq -c '.detection // {}')
|
||||
IDLE_CONFIDENCE=$(echo "$DETECTION_STATUS" | jq -r '.confidenceLevel // 0')
|
||||
CYCLE_COUNT=$(echo "$RESPAWN_DETAILS" | jq -r '.cycleCount // 0')
|
||||
LAST_ACTIVITY=$(echo "$RESPAWN_DETAILS" | jq -r '.lastActivityTime // 0')
|
||||
TIME_SINCE_ACTIVITY=$(echo "$RESPAWN_DETAILS" | jq -r '.timeSinceActivity // 0')
|
||||
STATUS_TEXT=$(echo "$DETECTION_STATUS" | jq -r '.statusText // "unknown"')
|
||||
WAITING_FOR=$(echo "$DETECTION_STATUS" | jq -r '.waitingFor // "unknown"')
|
||||
AI_CHECK_STATUS=$(echo "$DETECTION_STATUS" | jq -r '.aiCheck.status // "unknown"')
|
||||
AI_LAST_VERDICT=$(echo "$DETECTION_STATUS" | jq -r '.aiCheck.lastVerdict // "none"')
|
||||
LAST_ERROR="none" # Respawn controller doesn't expose lastError directly
|
||||
fi
|
||||
|
||||
echo "[$TIMESTAMP] status=$STATUS respawn=$RESPAWN_ENABLED state=$RESPAWN_STATE cycle=$CYCLE_COUNT confidence=$IDLE_CONFIDENCE tokens=$TOTAL_TOKENS" >> "$LOG_FILE"
|
||||
|
||||
# Check 1: Respawn not enabled when it should be
|
||||
if [ "$RESPAWN_ENABLED" = "null" ] || [ "$RESPAWN_ENABLED" = "false" ]; then
|
||||
if [ -z "$RESPAWN_NOT_ENABLED_LOGGED" ]; then
|
||||
log_issue "INFO" "Respawn controller not enabled" "Respawn is currently disabled for this session. State: $RESPAWN_ENABLED"
|
||||
log_fix "$ISSUE_COUNT" "Enable respawn controller" "Via API:
|
||||
\`\`\`bash
|
||||
curl -sk -X POST '$API_BASE/api/sessions/$SESSION_ID/respawn/enable'
|
||||
\`\`\`
|
||||
|
||||
Or via web UI: Open session tab -> Respawn panel -> Enable"
|
||||
RESPAWN_NOT_ENABLED_LOGGED=1
|
||||
fi
|
||||
else
|
||||
unset RESPAWN_NOT_ENABLED_LOGGED
|
||||
|
||||
# Check 2: State stuck for too long
|
||||
if [ "$RESPAWN_STATE" = "$PREV_STATE" ] && [ -n "$PREV_STATE" ]; then
|
||||
STATE_STUCK_COUNT=$((STATE_STUCK_COUNT + 1))
|
||||
|
||||
# Stuck in same state for 10+ minutes (20 polls at 30s)
|
||||
if [ $STATE_STUCK_COUNT -ge 20 ]; then
|
||||
STUCK_MINS=$((STATE_STUCK_COUNT * POLL_INTERVAL / 60))
|
||||
log_issue "WARNING" "State stuck: $RESPAWN_STATE for ${STUCK_MINS}+ minutes" "The respawn controller has been in '$RESPAWN_STATE' state for over $STUCK_MINS minutes.
|
||||
Time since activity: ${TIME_SINCE_ACTIVITY}ms
|
||||
Detection status: $DETECTION_STATUS"
|
||||
|
||||
case "$RESPAWN_STATE" in
|
||||
"watching")
|
||||
log_fix "$ISSUE_COUNT" "Watching state stuck" "The controller is watching but not detecting idle. Possible causes:
|
||||
1. Session is actively working (expected behavior)
|
||||
2. Idle detection thresholds too high
|
||||
|
||||
Check session activity:
|
||||
\`\`\`bash
|
||||
curl -sk '$API_BASE/api/sessions/$SESSION_ID' | jq '{status, lastActivityAt}'
|
||||
\`\`\`
|
||||
|
||||
If truly idle, try adjusting config:
|
||||
\`\`\`bash
|
||||
curl -sk -X POST '$API_BASE/api/sessions/$SESSION_ID/respawn/config' -H 'Content-Type: application/json' -d '{\"idleThresholdMs\": 30000}'
|
||||
\`\`\`"
|
||||
;;
|
||||
"confirming_idle")
|
||||
log_fix "$ISSUE_COUNT" "Confirming idle stuck" "Stuck confirming idle state. The session may be oscillating between idle/working.
|
||||
|
||||
Check idle confidence:
|
||||
\`\`\`bash
|
||||
curl -sk '$API_BASE/api/sessions/$SESSION_ID/respawn/status' | jq '.detectionStatus'
|
||||
\`\`\`
|
||||
|
||||
Consider increasing confirmation time or check for output patterns that reset detection."
|
||||
;;
|
||||
"ai_checking")
|
||||
log_fix "$ISSUE_COUNT" "AI check stuck" "AI idle check is taking too long or failing.
|
||||
|
||||
Check AI checker status and logs:
|
||||
\`\`\`bash
|
||||
journalctl --user -u claudeman-web -n 100 | grep -i 'ai.*check\\|idle.*check'
|
||||
\`\`\`
|
||||
|
||||
The AI checker spawns a separate Claude process. It may be hanging. Consider:
|
||||
1. Check for stuck Claude processes: \`ps aux | grep claude\`
|
||||
2. Restart the respawn controller:
|
||||
\`\`\`bash
|
||||
curl -sk -X POST '$API_BASE/api/sessions/$SESSION_ID/respawn/stop'
|
||||
sleep 2
|
||||
curl -sk -X POST '$API_BASE/api/sessions/$SESSION_ID/respawn/start'
|
||||
\`\`\`"
|
||||
;;
|
||||
"sending_"*|"waiting_"*)
|
||||
log_fix "$ISSUE_COUNT" "Send/wait state stuck" "Stuck in $RESPAWN_STATE - the controller is trying to send a command but may not be receiving expected response.
|
||||
|
||||
Check terminal output:
|
||||
\`\`\`bash
|
||||
curl -sk '$API_BASE/api/sessions/$SESSION_ID/terminal?tail=10000' | tail -100
|
||||
\`\`\`
|
||||
|
||||
Try restarting respawn:
|
||||
\`\`\`bash
|
||||
curl -sk -X POST '$API_BASE/api/sessions/$SESSION_ID/respawn/stop'
|
||||
curl -sk -X POST '$API_BASE/api/sessions/$SESSION_ID/respawn/start'
|
||||
\`\`\`"
|
||||
;;
|
||||
*)
|
||||
log_fix "$ISSUE_COUNT" "Unknown stuck state" "State '$RESPAWN_STATE' is stuck. Review respawn-controller.ts for this state's expected behavior and transitions."
|
||||
;;
|
||||
esac
|
||||
STATE_STUCK_COUNT=0
|
||||
fi
|
||||
else
|
||||
STATE_STUCK_COUNT=0
|
||||
PREV_STATE="$RESPAWN_STATE"
|
||||
fi
|
||||
|
||||
# Check 3: Error in respawn controller
|
||||
if [ "$LAST_ERROR" != "none" ] && [ "$LAST_ERROR" != "null" ] && [ -n "$LAST_ERROR" ]; then
|
||||
log_issue "ERROR" "Respawn controller error" "Error reported: $LAST_ERROR"
|
||||
log_fix "$ISSUE_COUNT" "Resolve respawn error" "Error: $LAST_ERROR
|
||||
|
||||
General recovery steps:
|
||||
1. Stop respawn: \`curl -sk -X POST '$API_BASE/api/sessions/$SESSION_ID/respawn/stop'\`
|
||||
2. Check session health: \`curl -sk '$API_BASE/api/sessions/$SESSION_ID' | jq .\`
|
||||
3. Restart respawn: \`curl -sk -X POST '$API_BASE/api/sessions/$SESSION_ID/respawn/start'\`
|
||||
|
||||
If error persists, check server logs:
|
||||
\`journalctl --user -u claudeman-web -n 200 | grep -i error\`"
|
||||
fi
|
||||
|
||||
# Check 4: Token count not progressing during active work
|
||||
if [ "$STATUS" = "busy" ] && [ "$RESPAWN_STATE" = "watching" ]; then
|
||||
if [ $TOTAL_TOKENS -eq $PREV_TOKENS ] && [ $PREV_TOKENS -gt 0 ]; then
|
||||
TOKENS_STUCK_COUNT=$((TOKENS_STUCK_COUNT + 1))
|
||||
if [ $TOKENS_STUCK_COUNT -ge 10 ]; then # 5 minutes
|
||||
log_issue "WARNING" "Tokens not increasing while busy" "Session status is 'busy' but token count hasn't changed in 5+ minutes.
|
||||
Current: $TOTAL_TOKENS, Previous: $PREV_TOKENS
|
||||
This could indicate a hung Claude process."
|
||||
log_fix "$ISSUE_COUNT" "Check for hung session" "The session may be hung. Check:
|
||||
|
||||
1. Session process status:
|
||||
\`\`\`bash
|
||||
screen -ls | grep reddit
|
||||
\`\`\`
|
||||
|
||||
2. Attach to screen and check:
|
||||
\`\`\`bash
|
||||
screen -r claudeman-w3-reddit-analyse
|
||||
# Press Ctrl+A D to detach
|
||||
\`\`\`
|
||||
|
||||
3. If hung, restart the session via web UI or:
|
||||
\`\`\`bash
|
||||
curl -sk -X POST '$API_BASE/api/sessions/$SESSION_ID/stop'
|
||||
# Then start a new session
|
||||
\`\`\`"
|
||||
TOKENS_STUCK_COUNT=0
|
||||
fi
|
||||
else
|
||||
TOKENS_STUCK_COUNT=0
|
||||
fi
|
||||
fi
|
||||
PREV_TOKENS=$TOTAL_TOKENS
|
||||
|
||||
# Check 5: Rapid state cycling (flapping)
|
||||
if [ "$RESPAWN_STATE" != "$PREV_STATE" ]; then
|
||||
STATE_CHANGES=$((STATE_CHANGES + 1))
|
||||
fi
|
||||
|
||||
# Check every 5 minutes for flapping
|
||||
if [ $((STATE_CHANGES_CHECK_COUNT % 10)) -eq 0 ] && [ $STATE_CHANGES_CHECK_COUNT -gt 0 ]; then
|
||||
if [ $STATE_CHANGES -gt 20 ]; then # More than 20 state changes in 5 mins
|
||||
log_issue "WARNING" "State flapping detected" "Respawn controller changed states $STATE_CHANGES times in the last 5 minutes. This indicates instability."
|
||||
log_fix "$ISSUE_COUNT" "Reduce state flapping" "The controller is cycling states too rapidly. This usually means:
|
||||
1. Idle detection is too sensitive
|
||||
2. Session output is oscillating
|
||||
|
||||
Adjust detection thresholds:
|
||||
\`\`\`bash
|
||||
curl -sk -X POST '$API_BASE/api/sessions/$SESSION_ID/respawn/config' -H 'Content-Type: application/json' -d '{
|
||||
\"idleThresholdMs\": 60000,
|
||||
\"completionConfirmMs\": 15000
|
||||
}'
|
||||
\`\`\`"
|
||||
fi
|
||||
STATE_CHANGES=0
|
||||
fi
|
||||
STATE_CHANGES_CHECK_COUNT=$((STATE_CHANGES_CHECK_COUNT + 1))
|
||||
|
||||
# Check 6: High token usage without respawn cycling
|
||||
if [ $TOTAL_TOKENS -gt 180000 ]; then
|
||||
log_issue "WARNING" "High token usage: $TOTAL_TOKENS" "Token count is very high. Auto-compact or respawn should have triggered by now."
|
||||
log_fix "$ISSUE_COUNT" "Handle high token usage" "Tokens at $TOTAL_TOKENS. Consider:
|
||||
|
||||
1. Manual compact:
|
||||
\`\`\`bash
|
||||
curl -sk -X POST '$API_BASE/api/sessions/$SESSION_ID/input' -H 'Content-Type: application/json' -d '{\"input\": \"/compact\\r\"}'
|
||||
\`\`\`
|
||||
|
||||
2. Check auto-compact settings:
|
||||
\`\`\`bash
|
||||
curl -sk '$API_BASE/api/sessions/$SESSION_ID' | jq '{autoCompactEnabled, autoCompactThreshold}'
|
||||
\`\`\`"
|
||||
fi
|
||||
fi
|
||||
|
||||
sleep $POLL_INTERVAL
|
||||
done
|
||||
|
||||
echo "" >> "$ISSUES_FILE"
|
||||
echo "---" >> "$ISSUES_FILE"
|
||||
echo "Monitoring completed: $(date)" >> "$ISSUES_FILE"
|
||||
echo "Total issues detected: $ISSUE_COUNT" >> "$ISSUES_FILE"
|
||||
|
||||
echo "" >> "$FIXES_FILE"
|
||||
echo "---" >> "$FIXES_FILE"
|
||||
echo "Monitoring completed: $(date)" >> "$FIXES_FILE"
|
||||
|
||||
echo "" >> "$LOG_FILE"
|
||||
echo "=== Monitoring Complete: $(date) ===" >> "$LOG_FILE"
|
||||
echo "Total issues: $ISSUE_COUNT" >> "$LOG_FILE"
|
||||
@@ -1,61 +0,0 @@
|
||||
/**
|
||||
* Take screenshots of the typing-visible-below bug.
|
||||
* Intercepts input to prevent damage to live sessions.
|
||||
*/
|
||||
import { chromium } from 'playwright';
|
||||
|
||||
async function main() {
|
||||
const browser = await chromium.launch({ headless: true, args: ['--window-size=1280,800'] });
|
||||
const context = await browser.newContext({ viewport: { width: 1280, height: 800 } });
|
||||
const page = await context.newPage();
|
||||
|
||||
// Intercept input to prevent sending to real sessions
|
||||
await page.route('**/api/sessions/*/input', route => route.fulfill({
|
||||
status: 200, contentType: 'application/json', body: '{"success":true}'
|
||||
}));
|
||||
|
||||
console.log('Opening Claudeman...');
|
||||
await page.goto('http://localhost:3000');
|
||||
await page.waitForSelector('.xterm-screen', { timeout: 10000 });
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// Screenshot 1: Before typing
|
||||
await page.screenshot({ path: '/tmp/before-typing.png' });
|
||||
console.log('Screenshot 1: /tmp/before-typing.png');
|
||||
|
||||
// Click on the terminal to focus it
|
||||
await page.click('.xterm-screen');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Type a short string
|
||||
await page.keyboard.type('hello world this is a test', { delay: 30 });
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: '/tmp/after-short-typing.png' });
|
||||
console.log('Screenshot 2: /tmp/after-short-typing.png');
|
||||
|
||||
// Type a LONG string that approaches the right edge
|
||||
await page.keyboard.type(' and now I am typing a very long line that should approach the right edge of the terminal to test whether text wraps incorrectly below the status bar area which would be the typing visible below bug', { delay: 15 });
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: '/tmp/after-long-typing.png' });
|
||||
console.log('Screenshot 3: /tmp/after-long-typing.png');
|
||||
|
||||
// Wait for flush + SIGWINCH to settle
|
||||
await page.waitForTimeout(1000);
|
||||
await page.screenshot({ path: '/tmp/after-settle.png' });
|
||||
console.log('Screenshot 4: /tmp/after-settle.png (after 1s settle)');
|
||||
|
||||
// Try typing even MORE to really push past the edge
|
||||
await page.keyboard.type(' AAAA BBBB CCCC DDDD EEEE FFFF GGGG HHHH IIII JJJJ KKKK LLLL MMMM NNNN OOOO PPPP', { delay: 10 });
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: '/tmp/after-overflow.png' });
|
||||
console.log('Screenshot 5: /tmp/after-overflow.png (past right edge)');
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
await page.screenshot({ path: '/tmp/after-overflow-settle.png' });
|
||||
console.log('Screenshot 6: /tmp/after-overflow-settle.png (settled)');
|
||||
|
||||
await browser.close();
|
||||
console.log('Done');
|
||||
}
|
||||
|
||||
main().catch(err => { console.error(err); process.exit(1); });
|
||||
@@ -1,71 +0,0 @@
|
||||
const { chromium } = require('playwright');
|
||||
|
||||
(async () => {
|
||||
const browser = await chromium.launch({
|
||||
headless: true,
|
||||
args: ['--ignore-certificate-errors']
|
||||
});
|
||||
|
||||
const context = await browser.newContext({ ignoreHTTPSErrors: true });
|
||||
const page = await context.newPage();
|
||||
|
||||
// Capture console messages
|
||||
const consoleLogs = [];
|
||||
page.on('console', msg => {
|
||||
consoleLogs.push('[' + msg.type() + '] ' + msg.text());
|
||||
});
|
||||
|
||||
console.log('Opening https://localhost:3000...');
|
||||
await page.goto('https://localhost:3000', { waitUntil: 'networkidle' });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
console.log('Looking for shell button...');
|
||||
// Click Run Shell button
|
||||
const shellBtn = page.locator('button:has-text("Run Shell")');
|
||||
if (await shellBtn.count() > 0) {
|
||||
await shellBtn.click();
|
||||
console.log('Clicked Run Shell');
|
||||
} else {
|
||||
console.log('Run Shell button not found');
|
||||
}
|
||||
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// Type command in terminal
|
||||
console.log('Typing command...');
|
||||
await page.keyboard.type('echo "tail -f /tmp/test.log"');
|
||||
await page.keyboard.press('Enter');
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Take screenshot
|
||||
await page.screenshot({ path: '/tmp/link-test-1.png', fullPage: true });
|
||||
console.log('Screenshot 1 saved');
|
||||
|
||||
// Move mouse over terminal area (approximate coordinates for /tmp/test.log)
|
||||
const terminal = page.locator('.xterm-screen');
|
||||
const box = await terminal.boundingBox();
|
||||
if (box) {
|
||||
console.log('Terminal at: ' + box.x + ', ' + box.y + ', ' + box.width + 'x' + box.height);
|
||||
|
||||
// Move mouse across the area where the path should be (roughly line 2)
|
||||
const y = box.y + 50; // Approximate line 2
|
||||
for (let x = box.x + 50; x < box.x + 300; x += 10) {
|
||||
await page.mouse.move(x, y);
|
||||
await page.waitForTimeout(50);
|
||||
}
|
||||
}
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
await page.screenshot({ path: '/tmp/link-test-2.png', fullPage: true });
|
||||
console.log('Screenshot 2 saved');
|
||||
|
||||
// Print console logs
|
||||
console.log('\n=== Console Logs ===');
|
||||
consoleLogs.filter(l => l.includes('LinkProvider')).forEach(l => console.log(l));
|
||||
|
||||
// Check if "Found links" appeared
|
||||
const foundLinks = consoleLogs.some(l => l.includes('Found links'));
|
||||
console.log('\nLinks detected:', foundLinks);
|
||||
|
||||
await browser.close();
|
||||
})();
|
||||
@@ -1,80 +0,0 @@
|
||||
|
||||
> claudeman@0.1490 test
|
||||
> vitest run test/state-store.test.ts
|
||||
|
||||
|
||||
[1m[46m RUN [49m[22m [36mv4.0.18 [39m[90m/home/arkon/default/claudeman[39m
|
||||
|
||||
[31m❯[39m test/state-store.test.ts [2m([22m[2m33 tests[22m[2m | [22m[31m1 failed[39m[2m)[22m[32m 57[2mms[22m[39m
|
||||
[32m✓[39m should create initial state when no file exists[32m 6[2mms[22m[39m
|
||||
[32m✓[39m should set state file path in config[32m 2[2mms[22m[39m
|
||||
[31m [31m×[31m should debounce saves by default[39m[32m 15[2mms[22m[39m
|
||||
[32m✓[39m should write immediately with saveNow[32m 2[2mms[22m[39m
|
||||
[32m✓[39m should flush pending saves[32m 1[2mms[22m[39m
|
||||
[32m✓[39m should set and get sessions[32m 1[2mms[22m[39m
|
||||
[32m✓[39m should return null for non-existent session[32m 1[2mms[22m[39m
|
||||
[32m✓[39m should remove sessions[32m 1[2mms[22m[39m
|
||||
[32m✓[39m should get all sessions[32m 2[2mms[22m[39m
|
||||
[32m✓[39m should set and get tasks[32m 1[2mms[22m[39m
|
||||
[32m✓[39m should return null for non-existent task[32m 1[2mms[22m[39m
|
||||
[32m✓[39m should remove tasks[32m 1[2mms[22m[39m
|
||||
[32m✓[39m should get all tasks[32m 1[2mms[22m[39m
|
||||
[32m✓[39m should get and set Ralph Loop state[32m 1[2mms[22m[39m
|
||||
[32m✓[39m should merge partial updates[32m 1[2mms[22m[39m
|
||||
[32m✓[39m should get and set config[32m 1[2mms[22m[39m
|
||||
[32m✓[39m should merge partial updates[32m 1[2mms[22m[39m
|
||||
[32m✓[39m should reset state to initial values[32m 1[2mms[22m[39m
|
||||
[32m✓[39m should preserve state file path in config[32m 1[2mms[22m[39m
|
||||
[32m✓[39m should get and set ralph state[32m 1[2mms[22m[39m
|
||||
[32m✓[39m should return null for non-existent ralph state[32m 1[2mms[22m[39m
|
||||
[32m✓[39m should update ralph state with partial merge[32m 1[2mms[22m[39m
|
||||
[32m✓[39m should create initial state on update if none exists[32m 1[2mms[22m[39m
|
||||
[32m✓[39m should remove ralph state[32m 1[2mms[22m[39m
|
||||
[32m✓[39m should get all ralph states[32m 1[2mms[22m[39m
|
||||
[32m✓[39m should flush both main and ralph state[32m 2[2mms[22m[39m
|
||||
[32m✓[39m should persist and restore state across instances[32m 2[2mms[22m[39m
|
||||
[32m✓[39m should initialize global stats with zero values[32m 1[2mms[22m[39m
|
||||
[32m✓[39m should add to global stats[32m 1[2mms[22m[39m
|
||||
[32m✓[39m should accumulate global stats across multiple calls[32m 1[2mms[22m[39m
|
||||
[32m✓[39m should increment sessions created counter[32m 1[2mms[22m[39m
|
||||
[32m✓[39m should compute aggregate stats combining global and active sessions[32m 1[2mms[22m[39m
|
||||
[32m✓[39m should persist global stats across instances[32m 1[2mms[22m[39m
|
||||
[90mstderr[2m | StateStore.saveNowAsync (/home/arkon/default/claudeman/src/state-store.ts:190:15)
|
||||
[22m[39m[StateStore] Failed to write state file: Error: ENOENT: no such file or directory, open '/tmp/claudeman-test-1770917629701/state.json.tmp'
|
||||
[90m at open (node:internal/fs/promises:636:25)[39m
|
||||
[90m at writeFile (node:internal/fs/promises:1205:14)[39m
|
||||
at StateStore.saveNowAsync [90m(/home/arkon/default/claudeman/[39msrc/state-store.ts:180:7[90m)[39m {
|
||||
errno: [33m-2[39m,
|
||||
code: [32m'ENOENT'[39m,
|
||||
syscall: [32m'open'[39m,
|
||||
path: [32m'/tmp/claudeman-test-1770917629701/state.json.tmp'[39m
|
||||
}
|
||||
|
||||
|
||||
[31m⎯⎯⎯⎯⎯⎯⎯[39m[1m[41m Failed Tests 1 [49m[22m[31m⎯⎯⎯⎯⎯⎯⎯[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m test/state-store.test.ts[2m > [22mStateStore[2m > [22msave and saveNow[2m > [22mshould debounce saves by default
|
||||
[31m[1mAssertionError[22m: expected false to be true // Object.is equality[39m
|
||||
|
||||
[32m- Expected[39m
|
||||
[31m+ Received[39m
|
||||
|
||||
[32m- true[39m
|
||||
[31m+ false[39m
|
||||
|
||||
[36m [2m❯[22m test/state-store.test.ts:[2m84:40[22m[39m
|
||||
[90m 82| [39m
|
||||
[90m 83| [39m [90m// Now file should exist[39m
|
||||
[90m 84| [39m [34mexpect[39m([34mexistsSync[39m(testFilePath))[33m.[39m[34mtoBe[39m([35mtrue[39m)[33m;[39m
|
||||
[90m | [39m [31m^[39m
|
||||
[90m 85| [39m })[33m;[39m
|
||||
[90m 86| [39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯[22m[39m
|
||||
|
||||
|
||||
[2m Test Files [22m [1m[31m1 failed[39m[22m[90m (1)[39m
|
||||
[2m Tests [22m [1m[31m1 failed[39m[22m[2m | [22m[1m[32m32 passed[39m[22m[90m (33)[39m
|
||||
[2m Start at [22m 18:33:49
|
||||
[2m Duration [22m 625ms[2m (transform 207ms, setup 49ms, import 219ms, tests 57ms, environment 0ms)[22m
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"status": "failed",
|
||||
"failedTests": []
|
||||
}
|
||||
|
Before Width: | Height: | Size: 207 KiB |
|
Before Width: | Height: | Size: 206 KiB |
|
Before Width: | Height: | Size: 201 KiB |
|
Before Width: | Height: | Size: 201 KiB |
|
Before Width: | Height: | Size: 202 KiB |
|
Before Width: | Height: | Size: 216 KiB |
|
Before Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 188 KiB |
@@ -1,99 +0,0 @@
|
||||
import { chromium } from 'playwright';
|
||||
|
||||
async function main() {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const page = await browser.newPage();
|
||||
|
||||
console.log('1. Loading page...');
|
||||
await page.goto('http://localhost:3000', { waitUntil: 'domcontentloaded', timeout: 10000 });
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
console.log('2. Opening App Settings...');
|
||||
await page.locator('button[title="App Settings"]').click();
|
||||
await page.waitForTimeout(500);
|
||||
console.log(` Modal active: ${(await page.locator('#appSettingsModal.active').count()) > 0}`);
|
||||
|
||||
console.log('3. Clicking Display tab...');
|
||||
await page.locator('[data-tab="settings-display"]').click();
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Scroll the modal body to the bottom to reveal all settings
|
||||
await page.locator('#settings-display').evaluate(el => el.scrollTop = el.scrollHeight);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
console.log('4. Tab Switch Animation section...');
|
||||
const header = page.locator('.settings-section-header:text("Tab Switch Animation")');
|
||||
console.log(` Header visible: ${await header.isVisible()}`);
|
||||
|
||||
console.log('5. Checking controls...');
|
||||
const toggleLabel = page.locator('#appSettingsTabFadeEnabled').locator('xpath=ancestor::label');
|
||||
console.log(` ✓ Enable toggle: visible=${await toggleLabel.isVisible()}`);
|
||||
for (const id of ['appSettingsTabFadeInDuration','appSettingsTabFadeInEasing','appSettingsTabFadeOutDuration','appSettingsTabFadeOutEasing']) {
|
||||
console.log(` ✓ #${id}: visible=${await page.locator('#' + id).isVisible()}`);
|
||||
}
|
||||
|
||||
console.log('6. Defaults...');
|
||||
const enabled = await page.locator('#appSettingsTabFadeEnabled').isChecked();
|
||||
const inDur = await page.locator('#appSettingsTabFadeInDuration').inputValue();
|
||||
const inEase = await page.locator('#appSettingsTabFadeInEasing').inputValue();
|
||||
const outDur = await page.locator('#appSettingsTabFadeOutDuration').inputValue();
|
||||
const outEase = await page.locator('#appSettingsTabFadeOutEasing').inputValue();
|
||||
console.log(` enabled=${enabled}, inDur=${inDur}, inEase=${inEase}, outDur=${outDur}, outEase=${outEase}`);
|
||||
|
||||
console.log('7. Modifying settings...');
|
||||
// Click the toggle label (switch slider) instead of the hidden checkbox
|
||||
await toggleLabel.click();
|
||||
const nowChecked = await page.locator('#appSettingsTabFadeEnabled').isChecked();
|
||||
console.log(` Toggle clicked, now checked=${nowChecked}`);
|
||||
|
||||
await page.locator('#appSettingsTabFadeInDuration').fill('500');
|
||||
await page.locator('#appSettingsTabFadeInEasing').selectOption('linear');
|
||||
await page.locator('#appSettingsTabFadeOutDuration').fill('200');
|
||||
await page.locator('#appSettingsTabFadeOutEasing').selectOption('ease-out');
|
||||
console.log(' All values changed');
|
||||
|
||||
// Save — scroll back up to find the save button in the footer
|
||||
const saveBtn = page.locator('#appSettingsModal .modal-footer button.btn-primary');
|
||||
await saveBtn.scrollIntoViewIfNeeded();
|
||||
await saveBtn.click();
|
||||
console.log(' Saved');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Re-open to verify persistence
|
||||
console.log('8. Persistence check...');
|
||||
await page.locator('button[title="App Settings"]').click();
|
||||
await page.waitForTimeout(500);
|
||||
await page.locator('[data-tab="settings-display"]').click();
|
||||
await page.waitForTimeout(300);
|
||||
// Scroll down again
|
||||
await page.locator('#settings-display').evaluate(el => el.scrollTop = el.scrollHeight);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
const p = {
|
||||
enabled: await page.locator('#appSettingsTabFadeEnabled').isChecked(),
|
||||
inDur: await page.locator('#appSettingsTabFadeInDuration').inputValue(),
|
||||
inEase: await page.locator('#appSettingsTabFadeInEasing').inputValue(),
|
||||
outDur: await page.locator('#appSettingsTabFadeOutDuration').inputValue(),
|
||||
outEase: await page.locator('#appSettingsTabFadeOutEasing').inputValue(),
|
||||
};
|
||||
|
||||
const checks = [
|
||||
[!p.enabled, `enabled=${p.enabled} (expect false)`],
|
||||
[p.inDur === '500', `inDur=${p.inDur} (expect 500)`],
|
||||
[p.inEase === 'linear', `inEase=${p.inEase} (expect linear)`],
|
||||
[p.outDur === '200', `outDur=${p.outDur} (expect 200)`],
|
||||
[p.outEase === 'ease-out', `outEase=${p.outEase} (expect ease-out)`],
|
||||
];
|
||||
|
||||
let allPass = true;
|
||||
for (const [ok, msg] of checks) {
|
||||
console.log(` ${ok ? '✓' : '✗'} ${msg}`);
|
||||
if (!ok) allPass = false;
|
||||
}
|
||||
|
||||
console.log(`\n${allPass ? 'ALL TESTS PASSED ✓' : 'SOME TESTS FAILED ✗'}`);
|
||||
await browser.close();
|
||||
process.exit(allPass ? 0 : 1);
|
||||
}
|
||||
|
||||
main().catch(err => { console.error('CRASH:', err.message); process.exit(1); });
|
||||