mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-03 14:09:42 +02:00
feat: implement Claudeman - Claude session manager with Ralph Loop
Claudeman manages multiple Claude CLI sessions as subprocesses with: - Session management (start/stop/list/logs) - Priority-based task queue with dependency support - Ralph Loop for autonomous task assignment and completion detection - Time-aware loops for extended work sessions - JSON file persistence to ~/.claudeman/state.json Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+186
@@ -0,0 +1,186 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { TaskDefinition, TaskState, TaskStatus } from './types.js';
|
||||
|
||||
export interface CreateTaskOptions {
|
||||
prompt: string;
|
||||
workingDir?: string;
|
||||
priority?: number;
|
||||
dependencies?: string[];
|
||||
completionPhrase?: string;
|
||||
timeoutMs?: number;
|
||||
}
|
||||
|
||||
export class Task {
|
||||
readonly id: string;
|
||||
readonly prompt: string;
|
||||
readonly workingDir: string;
|
||||
readonly priority: number;
|
||||
readonly dependencies: string[];
|
||||
readonly completionPhrase: string | undefined;
|
||||
readonly timeoutMs: number | undefined;
|
||||
readonly createdAt: number;
|
||||
|
||||
private _status: TaskStatus = 'pending';
|
||||
private _assignedSessionId: string | null = null;
|
||||
private _startedAt: number | null = null;
|
||||
private _completedAt: number | null = null;
|
||||
private _output: string = '';
|
||||
private _error: string | null = null;
|
||||
|
||||
constructor(options: CreateTaskOptions, id?: string) {
|
||||
this.id = id || uuidv4();
|
||||
this.prompt = options.prompt;
|
||||
this.workingDir = options.workingDir || process.cwd();
|
||||
this.priority = options.priority ?? 0;
|
||||
this.dependencies = options.dependencies || [];
|
||||
this.completionPhrase = options.completionPhrase;
|
||||
this.timeoutMs = options.timeoutMs;
|
||||
this.createdAt = Date.now();
|
||||
}
|
||||
|
||||
get status(): TaskStatus {
|
||||
return this._status;
|
||||
}
|
||||
|
||||
get assignedSessionId(): string | null {
|
||||
return this._assignedSessionId;
|
||||
}
|
||||
|
||||
get startedAt(): number | null {
|
||||
return this._startedAt;
|
||||
}
|
||||
|
||||
get completedAt(): number | null {
|
||||
return this._completedAt;
|
||||
}
|
||||
|
||||
get output(): string {
|
||||
return this._output;
|
||||
}
|
||||
|
||||
get error(): string | null {
|
||||
return this._error;
|
||||
}
|
||||
|
||||
isPending(): boolean {
|
||||
return this._status === 'pending';
|
||||
}
|
||||
|
||||
isRunning(): boolean {
|
||||
return this._status === 'running';
|
||||
}
|
||||
|
||||
isCompleted(): boolean {
|
||||
return this._status === 'completed';
|
||||
}
|
||||
|
||||
isFailed(): boolean {
|
||||
return this._status === 'failed';
|
||||
}
|
||||
|
||||
isDone(): boolean {
|
||||
return this._status === 'completed' || this._status === 'failed';
|
||||
}
|
||||
|
||||
toDefinition(): TaskDefinition {
|
||||
return {
|
||||
id: this.id,
|
||||
prompt: this.prompt,
|
||||
workingDir: this.workingDir,
|
||||
priority: this.priority,
|
||||
dependencies: this.dependencies,
|
||||
completionPhrase: this.completionPhrase,
|
||||
timeoutMs: this.timeoutMs,
|
||||
};
|
||||
}
|
||||
|
||||
toState(): TaskState {
|
||||
return {
|
||||
...this.toDefinition(),
|
||||
status: this._status,
|
||||
assignedSessionId: this._assignedSessionId,
|
||||
createdAt: this.createdAt,
|
||||
startedAt: this._startedAt,
|
||||
completedAt: this._completedAt,
|
||||
output: this._output,
|
||||
error: this._error,
|
||||
};
|
||||
}
|
||||
|
||||
static fromState(state: TaskState): Task {
|
||||
const task = new Task(
|
||||
{
|
||||
prompt: state.prompt,
|
||||
workingDir: state.workingDir,
|
||||
priority: state.priority,
|
||||
dependencies: state.dependencies,
|
||||
completionPhrase: state.completionPhrase,
|
||||
timeoutMs: state.timeoutMs,
|
||||
},
|
||||
state.id
|
||||
);
|
||||
task._status = state.status;
|
||||
task._assignedSessionId = state.assignedSessionId;
|
||||
task._startedAt = state.startedAt;
|
||||
task._completedAt = state.completedAt;
|
||||
task._output = state.output;
|
||||
task._error = state.error;
|
||||
return task;
|
||||
}
|
||||
|
||||
assign(sessionId: string): void {
|
||||
if (this._status !== 'pending') {
|
||||
throw new Error(`Cannot assign task ${this.id}: status is ${this._status}`);
|
||||
}
|
||||
this._status = 'running';
|
||||
this._assignedSessionId = sessionId;
|
||||
this._startedAt = Date.now();
|
||||
}
|
||||
|
||||
appendOutput(output: string): void {
|
||||
this._output += output;
|
||||
}
|
||||
|
||||
setError(error: string): void {
|
||||
this._error = error;
|
||||
}
|
||||
|
||||
complete(): void {
|
||||
this._status = 'completed';
|
||||
this._completedAt = Date.now();
|
||||
}
|
||||
|
||||
fail(error?: string): void {
|
||||
this._status = 'failed';
|
||||
this._completedAt = Date.now();
|
||||
if (error) {
|
||||
this._error = error;
|
||||
}
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this._status = 'pending';
|
||||
this._assignedSessionId = null;
|
||||
this._startedAt = null;
|
||||
this._completedAt = null;
|
||||
this._output = '';
|
||||
this._error = null;
|
||||
}
|
||||
|
||||
checkCompletion(output: string): boolean {
|
||||
if (this.completionPhrase) {
|
||||
// Check for exact completion phrase in promise tags
|
||||
const pattern = new RegExp(`<promise>${this.completionPhrase}</promise>`);
|
||||
return pattern.test(output);
|
||||
}
|
||||
// Default: check for any promise tag completion
|
||||
return /<promise>[^<]+<\/promise>/.test(output);
|
||||
}
|
||||
|
||||
isTimedOut(): boolean {
|
||||
if (!this.timeoutMs || !this._startedAt) {
|
||||
return false;
|
||||
}
|
||||
return Date.now() - this._startedAt > this.timeoutMs;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user