refactor(agent): Use agent name instead of ID for log directory paths

Aligns agent-logs directory naming with agent-workdirs so both use the
human-readable agent name, making filesystem correlation trivial.
This commit is contained in:
Lukas May
2026-02-10 10:41:47 +01:00
parent bf898cb86e
commit f9f8b4c185
5 changed files with 27 additions and 21 deletions

View File

@@ -108,8 +108,8 @@ export class CleanupManager {
/**
* Remove log directory for an agent.
*/
async removeAgentLogs(agentId: string): Promise<void> {
const logDir = join(this.workspaceRoot, '.cw', 'agent-logs', agentId);
async removeAgentLogs(agentName: string): Promise<void> {
const logDir = join(this.workspaceRoot, '.cw', 'agent-logs', agentName);
await rm(logDir, { recursive: true, force: true });
}
@@ -191,10 +191,10 @@ export class CleanupManager {
}
const agents = await this.repository.findAll();
const knownIds = new Set(agents.map(a => a.id));
const knownNames = new Set(agents.map(a => a.name));
for (const entry of entries) {
if (!knownIds.has(entry)) {
if (!knownNames.has(entry)) {
log.info({ orphan: entry }, 'removing orphaned agent log dir');
try {
await rm(join(logsPath, entry), { recursive: true, force: true });
@@ -251,8 +251,8 @@ export class CleanupManager {
async archiveForDebug(alias: string, agentId: string): Promise<void> {
const agentWorkdir = this.getAgentWorkdir(alias);
const debugWorkdir = join(this.workspaceRoot, '.cw', 'debug', 'workdirs', alias);
const logDir = join(this.workspaceRoot, '.cw', 'agent-logs', agentId);
const debugLogDir = join(this.workspaceRoot, '.cw', 'debug', 'agent-logs', agentId);
const logDir = join(this.workspaceRoot, '.cw', 'agent-logs', alias);
const debugLogDir = join(this.workspaceRoot, '.cw', 'debug', 'agent-logs', alias);
try {
if (existsSync(agentWorkdir)) {
@@ -307,7 +307,7 @@ export class CleanupManager {
}
try {
await this.removeAgentLogs(agentId);
await this.removeAgentLogs(alias);
} catch (err) {
log.warn({ agentId, err: err instanceof Error ? err.message : String(err) }, 'auto-cleanup: failed to remove logs');
}

View File

@@ -300,7 +300,7 @@ export class MultiProviderAgentManager implements AgentManager {
// 6. Spawn detached subprocess
const { pid, outputFilePath, tailer } = this.processManager.spawnDetached(
agentId, command, args, cwd ?? agentCwd, processEnv, providerName, prompt,
agentId, alias, command, args, cwd ?? agentCwd, processEnv, providerName, prompt,
(event) => this.outputHandler.handleStreamEvent(agentId, event, this.activeAgents.get(agentId), this.outputBuffers),
);
@@ -451,7 +451,7 @@ export class MultiProviderAgentManager implements AgentManager {
}
const { pid, outputFilePath, tailer } = this.processManager.spawnDetached(
agentId, command, args, agentCwd, processEnv, provider.name, commitPrompt,
agentId, agent.name, command, args, agentCwd, processEnv, provider.name, commitPrompt,
(event) => this.outputHandler.handleStreamEvent(agentId, event, this.activeAgents.get(agentId), this.outputBuffers),
this.createLogChunkCallback(agentId, agent.name, commitSessionNumber),
);
@@ -624,7 +624,7 @@ export class MultiProviderAgentManager implements AgentManager {
}
const { pid, outputFilePath, tailer } = this.processManager.spawnDetached(
agentId, command, args, agentCwd, processEnv, provider.name, prompt,
agentId, agent.name, command, args, agentCwd, processEnv, provider.name, prompt,
(event) => this.outputHandler.handleStreamEvent(agentId, event, this.activeAgents.get(agentId), this.outputBuffers),
this.createLogChunkCallback(agentId, agent.name, resumeSessionNumber),
);
@@ -696,7 +696,7 @@ export class MultiProviderAgentManager implements AgentManager {
try { await this.cleanupManager.removeAgentBranches(agent.name, agent.initiativeId); }
catch (err) { log.warn({ agentId, err: err instanceof Error ? err.message : String(err) }, 'failed to remove branches'); }
try { await this.cleanupManager.removeAgentLogs(agentId); }
try { await this.cleanupManager.removeAgentLogs(agent.name); }
catch (err) { log.warn({ agentId, err: err instanceof Error ? err.message : String(err) }, 'failed to remove logs'); }
// 3b. Delete log chunks from DB

View File

@@ -220,13 +220,14 @@ describe('ProcessManager', () => {
it('validates cwd exists before spawn', () => {
const agentId = 'agent-123';
const agentName = 'test-agent';
const command = 'claude';
const args = ['--help'];
const cwd = '/test/workspace/agent-workdirs/test-agent';
const env = { TEST_VAR: 'value' };
const providerName = 'claude';
processManager.spawnDetached(agentId, command, args, cwd, env, providerName);
processManager.spawnDetached(agentId, agentName, command, args, cwd, env, providerName);
expect(mockExistsSync).toHaveBeenCalledWith(cwd);
expect(mockSpawn).toHaveBeenCalledWith(command, args, {
@@ -241,6 +242,7 @@ describe('ProcessManager', () => {
mockExistsSync.mockReturnValue(false);
const agentId = 'agent-123';
const agentName = 'test-agent';
const command = 'claude';
const args = ['--help'];
const cwd = '/nonexistent/path';
@@ -248,19 +250,20 @@ describe('ProcessManager', () => {
const providerName = 'claude';
expect(() => {
processManager.spawnDetached(agentId, command, args, cwd, env, providerName);
processManager.spawnDetached(agentId, agentName, command, args, cwd, env, providerName);
}).toThrow('Agent working directory does not exist: /nonexistent/path');
});
it('passes correct cwd parameter to spawn', () => {
const agentId = 'agent-123';
const agentName = 'test-agent';
const command = 'claude';
const args = ['--help'];
const cwd = '/test/workspace/agent-workdirs/test-agent';
const env = { CLAUDE_CONFIG_DIR: '/config' };
const providerName = 'claude';
processManager.spawnDetached(agentId, command, args, cwd, env, providerName);
processManager.spawnDetached(agentId, agentName, command, args, cwd, env, providerName);
expect(mockSpawn).toHaveBeenCalledTimes(1);
const spawnCall = mockSpawn.mock.calls[0];
@@ -279,27 +282,29 @@ describe('ProcessManager', () => {
it('logs comprehensive spawn information', () => {
const agentId = 'agent-123';
const agentName = 'test-agent';
const command = 'claude';
const args = ['--json-schema', 'schema.json'];
const cwd = '/test/workspace/agent-workdirs/test-agent';
const env = { CLAUDE_CONFIG_DIR: '/config' };
const providerName = 'claude';
const result = processManager.spawnDetached(agentId, command, args, cwd, env, providerName);
const result = processManager.spawnDetached(agentId, agentName, command, args, cwd, env, providerName);
expect(result).toHaveProperty('pid', 12345);
expect(result).toHaveProperty('outputFilePath');
expect(result).toHaveProperty('tailer');
// Verify log directory creation
// Verify log directory creation uses agent name, not ID
expect(mockMkdirSync).toHaveBeenCalledWith(
'/test/workspace/.cw/agent-logs/agent-123',
'/test/workspace/.cw/agent-logs/test-agent',
{ recursive: true }
);
});
it('writes prompt file when provided', () => {
const agentId = 'agent-123';
const agentName = 'test-agent';
const command = 'claude';
const args = ['--help'];
const cwd = '/test/workspace/agent-workdirs/test-agent';
@@ -307,10 +312,10 @@ describe('ProcessManager', () => {
const providerName = 'claude';
const prompt = 'Test prompt';
processManager.spawnDetached(agentId, command, args, cwd, env, providerName, prompt);
processManager.spawnDetached(agentId, agentName, command, args, cwd, env, providerName, prompt);
expect(mockWriteFileSync).toHaveBeenCalledWith(
'/test/workspace/.cw/agent-logs/agent-123/PROMPT.md',
'/test/workspace/.cw/agent-logs/test-agent/PROMPT.md',
'Test prompt',
'utf-8'
);

View File

@@ -236,6 +236,7 @@ export class ProcessManager {
*/
spawnDetached(
agentId: string,
agentName: string,
command: string,
args: string[],
cwd: string,
@@ -271,7 +272,7 @@ export class ProcessManager {
throw new Error(`Agent working directory does not exist: ${cwd}`);
}
const logDir = join(this.workspaceRoot, '.cw', 'agent-logs', agentId);
const logDir = join(this.workspaceRoot, '.cw', 'agent-logs', agentName);
mkdirSync(logDir, { recursive: true });
const outputFilePath = join(logDir, 'output.jsonl');
const stderrFilePath = join(logDir, 'stderr.log');

View File

@@ -201,7 +201,7 @@ export function agentProcedures(publicProcedure: ProcedureBuilder) {
return '';
}
const outputFilePath = join(workspaceRoot, '.cw', 'agent-logs', agent.id, 'output.jsonl');
const outputFilePath = join(workspaceRoot, '.cw', 'agent-logs', agent.name, 'output.jsonl');
try {
const content = await readFile(outputFilePath, 'utf-8');