mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 20:49:41 +02:00
chore: bump version to 0.1549
This commit is contained in:
@@ -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.1548 (must match `package.json` for npm publish)
|
||||
**Version**: 0.1549 (must match `package.json` for npm publish)
|
||||
|
||||
## Project Overview
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claudeman",
|
||||
"version": "0.1548",
|
||||
"version": "0.1549",
|
||||
"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",
|
||||
|
||||
@@ -70,6 +70,7 @@ export interface TerminalMultiplexer extends EventEmitter {
|
||||
mode: 'claude' | 'shell',
|
||||
name?: string,
|
||||
niceConfig?: NiceConfig,
|
||||
model?: string,
|
||||
): Promise<MuxSession>;
|
||||
|
||||
/**
|
||||
|
||||
+25
-16
@@ -150,7 +150,7 @@ function tryParseJSON(jsonString: string): { success: boolean; data?: unknown; e
|
||||
// Constants
|
||||
// ============================================================================
|
||||
|
||||
const MODEL = 'opus';
|
||||
const DEFAULT_MODEL = 'opus';
|
||||
|
||||
// ============================================================================
|
||||
// Main Orchestrator Class
|
||||
@@ -163,11 +163,20 @@ export class PlanOrchestrator {
|
||||
private runningSessions: Set<Session> = new Set();
|
||||
private cancelled = false;
|
||||
private taskDescription = '';
|
||||
private researchModel: string;
|
||||
private plannerModel: string;
|
||||
|
||||
constructor(mux: TerminalMultiplexer, workingDir: string = process.cwd(), outputDir?: string) {
|
||||
constructor(
|
||||
mux: TerminalMultiplexer,
|
||||
workingDir: string = process.cwd(),
|
||||
outputDir?: string,
|
||||
modelConfig?: { defaultModel?: string; agentTypeOverrides?: Record<string, string> },
|
||||
) {
|
||||
this.mux = mux;
|
||||
this.workingDir = workingDir;
|
||||
this.outputDir = outputDir;
|
||||
this.researchModel = modelConfig?.agentTypeOverrides?.explore || modelConfig?.defaultModel || DEFAULT_MODEL;
|
||||
this.plannerModel = modelConfig?.agentTypeOverrides?.review || modelConfig?.defaultModel || DEFAULT_MODEL;
|
||||
}
|
||||
|
||||
private saveAgentOutput(agentType: string, prompt: string, result: unknown, durationMs: number): void {
|
||||
@@ -386,7 +395,7 @@ export class PlanOrchestrator {
|
||||
return { success: false, findings: { externalResources: [], codebasePatterns: [], technicalRecommendations: [], potentialChallenges: [], recommendedTools: [] }, enrichedTaskDescription: taskDescription, error: 'Cancelled', durationMs: 0 };
|
||||
}
|
||||
|
||||
onSubagent?.({ type: 'started', agentId, agentType: 'research', model: MODEL, status: 'running', detail: 'Researching...' });
|
||||
onSubagent?.({ type: 'started', agentId, agentType: 'research', model: this.researchModel, status: 'running', detail: 'Researching...' });
|
||||
|
||||
const session = new Session({
|
||||
workingDir: this.workingDir,
|
||||
@@ -402,11 +411,11 @@ export class PlanOrchestrator {
|
||||
// Start progress interval before try block to ensure cleanup in finally
|
||||
const progressInterval = setInterval(() => {
|
||||
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
||||
onSubagent?.({ type: 'progress', agentId, agentType: 'research', model: MODEL, status: 'running', detail: `${elapsed}s elapsed` });
|
||||
onSubagent?.({ type: 'progress', agentId, agentType: 'research', model: this.researchModel, status: 'running', detail: `${elapsed}s elapsed` });
|
||||
}, 30000);
|
||||
|
||||
try {
|
||||
const { result: response } = await session.runPrompt(prompt, { model: MODEL });
|
||||
const { result: response } = await session.runPrompt(prompt, { model: this.researchModel });
|
||||
|
||||
this.runningSessions.delete(session);
|
||||
|
||||
@@ -415,13 +424,13 @@ export class PlanOrchestrator {
|
||||
// Extract JSON from response
|
||||
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
||||
if (!jsonMatch) {
|
||||
onSubagent?.({ type: 'failed', agentId, agentType: 'research', model: MODEL, status: 'failed', error: 'No JSON found', durationMs });
|
||||
onSubagent?.({ type: 'failed', agentId, agentType: 'research', model: this.researchModel, status: 'failed', error: 'No JSON found', durationMs });
|
||||
return { success: false, findings: { externalResources: [], codebasePatterns: [], technicalRecommendations: [], potentialChallenges: [], recommendedTools: [] }, enrichedTaskDescription: taskDescription, error: 'No JSON in response', durationMs };
|
||||
}
|
||||
|
||||
const parsed = tryParseJSON(jsonMatch[0]);
|
||||
if (!parsed.success) {
|
||||
onSubagent?.({ type: 'failed', agentId, agentType: 'research', model: MODEL, status: 'failed', error: parsed.error, durationMs });
|
||||
onSubagent?.({ type: 'failed', agentId, agentType: 'research', model: this.researchModel, status: 'failed', error: parsed.error, durationMs });
|
||||
return { success: false, findings: { externalResources: [], codebasePatterns: [], technicalRecommendations: [], potentialChallenges: [], recommendedTools: [] }, enrichedTaskDescription: taskDescription, error: parsed.error, durationMs };
|
||||
}
|
||||
|
||||
@@ -440,14 +449,14 @@ export class PlanOrchestrator {
|
||||
};
|
||||
|
||||
this.saveAgentOutput('research', prompt, result, durationMs);
|
||||
onSubagent?.({ type: 'completed', agentId, agentType: 'research', model: MODEL, status: 'completed', durationMs });
|
||||
onSubagent?.({ type: 'completed', agentId, agentType: 'research', model: this.researchModel, status: 'completed', durationMs });
|
||||
|
||||
return result;
|
||||
} catch (err) {
|
||||
this.runningSessions.delete(session);
|
||||
const durationMs = Date.now() - startTime;
|
||||
const error = err instanceof Error ? err.message : String(err);
|
||||
onSubagent?.({ type: 'failed', agentId, agentType: 'research', model: MODEL, status: 'failed', error, durationMs });
|
||||
onSubagent?.({ type: 'failed', agentId, agentType: 'research', model: this.researchModel, status: 'failed', error, durationMs });
|
||||
return { success: false, findings: { externalResources: [], codebasePatterns: [], technicalRecommendations: [], potentialChallenges: [], recommendedTools: [] }, enrichedTaskDescription: taskDescription, error, durationMs };
|
||||
} finally {
|
||||
// Always clear the progress interval to prevent memory leaks
|
||||
@@ -468,7 +477,7 @@ export class PlanOrchestrator {
|
||||
return { success: false, error: 'Cancelled' };
|
||||
}
|
||||
|
||||
onSubagent?.({ type: 'started', agentId, agentType: 'planner', model: MODEL, status: 'running', detail: 'Generating plan...' });
|
||||
onSubagent?.({ type: 'started', agentId, agentType: 'planner', model: this.plannerModel, status: 'running', detail: 'Generating plan...' });
|
||||
|
||||
const session = new Session({
|
||||
workingDir: this.workingDir,
|
||||
@@ -486,11 +495,11 @@ export class PlanOrchestrator {
|
||||
// Start progress interval before try block to ensure cleanup in finally
|
||||
const progressInterval = setInterval(() => {
|
||||
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
||||
onSubagent?.({ type: 'progress', agentId, agentType: 'planner', model: MODEL, status: 'running', detail: `${elapsed}s elapsed` });
|
||||
onSubagent?.({ type: 'progress', agentId, agentType: 'planner', model: this.plannerModel, status: 'running', detail: `${elapsed}s elapsed` });
|
||||
}, 30000);
|
||||
|
||||
try {
|
||||
const { result: response } = await session.runPrompt(prompt, { model: MODEL });
|
||||
const { result: response } = await session.runPrompt(prompt, { model: this.plannerModel });
|
||||
|
||||
this.runningSessions.delete(session);
|
||||
|
||||
@@ -499,13 +508,13 @@ export class PlanOrchestrator {
|
||||
// Extract JSON from response
|
||||
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
||||
if (!jsonMatch) {
|
||||
onSubagent?.({ type: 'failed', agentId, agentType: 'planner', model: MODEL, status: 'failed', error: 'No JSON found', durationMs });
|
||||
onSubagent?.({ type: 'failed', agentId, agentType: 'planner', model: this.plannerModel, status: 'failed', error: 'No JSON found', durationMs });
|
||||
return { success: false, error: 'No JSON in response' };
|
||||
}
|
||||
|
||||
const parsed = tryParseJSON(jsonMatch[0]);
|
||||
if (!parsed.success) {
|
||||
onSubagent?.({ type: 'failed', agentId, agentType: 'planner', model: MODEL, status: 'failed', error: parsed.error, durationMs });
|
||||
onSubagent?.({ type: 'failed', agentId, agentType: 'planner', model: this.plannerModel, status: 'failed', error: parsed.error, durationMs });
|
||||
return { success: false, error: parsed.error };
|
||||
}
|
||||
|
||||
@@ -515,7 +524,7 @@ export class PlanOrchestrator {
|
||||
const warnings: string[] = Array.isArray(data.warnings) ? data.warnings : [];
|
||||
|
||||
this.saveAgentOutput('planner', prompt, { items, gaps, warnings }, durationMs);
|
||||
onSubagent?.({ type: 'completed', agentId, agentType: 'planner', model: MODEL, status: 'completed', itemCount: items.length, durationMs });
|
||||
onSubagent?.({ type: 'completed', agentId, agentType: 'planner', model: this.plannerModel, status: 'completed', itemCount: items.length, durationMs });
|
||||
|
||||
onProgress?.('planning', `Generated ${items.length} tasks`);
|
||||
|
||||
@@ -524,7 +533,7 @@ export class PlanOrchestrator {
|
||||
this.runningSessions.delete(session);
|
||||
const durationMs = Date.now() - startTime;
|
||||
const error = err instanceof Error ? err.message : String(err);
|
||||
onSubagent?.({ type: 'failed', agentId, agentType: 'planner', model: MODEL, status: 'failed', error, durationMs });
|
||||
onSubagent?.({ type: 'failed', agentId, agentType: 'planner', model: this.plannerModel, status: 'failed', error, durationMs });
|
||||
return { success: false, error };
|
||||
} finally {
|
||||
// Always clear the progress interval to prevent memory leaks
|
||||
|
||||
+14
-5
@@ -317,6 +317,9 @@ export class Session extends EventEmitter {
|
||||
// Nice prioritying configuration
|
||||
private _niceConfig: NiceConfig = { ...DEFAULT_NICE_CONFIG };
|
||||
|
||||
// Claude model override (e.g., 'opus', 'sonnet', 'haiku')
|
||||
private _model: string | undefined;
|
||||
|
||||
// Session color for visual differentiation
|
||||
private _color: import('./types.js').SessionColor = 'default';
|
||||
|
||||
@@ -361,6 +364,8 @@ export class Session extends EventEmitter {
|
||||
/** Existing mux session for restored sessions */
|
||||
muxSession?: MuxSession;
|
||||
niceConfig?: NiceConfig; // Nice prioritying configuration
|
||||
/** Claude model override (e.g., 'opus', 'sonnet', 'haiku') */
|
||||
model?: string;
|
||||
}) {
|
||||
super();
|
||||
this.setMaxListeners(25);
|
||||
@@ -391,6 +396,11 @@ export class Session extends EventEmitter {
|
||||
this._niceConfig = { ...config.niceConfig };
|
||||
}
|
||||
|
||||
// Apply model override if provided
|
||||
if (config.model) {
|
||||
this._model = config.model;
|
||||
}
|
||||
|
||||
// Initialize task tracker and forward events (store handlers for cleanup)
|
||||
this._taskTracker = new TaskTracker();
|
||||
this._taskTrackerHandlers = {
|
||||
@@ -845,7 +855,7 @@ export class Session extends EventEmitter {
|
||||
console.log('[Session] Attaching to existing mux session:', this._muxSession!.muxName);
|
||||
} else {
|
||||
// Create a new mux session
|
||||
this._muxSession = await this._mux.createSession(this.id, this.workingDir, 'claude', this._name, this._niceConfig);
|
||||
this._muxSession = await this._mux.createSession(this.id, this.workingDir, 'claude', this._name, this._niceConfig, this._model);
|
||||
console.log('[Session] Created mux session:', this._muxSession.muxName);
|
||||
|
||||
// Wait a moment for mux to fully start
|
||||
@@ -920,10 +930,9 @@ export class Session extends EventEmitter {
|
||||
try {
|
||||
// Pass --session-id to use the SAME ID as the Claudeman session
|
||||
// This ensures subagents can be directly matched to the correct tab
|
||||
this.ptyProcess = pty.spawn('claude', [
|
||||
'--dangerously-skip-permissions',
|
||||
'--session-id', this.id
|
||||
], {
|
||||
const args = ['--dangerously-skip-permissions', '--session-id', this.id];
|
||||
if (this._model) args.push('--model', this._model);
|
||||
this.ptyProcess = pty.spawn('claude', args, {
|
||||
name: 'xterm-256color',
|
||||
cols: 120,
|
||||
rows: 40,
|
||||
|
||||
+5
-1
@@ -201,6 +201,7 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
|
||||
mode: 'claude' | 'shell',
|
||||
name?: string,
|
||||
niceConfig?: NiceConfig,
|
||||
model?: string,
|
||||
): Promise<MuxSession> {
|
||||
const muxName = `claudeman-${sessionId.slice(0, 8)}`;
|
||||
|
||||
@@ -240,8 +241,11 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
|
||||
`export CLAUDEMAN_API_URL=${process.env.CLAUDEMAN_API_URL || 'http://localhost:3000'}`,
|
||||
].join(' && ');
|
||||
|
||||
// Validate model to prevent command injection (only allow alphanumeric, hyphens, dots)
|
||||
const safeModel = (model && /^[a-zA-Z0-9._-]+$/.test(model)) ? model : undefined;
|
||||
const modelFlag = (mode === 'claude' && safeModel) ? ` --model ${safeModel}` : '';
|
||||
const baseCmd = mode === 'claude'
|
||||
? `claude --dangerously-skip-permissions --session-id "${sessionId}"`
|
||||
? `claude --dangerously-skip-permissions --session-id "${sessionId}"${modelFlag}`
|
||||
: '$SHELL';
|
||||
|
||||
const config = niceConfig || DEFAULT_NICE_CONFIG;
|
||||
|
||||
+24
-4
@@ -785,6 +785,8 @@ export class WebServer extends EventEmitter {
|
||||
}
|
||||
|
||||
const globalNice = await this.getGlobalNiceConfig();
|
||||
const modelConfig = await this.getModelConfig();
|
||||
const model = (body.mode !== 'shell') ? modelConfig?.defaultModel : undefined;
|
||||
const session = new Session({
|
||||
workingDir,
|
||||
mode: body.mode || 'claude',
|
||||
@@ -792,6 +794,7 @@ export class WebServer extends EventEmitter {
|
||||
mux: this.mux,
|
||||
useMux: true,
|
||||
niceConfig: globalNice,
|
||||
model,
|
||||
});
|
||||
|
||||
this.sessions.set(session.id, session);
|
||||
@@ -2569,14 +2572,17 @@ export class WebServer extends EventEmitter {
|
||||
}
|
||||
|
||||
// Create a new session with the case as working directory
|
||||
// Apply global Nice priority config if enabled in settings
|
||||
// Apply global Nice priority config and model config from settings
|
||||
const niceConfig = await this.getGlobalNiceConfig();
|
||||
const qsModelConfig = await this.getModelConfig();
|
||||
const qsModel = (mode !== 'shell') ? qsModelConfig?.defaultModel : undefined;
|
||||
const session = new Session({
|
||||
workingDir: casePath,
|
||||
mux: this.mux,
|
||||
useMux: true,
|
||||
mode: mode,
|
||||
niceConfig: niceConfig,
|
||||
model: qsModel,
|
||||
});
|
||||
|
||||
// Auto-detect completion phrase from CLAUDE.md BEFORE broadcasting
|
||||
@@ -2750,8 +2756,9 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
mode: 'claude',
|
||||
});
|
||||
|
||||
// Use Opus 4.5 for plan generation (better reasoning)
|
||||
const modelToUse = 'opus';
|
||||
// Use configured model for plan generation, falling back to opus
|
||||
const planModelConfig = await this.getModelConfig();
|
||||
const modelToUse = planModelConfig?.agentTypeOverrides?.implement || planModelConfig?.defaultModel || 'opus';
|
||||
|
||||
try {
|
||||
const { result, cost } = await session.runPrompt(prompt, { model: modelToUse });
|
||||
@@ -2866,7 +2873,8 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
}
|
||||
}
|
||||
|
||||
const orchestrator = new PlanOrchestrator(this.mux, process.cwd(), outputDir);
|
||||
const detailedModelConfig = await this.getModelConfig();
|
||||
const orchestrator = new PlanOrchestrator(this.mux, process.cwd(), outputDir, detailedModelConfig ?? undefined);
|
||||
|
||||
// Store orchestrator for potential cancellation via API (not on disconnect)
|
||||
// Plan generation continues even if browser disconnects - only explicit cancel stops it
|
||||
@@ -4566,6 +4574,18 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Helper to get model configuration from settings
|
||||
private async getModelConfig(): Promise<{ defaultModel?: string; agentTypeOverrides?: Record<string, string> } | null> {
|
||||
const settingsPath = join(homedir(), '.claudeman', 'settings.json');
|
||||
|
||||
try {
|
||||
const content = await fs.readFile(settingsPath, 'utf-8');
|
||||
return JSON.parse(content).modelConfig || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async startScheduledRun(prompt: string, workingDir: string, durationMinutes: number): Promise<ScheduledRun> {
|
||||
const id = uuidv4();
|
||||
const now = Date.now();
|
||||
|
||||
Reference in New Issue
Block a user