@stackmemoryai/stackmemory
Version:
Lossless, project-scoped memory for AI coding tools. Durable context across sessions with 56 MCP tools, FTS5 search, conductor orchestrator, loop/watch monitoring, snapshot capture, pre-flight overlap checks, Claude/Codex/OpenCode wrappers, Linear sync, a
413 lines (412 loc) • 12.8 kB
JavaScript
import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import { v4 as uuidv4 } from "uuid";
import { spawn } from "child_process";
import { logger } from "../../core/monitoring/logger.js";
class ClaudeCodeTaskCoordinator {
activeTasks = /* @__PURE__ */ new Map();
completedTasks = [];
metrics;
constructor() {
this.metrics = {
totalTasks: 0,
completedTasks: 0,
failedTasks: 0,
averageExecutionTime: 0,
totalCost: 0,
successRate: 0,
agentUtilization: {}
};
}
/**
* Execute task with Claude Code agent
*/
async executeTask(agentName, agentConfig, prompt, options = {}) {
const taskId = uuidv4();
const { maxRetries = 2, timeout = 3e5, priority = "medium" } = options;
const task = {
id: taskId,
agentName,
agentType: agentConfig.type,
prompt,
startTime: Date.now(),
status: "pending",
retryCount: 0,
estimatedCost: this.estimateTaskCost(prompt, agentConfig)
};
this.activeTasks.set(taskId, task);
this.metrics.totalTasks++;
logger.info("Starting Claude Code task execution", {
taskId,
agentName,
agentType: agentConfig.type,
promptLength: prompt.length,
estimatedCost: task.estimatedCost,
priority
});
try {
let lastError = null;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
task.retryCount = attempt;
task.status = "running";
const result = await this.executeWithTimeout(
() => this.invokeClaudeCodeAgent(agentName, prompt, agentConfig),
timeout
);
task.status = "completed";
task.result = result;
task.endTime = Date.now();
task.actualTokens = this.estimateTokenUsage(prompt, result);
this.completeTask(task);
return result;
} catch (error) {
lastError = error;
task.status = "failed";
logger.warn(`Claude Code task attempt ${attempt + 1} failed`, {
taskId,
agentName,
error: lastError.message,
attempt: attempt + 1,
maxRetries: maxRetries + 1
});
if (attempt === maxRetries) {
break;
}
const backoffMs = Math.min(1e3 * Math.pow(2, attempt), 1e4);
await new Promise((resolve) => setTimeout(resolve, backoffMs));
}
}
task.error = lastError?.message || "Unknown error";
task.endTime = Date.now();
this.failTask(task, lastError);
throw lastError;
} finally {
this.activeTasks.delete(taskId);
}
}
/**
* Execute multiple tasks in parallel with coordination
*/
async executeParallelTasks(tasks) {
logger.info("Executing parallel Claude Code tasks", {
taskCount: tasks.length,
agents: tasks.map((t) => t.agentName)
});
const priorityGroups = {
high: tasks.filter((t) => t.priority === "high"),
medium: tasks.filter((t) => t.priority === "medium"),
low: tasks.filter((t) => t.priority === "low")
};
const results = [];
const failures = [];
for (const priorityLevel of ["high", "medium", "low"]) {
const priorityTasks = priorityGroups[priorityLevel];
if (priorityTasks.length === 0) continue;
logger.info(`Executing ${priorityLevel} priority tasks`, {
count: priorityTasks.length
});
const promises = priorityTasks.map(async (task) => {
try {
const result = await this.executeTask(
task.agentName,
task.agentConfig,
task.prompt,
{ priority: task.priority }
);
return { success: true, result };
} catch (error) {
return { success: false, error };
}
});
const outcomes = await Promise.allSettled(promises);
for (const outcome of outcomes) {
if (outcome.status === "fulfilled") {
if (outcome.value.success) {
results.push(outcome.value.result);
} else {
failures.push(outcome.value.error);
}
} else {
failures.push(new Error(outcome.reason));
}
}
}
logger.info("Parallel task execution completed", {
totalTasks: tasks.length,
successful: results.length,
failed: failures.length,
successRate: (results.length / tasks.length * 100).toFixed(1)
});
return { results, failures };
}
/**
* Get coordination metrics and health status
*/
getCoordinationMetrics() {
const recentTasks = this.completedTasks.slice(-10);
const recentErrorRate = recentTasks.length > 0 ? recentTasks.filter((t) => t.status === "failed").length / recentTasks.length : 0;
const performanceTrend = recentErrorRate < 0.1 ? "improving" : recentErrorRate < 0.3 ? "stable" : "degrading";
const recentErrors = this.completedTasks.slice(-5).filter((t) => t.status === "failed").map((t) => t.error || "Unknown error");
return {
...this.metrics,
activeTasks: this.activeTasks.size,
recentErrors,
performanceTrend
};
}
/**
* Clean up resources and reset metrics
*/
async cleanup() {
logger.info("Cleaning up Claude Code Task Coordinator", {
activeTasks: this.activeTasks.size,
completedTasks: this.completedTasks.length
});
if (this.activeTasks.size > 0) {
let timer;
const timeoutPromise = new Promise((resolve) => {
timer = setTimeout(resolve, 3e4);
});
const completionPromise = this.waitForTaskCompletion();
try {
await Promise.race([completionPromise, timeoutPromise]);
} finally {
clearTimeout(timer);
}
if (this.activeTasks.size > 0) {
logger.warn("Force terminating active tasks", {
remainingTasks: this.activeTasks.size
});
}
}
this.activeTasks.clear();
this.completedTasks = [];
this.resetMetrics();
}
/**
* Execute with timeout wrapper
*/
async executeWithTimeout(fn, timeoutMs) {
let timer;
const timeoutPromise = new Promise((_, reject) => {
timer = setTimeout(() => {
reject(new Error(`Task execution timeout after ${timeoutMs}ms`));
}, timeoutMs);
});
try {
return await Promise.race([fn(), timeoutPromise]);
} finally {
clearTimeout(timer);
}
}
/**
* Invoke Claude Code agent (integration point)
*/
async invokeClaudeCodeAgent(agentName, prompt, agentConfig) {
logger.debug("Invoking Claude Code agent", {
agentName,
agentType: agentConfig.type,
promptTokens: this.estimateTokenUsage(prompt, "")
});
return this.spawnClaudeCode(agentName, prompt, agentConfig);
}
/**
* Spawn Claude Code CLI as a subprocess.
* Uses `claude --print` for non-interactive execution with stdout capture.
* Workspace cwd is inherited from the coordinator's process.
*/
spawnClaudeCode(agentName, prompt, agentConfig) {
return new Promise((resolve, reject) => {
const args = ["--print"];
if (agentConfig.type === "oracle") {
args.push("--model", "opus");
}
if (agentConfig.capabilities.includes("code_implementation")) {
args.push("--allowedTools", "Edit,Write,Bash,Read,Glob,Grep");
} else {
args.push("--allowedTools", "Read,Glob,Grep,Bash");
}
args.push(prompt);
logger.info("Spawning claude CLI", {
agentName,
agentType: agentConfig.type,
cwd: process.cwd(),
promptLength: prompt.length
});
const proc = spawn("claude", args, {
cwd: process.cwd(),
env: { ...process.env },
stdio: ["pipe", "pipe", "pipe"]
});
let stdout = "";
let stderr = "";
proc.stdout.on("data", (data) => {
stdout += data.toString();
});
proc.stderr.on("data", (data) => {
stderr += data.toString();
});
proc.on("error", (err) => {
logger.error("Failed to spawn claude CLI", {
agentName,
error: err.message
});
reject(new Error(`Failed to spawn claude: ${err.message}`));
});
proc.on("close", (code) => {
if (code === 0) {
logger.debug("Claude CLI completed", {
agentName,
outputLength: stdout.length
});
resolve(stdout.trim());
} else {
const errMsg = stderr.slice(0, 500) || `exit code ${code}`;
logger.warn("Claude CLI failed", {
agentName,
exitCode: code,
stderr: errMsg
});
reject(new Error(`Claude CLI exited with code ${code}: ${errMsg}`));
}
});
});
}
/**
* Complete a successful task
*/
completeTask(task) {
this.completedTasks.push({ ...task });
this.metrics.completedTasks++;
this.updateExecutionMetrics(task);
this.updateAgentUtilization(task.agentName);
this.updateSuccessRate();
logger.info("Claude Code task completed", {
taskId: task.id,
agentName: task.agentName,
executionTime: task.endTime - task.startTime,
retries: task.retryCount,
cost: this.calculateActualCost(task)
});
}
/**
* Handle a failed task
*/
failTask(task, error) {
this.completedTasks.push({ ...task });
this.metrics.failedTasks++;
this.updateExecutionMetrics(task);
this.updateSuccessRate();
logger.error("Claude Code task failed", {
taskId: task.id,
agentName: task.agentName,
error: error.message,
retries: task.retryCount,
executionTime: task.endTime - task.startTime
});
}
/**
* Update execution time metrics
*/
updateExecutionMetrics(task) {
if (!task.endTime) return;
const executionTime = task.endTime - task.startTime;
const totalTasks = this.metrics.completedTasks + this.metrics.failedTasks;
if (totalTasks === 1) {
this.metrics.averageExecutionTime = executionTime;
} else {
this.metrics.averageExecutionTime = (this.metrics.averageExecutionTime * (totalTasks - 1) + executionTime) / totalTasks;
}
this.metrics.totalCost += this.calculateActualCost(task);
}
/**
* Update agent utilization metrics
*/
updateAgentUtilization(agentName) {
this.metrics.agentUtilization[agentName] = (this.metrics.agentUtilization[agentName] || 0) + 1;
}
/**
* Update success rate
*/
updateSuccessRate() {
const total = this.metrics.completedTasks + this.metrics.failedTasks;
this.metrics.successRate = total > 0 ? this.metrics.completedTasks / total : 0;
}
/**
* Estimate task cost based on prompt and agent
*/
estimateTaskCost(prompt, agentConfig) {
const estimatedTokens = this.estimateTokenUsage(prompt, "");
const baseCost = agentConfig.type === "oracle" ? 0.015 : 25e-5;
return estimatedTokens / 1e3 * baseCost * agentConfig.costMultiplier;
}
/**
* Estimate token usage
*/
estimateTokenUsage(prompt, response) {
return Math.ceil((prompt.length + response.length) / 4);
}
/**
* Calculate actual task cost
*/
calculateActualCost(task) {
if (!task.actualTokens) return task.estimatedCost;
const baseCost = task.agentType === "oracle" ? 0.015 : 25e-5;
return task.actualTokens / 1e3 * baseCost;
}
/**
* Wait for all active tasks to complete
*/
async waitForTaskCompletion() {
while (this.activeTasks.size > 0) {
await new Promise((resolve) => setTimeout(resolve, 1e3));
}
}
/**
* Reset metrics
*/
resetMetrics() {
this.metrics = {
totalTasks: 0,
completedTasks: 0,
failedTasks: 0,
averageExecutionTime: 0,
totalCost: 0,
successRate: 0,
agentUtilization: {}
};
}
/**
* Get active task status
*/
getActiveTaskStatus() {
return Array.from(this.activeTasks.values()).map((task) => ({
taskId: task.id,
agentName: task.agentName,
status: task.status,
runtime: Date.now() - task.startTime
}));
}
/**
* Cancel active task
*/
async cancelTask(taskId) {
const task = this.activeTasks.get(taskId);
if (!task) return false;
task.status = "failed";
task.error = "Task cancelled by user";
task.endTime = Date.now();
this.failTask(task, new Error("Task cancelled"));
this.activeTasks.delete(taskId);
logger.info("Task cancelled", { taskId, agentName: task.agentName });
return true;
}
}
var task_coordinator_default = ClaudeCodeTaskCoordinator;
export {
ClaudeCodeTaskCoordinator,
task_coordinator_default as default
};