mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-09 00:49:41 +02:00
chore: bump version to 0.1399
This commit is contained in:
@@ -38,7 +38,7 @@ Claudeman is a Claude Code session manager with a web interface and autonomous R
|
|||||||
|
|
||||||
When adding new features, always ask: "Will this maintain responsiveness with 20 sessions and 50 agent windows?"
|
When adding new features, always ask: "Will this maintain responsiveness with 20 sessions and 50 agent windows?"
|
||||||
|
|
||||||
**Version**: 0.1398 (must match `package.json`)
|
**Version**: 0.1399 (must match `package.json`)
|
||||||
|
|
||||||
**Tech Stack**: TypeScript (ES2022/NodeNext, strict mode), Node.js, Fastify, Server-Sent Events, node-pty
|
**Tech Stack**: TypeScript (ES2022/NodeNext, strict mode), Node.js, Fastify, Server-Sent Events, node-pty
|
||||||
|
|
||||||
@@ -610,6 +610,14 @@ Both wait for idle. Configure via `session.setAutoCompact()` / `session.setAutoC
|
|||||||
|
|
||||||
Detects Ralph loops and todos inside Claude sessions. **Disabled by default** but auto-enables when Ralph-related patterns are detected (promise tags, TodoWrite, iteration patterns, etc.). See `ralph-tracker.ts:shouldAutoEnable()` for the full pattern list.
|
Detects Ralph loops and todos inside Claude sessions. **Disabled by default** but auto-enables when Ralph-related patterns are detected (promise tags, TodoWrite, iteration patterns, etc.). See `ralph-tracker.ts:shouldAutoEnable()` for the full pattern list.
|
||||||
|
|
||||||
|
**Auto-Loading @fix_plan.md**: When a session starts, the Ralph tracker automatically:
|
||||||
|
1. Loads existing `@fix_plan.md` from the working directory (if present)
|
||||||
|
2. Imports todos and shows them in the Ralph panel
|
||||||
|
3. Watches the file for changes and reloads when modified by Claude
|
||||||
|
4. Auto-enables the tracker if todos are found
|
||||||
|
|
||||||
|
This means you can create a session in a folder with an existing `@fix_plan.md` and the tracker will immediately show and track those tasks.
|
||||||
|
|
||||||
**Auto-Configuration from Ralph Plugin State**: When a session starts, Claudeman reads `.claude/ralph-loop.local.md` to auto-configure:
|
**Auto-Configuration from Ralph Plugin State**: When a session starts, Claudeman reads `.claude/ralph-loop.local.md` to auto-configure:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "claudeman",
|
"name": "claudeman",
|
||||||
"version": "0.1398",
|
"version": "0.1399",
|
||||||
"description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence",
|
"description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
@@ -14,6 +14,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventEmitter } from 'node:events';
|
import { EventEmitter } from 'node:events';
|
||||||
|
import { readFile } from 'node:fs/promises';
|
||||||
|
import { existsSync, FSWatcher, watch as fsWatch } from 'node:fs';
|
||||||
|
import { join } from 'node:path';
|
||||||
import {
|
import {
|
||||||
RalphTrackerState,
|
RalphTrackerState,
|
||||||
RalphTodoItem,
|
RalphTodoItem,
|
||||||
@@ -429,6 +432,18 @@ export class RalphTracker extends EventEmitter {
|
|||||||
/** Cumulative tasks completed across all iterations */
|
/** Cumulative tasks completed across all iterations */
|
||||||
private _totalTasksCompleted: number = 0;
|
private _totalTasksCompleted: number = 0;
|
||||||
|
|
||||||
|
/** Working directory for @fix_plan.md watching */
|
||||||
|
private _workingDir: string | null = null;
|
||||||
|
|
||||||
|
/** File watcher for @fix_plan.md */
|
||||||
|
private _fixPlanWatcher: FSWatcher | null = null;
|
||||||
|
|
||||||
|
/** Debounce timer for file change events */
|
||||||
|
private _fixPlanReloadTimer: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
|
/** Path to the @fix_plan.md file being watched */
|
||||||
|
private _fixPlanPath: string | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new RalphTracker instance.
|
* Creates a new RalphTracker instance.
|
||||||
* Starts in disabled state until Ralph patterns are detected.
|
* Starts in disabled state until Ralph patterns are detected.
|
||||||
@@ -461,6 +476,112 @@ export class RalphTracker extends EventEmitter {
|
|||||||
return this._autoEnableDisabled;
|
return this._autoEnableDisabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the working directory and start watching @fix_plan.md.
|
||||||
|
* Automatically loads existing @fix_plan.md if present.
|
||||||
|
* @param workingDir - The session's working directory
|
||||||
|
*/
|
||||||
|
setWorkingDir(workingDir: string): void {
|
||||||
|
this._workingDir = workingDir;
|
||||||
|
this._fixPlanPath = join(workingDir, '@fix_plan.md');
|
||||||
|
|
||||||
|
// Try to load existing @fix_plan.md
|
||||||
|
this.loadFixPlanFromDisk();
|
||||||
|
|
||||||
|
// Start watching for changes
|
||||||
|
this.startWatchingFixPlan();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load @fix_plan.md from disk if it exists.
|
||||||
|
* Called on initialization and when file changes are detected.
|
||||||
|
*/
|
||||||
|
async loadFixPlanFromDisk(): Promise<number> {
|
||||||
|
if (!this._fixPlanPath) return 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!existsSync(this._fixPlanPath)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = await readFile(this._fixPlanPath, 'utf-8');
|
||||||
|
const count = this.importFixPlanMarkdown(content);
|
||||||
|
|
||||||
|
if (count > 0) {
|
||||||
|
// Auto-enable tracker when we have todos from @fix_plan.md
|
||||||
|
if (!this._loopState.enabled) {
|
||||||
|
this.enable();
|
||||||
|
}
|
||||||
|
console.log(`[RalphTracker] Loaded ${count} todos from @fix_plan.md`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
} catch (err) {
|
||||||
|
// File doesn't exist or can't be read - that's OK
|
||||||
|
console.log(`[RalphTracker] Could not load @fix_plan.md: ${err}`);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start watching @fix_plan.md for changes.
|
||||||
|
* Reloads todos when the file is modified.
|
||||||
|
*/
|
||||||
|
private startWatchingFixPlan(): void {
|
||||||
|
if (!this._fixPlanPath || !this._workingDir) return;
|
||||||
|
|
||||||
|
// Stop existing watcher if any
|
||||||
|
this.stopWatchingFixPlan();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Only watch if the file exists
|
||||||
|
if (!existsSync(this._fixPlanPath)) {
|
||||||
|
// Watch the directory instead for file creation
|
||||||
|
this._fixPlanWatcher = fsWatch(this._workingDir, (_eventType, filename) => {
|
||||||
|
if (filename === '@fix_plan.md') {
|
||||||
|
this.handleFixPlanChange();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Watch the file directly
|
||||||
|
this._fixPlanWatcher = fsWatch(this._fixPlanPath, () => {
|
||||||
|
this.handleFixPlanChange();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`[RalphTracker] Could not watch @fix_plan.md: ${err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle @fix_plan.md file change with debouncing.
|
||||||
|
*/
|
||||||
|
private handleFixPlanChange(): void {
|
||||||
|
// Debounce rapid changes (e.g., multiple writes)
|
||||||
|
if (this._fixPlanReloadTimer) {
|
||||||
|
clearTimeout(this._fixPlanReloadTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._fixPlanReloadTimer = setTimeout(() => {
|
||||||
|
this._fixPlanReloadTimer = null;
|
||||||
|
this.loadFixPlanFromDisk();
|
||||||
|
}, 500); // 500ms debounce
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop watching @fix_plan.md.
|
||||||
|
*/
|
||||||
|
stopWatchingFixPlan(): void {
|
||||||
|
if (this._fixPlanWatcher) {
|
||||||
|
this._fixPlanWatcher.close();
|
||||||
|
this._fixPlanWatcher = null;
|
||||||
|
}
|
||||||
|
if (this._fixPlanReloadTimer) {
|
||||||
|
clearTimeout(this._fixPlanReloadTimer);
|
||||||
|
this._fixPlanReloadTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the tracker is enabled and actively monitoring output.
|
* Whether the tracker is enabled and actively monitoring output.
|
||||||
* Disabled by default; auto-enables when Ralph patterns detected.
|
* Disabled by default; auto-enables when Ralph patterns detected.
|
||||||
|
|||||||
+38
-9
@@ -2662,6 +2662,9 @@ class ClaudemanApp {
|
|||||||
const plan = this.ralphWizardConfig.existingPlan;
|
const plan = this.ralphWizardConfig.existingPlan;
|
||||||
if (!plan) return;
|
if (!plan) return;
|
||||||
|
|
||||||
|
// Stop any ongoing plan generation
|
||||||
|
this.stopPlanGeneration();
|
||||||
|
|
||||||
// Convert existing todos to generatedPlan format (only pending items)
|
// Convert existing todos to generatedPlan format (only pending items)
|
||||||
const pendingTodos = plan.todos.filter(t => t.status === 'pending' || t.status === 'in_progress');
|
const pendingTodos = plan.todos.filter(t => t.status === 'pending' || t.status === 'in_progress');
|
||||||
this.ralphWizardConfig.generatedPlan = pendingTodos.map((todo, idx) => ({
|
this.ralphWizardConfig.generatedPlan = pendingTodos.map((todo, idx) => ({
|
||||||
@@ -2678,6 +2681,28 @@ class ClaudemanApp {
|
|||||||
this.updateDetailLevelButtons();
|
this.updateDetailLevelButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop any ongoing plan generation (abort fetch, clear timers, hide spinner)
|
||||||
|
stopPlanGeneration() {
|
||||||
|
// Abort ongoing fetch
|
||||||
|
if (this.planGenerationAbortController) {
|
||||||
|
this.planGenerationAbortController.abort();
|
||||||
|
this.planGenerationAbortController = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear timers
|
||||||
|
if (this.planLoadingTimer) {
|
||||||
|
clearInterval(this.planLoadingTimer);
|
||||||
|
this.planLoadingTimer = null;
|
||||||
|
}
|
||||||
|
if (this.planPhaseTimer) {
|
||||||
|
clearInterval(this.planPhaseTimer);
|
||||||
|
this.planPhaseTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide loading spinner
|
||||||
|
document.getElementById('planGenerationLoading')?.classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
// Generate a new plan (ignore existing)
|
// Generate a new plan (ignore existing)
|
||||||
generateNewPlan() {
|
generateNewPlan() {
|
||||||
this.ralphWizardConfig.useExistingPlan = false;
|
this.ralphWizardConfig.useExistingPlan = false;
|
||||||
@@ -2832,15 +2857,11 @@ class ClaudemanApp {
|
|||||||
async generatePlan() {
|
async generatePlan() {
|
||||||
const config = this.ralphWizardConfig;
|
const config = this.ralphWizardConfig;
|
||||||
|
|
||||||
// Stop any existing timers
|
// Stop any existing generation first
|
||||||
if (this.planLoadingTimer) {
|
this.stopPlanGeneration();
|
||||||
clearInterval(this.planLoadingTimer);
|
|
||||||
this.planLoadingTimer = null;
|
// Create abort controller for this generation
|
||||||
}
|
this.planGenerationAbortController = new AbortController();
|
||||||
if (this.planPhaseTimer) {
|
|
||||||
clearInterval(this.planPhaseTimer);
|
|
||||||
this.planPhaseTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show loading state, hide other sections
|
// Show loading state, hide other sections
|
||||||
document.getElementById('existingPlanSection')?.classList.add('hidden');
|
document.getElementById('existingPlanSection')?.classList.add('hidden');
|
||||||
@@ -2895,6 +2916,7 @@ class ClaudemanApp {
|
|||||||
taskDescription: config.taskDescription,
|
taskDescription: config.taskDescription,
|
||||||
detailLevel: config.planDetailLevel,
|
detailLevel: config.planDetailLevel,
|
||||||
}),
|
}),
|
||||||
|
signal: this.planGenerationAbortController?.signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
@@ -2946,6 +2968,13 @@ class ClaudemanApp {
|
|||||||
clearInterval(this.planLoadingTimer);
|
clearInterval(this.planLoadingTimer);
|
||||||
this.planLoadingTimer = null;
|
this.planLoadingTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ignore abort errors (user cancelled, e.g., clicked "Use Existing Plan")
|
||||||
|
if (err.name === 'AbortError') {
|
||||||
|
console.log('Plan generation aborted by user');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.error('Plan generation failed:', err);
|
console.error('Plan generation failed:', err);
|
||||||
this.showPlanError('Network error: ' + err.message);
|
this.showPlanError('Network error: ' + err.message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2917,6 +2917,11 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
|||||||
private async _doCleanupSession(sessionId: string, killScreen: boolean): Promise<void> {
|
private async _doCleanupSession(sessionId: string, killScreen: boolean): Promise<void> {
|
||||||
const session = this.sessions.get(sessionId);
|
const session = this.sessions.get(sessionId);
|
||||||
|
|
||||||
|
// Stop watching @fix_plan.md for this session
|
||||||
|
if (session) {
|
||||||
|
session.ralphTracker.stopWatchingFixPlan();
|
||||||
|
}
|
||||||
|
|
||||||
// Kill all subagents spawned by this session
|
// Kill all subagents spawned by this session
|
||||||
if (session && killScreen) {
|
if (session && killScreen) {
|
||||||
try {
|
try {
|
||||||
@@ -3032,6 +3037,9 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
|||||||
this.runSummaryTrackers.set(session.id, summaryTracker);
|
this.runSummaryTrackers.set(session.id, summaryTracker);
|
||||||
summaryTracker.recordSessionStarted(session.mode, session.workingDir);
|
summaryTracker.recordSessionStarted(session.mode, session.workingDir);
|
||||||
|
|
||||||
|
// Set working directory for Ralph tracker to auto-load @fix_plan.md
|
||||||
|
session.ralphTracker.setWorkingDir(session.workingDir);
|
||||||
|
|
||||||
session.on('output', (data) => {
|
session.on('output', (data) => {
|
||||||
// Use batching for better performance at high throughput
|
// Use batching for better performance at high throughput
|
||||||
this.batchOutputData(session.id, data);
|
this.batchOutputData(session.id, data);
|
||||||
|
|||||||
Reference in New Issue
Block a user