mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-06 23:49:41 +02:00
feat: add auto-clear, timed respawn, and enable on existing sessions
- Add token tracking to Session class (input/output tokens) - Add auto-clear feature that sends /clear when token threshold reached - Add API endpoint to enable respawn on existing running sessions - Add timed duration setting for respawn (auto-stops after N minutes) - Add TaskTracker class for background task detection and tree display - Update UI with duration input, auto-clear settings, and "Enable on Current" button - Add respawn timer display in banner when timed run is active - Add token count display in respawn banner Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+128
@@ -2,6 +2,9 @@ import { EventEmitter } from 'node:events';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import * as pty from 'node-pty';
|
||||
import { SessionState, SessionStatus, SessionConfig } from './types.js';
|
||||
import { TaskTracker, type BackgroundTask } from './task-tracker.js';
|
||||
|
||||
export type { BackgroundTask } from './task-tracker.js';
|
||||
|
||||
// Maximum terminal buffer size in characters (default 5MB of text)
|
||||
const MAX_TERMINAL_BUFFER_SIZE = 5 * 1024 * 1024;
|
||||
@@ -37,6 +40,13 @@ export interface SessionEvents {
|
||||
exit: (code: number | null) => void;
|
||||
completion: (result: string, cost: number) => void;
|
||||
terminal: (data: string) => void; // Raw terminal data
|
||||
// Background task events
|
||||
taskCreated: (task: BackgroundTask) => void;
|
||||
taskUpdated: (task: BackgroundTask) => void;
|
||||
taskCompleted: (task: BackgroundTask) => void;
|
||||
taskFailed: (task: BackgroundTask, error: string) => void;
|
||||
// Auto-clear event
|
||||
autoClear: (data: { tokens: number; threshold: number }) => void;
|
||||
}
|
||||
|
||||
export class Session extends EventEmitter {
|
||||
@@ -62,6 +72,14 @@ export class Session extends EventEmitter {
|
||||
private _isWorking: boolean = false;
|
||||
private _lastPromptTime: number = 0;
|
||||
private activityTimeout: NodeJS.Timeout | null = null;
|
||||
private _taskTracker: TaskTracker;
|
||||
|
||||
// Token tracking for auto-clear
|
||||
private _totalInputTokens: number = 0;
|
||||
private _totalOutputTokens: number = 0;
|
||||
private _autoClearThreshold: number = 100000; // Default 100k tokens
|
||||
private _autoClearEnabled: boolean = false;
|
||||
private _isClearing: boolean = false; // Prevent recursive clearing
|
||||
|
||||
constructor(config: Partial<SessionConfig> & { workingDir: string }) {
|
||||
super();
|
||||
@@ -69,6 +87,13 @@ export class Session extends EventEmitter {
|
||||
this.workingDir = config.workingDir;
|
||||
this.createdAt = config.createdAt || Date.now();
|
||||
this._lastActivityAt = this.createdAt;
|
||||
|
||||
// Initialize task tracker and forward events
|
||||
this._taskTracker = new TaskTracker();
|
||||
this._taskTracker.on('taskCreated', (task) => this.emit('taskCreated', task));
|
||||
this._taskTracker.on('taskUpdated', (task) => this.emit('taskUpdated', task));
|
||||
this._taskTracker.on('taskCompleted', (task) => this.emit('taskCompleted', task));
|
||||
this._taskTracker.on('taskFailed', (task, error) => this.emit('taskFailed', task, error));
|
||||
}
|
||||
|
||||
get status(): SessionStatus {
|
||||
@@ -123,6 +148,50 @@ export class Session extends EventEmitter {
|
||||
return this._lastPromptTime;
|
||||
}
|
||||
|
||||
get taskTracker(): TaskTracker {
|
||||
return this._taskTracker;
|
||||
}
|
||||
|
||||
get runningTaskCount(): number {
|
||||
return this._taskTracker.getRunningCount();
|
||||
}
|
||||
|
||||
get taskTree(): BackgroundTask[] {
|
||||
return this._taskTracker.getTaskTree();
|
||||
}
|
||||
|
||||
get taskStats(): { total: number; running: number; completed: number; failed: number } {
|
||||
return this._taskTracker.getStats();
|
||||
}
|
||||
|
||||
// Token tracking getters and setters
|
||||
get totalTokens(): number {
|
||||
return this._totalInputTokens + this._totalOutputTokens;
|
||||
}
|
||||
|
||||
get inputTokens(): number {
|
||||
return this._totalInputTokens;
|
||||
}
|
||||
|
||||
get outputTokens(): number {
|
||||
return this._totalOutputTokens;
|
||||
}
|
||||
|
||||
get autoClearThreshold(): number {
|
||||
return this._autoClearThreshold;
|
||||
}
|
||||
|
||||
get autoClearEnabled(): boolean {
|
||||
return this._autoClearEnabled;
|
||||
}
|
||||
|
||||
setAutoClear(enabled: boolean, threshold?: number): void {
|
||||
this._autoClearEnabled = enabled;
|
||||
if (threshold !== undefined) {
|
||||
this._autoClearThreshold = threshold;
|
||||
}
|
||||
}
|
||||
|
||||
isIdle(): boolean {
|
||||
return this._status === 'idle';
|
||||
}
|
||||
@@ -166,6 +235,19 @@ export class Session extends EventEmitter {
|
||||
maxTextOutput: MAX_TEXT_OUTPUT_SIZE,
|
||||
maxMessages: MAX_MESSAGES,
|
||||
},
|
||||
// Background task tracking
|
||||
taskStats: this._taskTracker.getStats(),
|
||||
taskTree: this._taskTracker.getTaskTree(),
|
||||
// Token tracking
|
||||
tokens: {
|
||||
input: this._totalInputTokens,
|
||||
output: this._totalOutputTokens,
|
||||
total: this._totalInputTokens + this._totalOutputTokens,
|
||||
},
|
||||
autoClear: {
|
||||
enabled: this._autoClearEnabled,
|
||||
threshold: this._autoClearThreshold,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -370,12 +452,23 @@ export class Session extends EventEmitter {
|
||||
this._claudeSessionId = msg.session_id;
|
||||
}
|
||||
|
||||
// Process message for task tracking
|
||||
this._taskTracker.processMessage(msg);
|
||||
|
||||
if (msg.type === 'assistant' && msg.message?.content) {
|
||||
for (const block of msg.message.content) {
|
||||
if (block.type === 'text' && block.text) {
|
||||
this._textOutput += block.text;
|
||||
}
|
||||
}
|
||||
// Track tokens from usage
|
||||
if (msg.message.usage) {
|
||||
this._totalInputTokens += msg.message.usage.input_tokens || 0;
|
||||
this._totalOutputTokens += msg.message.usage.output_tokens || 0;
|
||||
|
||||
// Check if we should auto-clear
|
||||
this.checkAutoClear();
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.type === 'result' && msg.total_cost_usd) {
|
||||
@@ -396,6 +489,40 @@ export class Session extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we should auto-clear based on token threshold
|
||||
private checkAutoClear(): void {
|
||||
if (!this._autoClearEnabled || this._isClearing) return;
|
||||
|
||||
const totalTokens = this._totalInputTokens + this._totalOutputTokens;
|
||||
if (totalTokens >= this._autoClearThreshold) {
|
||||
this._isClearing = true;
|
||||
console.log(`[Session] Auto-clear triggered: ${totalTokens} tokens >= ${this._autoClearThreshold} threshold`);
|
||||
|
||||
// Wait for Claude to be idle before clearing
|
||||
const checkAndClear = () => {
|
||||
if (!this._isWorking) {
|
||||
// Send /clear command
|
||||
this.write('/clear\n');
|
||||
// Reset token counts
|
||||
this._totalInputTokens = 0;
|
||||
this._totalOutputTokens = 0;
|
||||
this.emit('autoClear', { tokens: totalTokens, threshold: this._autoClearThreshold });
|
||||
|
||||
// Wait a moment then re-enable
|
||||
setTimeout(() => {
|
||||
this._isClearing = false;
|
||||
}, 5000);
|
||||
} else {
|
||||
// Check again in 2 seconds
|
||||
setTimeout(checkAndClear, 2000);
|
||||
}
|
||||
};
|
||||
|
||||
// Start checking after a short delay
|
||||
setTimeout(checkAndClear, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// Send input to the PTY (for interactive sessions)
|
||||
write(data: string): void {
|
||||
if (this.ptyProcess) {
|
||||
@@ -510,5 +637,6 @@ export class Session extends EventEmitter {
|
||||
this._textOutput = '';
|
||||
this._errorBuffer = '';
|
||||
this._messages = [];
|
||||
this._taskTracker.clear();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user