feat: Add agent preview integration with auto-teardown and simplified commands

- Add agentId label to preview containers (cw.agent-id) for tracking
- Add startForAgent/stopByAgentId methods to PreviewManager
- Auto-teardown: previews torn down on agent:stopped event
- Conditional preview prompt injection for execute/refine/discuss agents
- Agent-simplified CLI: cw preview start/stop --agent <id>
- cw preview setup command with --auto mode for guided config generation
- hasPreviewConfig hint on cw project register output
- New tRPC procedures: startPreviewForAgent, stopPreviewByAgent
This commit is contained in:
Lukas May
2026-03-05 15:39:15 +01:00
parent 66605da30d
commit ebe186bd5e
10 changed files with 381 additions and 22 deletions

View File

@@ -37,9 +37,10 @@ import type {
ProcessCrashedEvent,
} from '../events/index.js';
import { writeInputFiles } from './file-io.js';
import { buildWorkspaceLayout, buildInterAgentCommunication } from './prompts/index.js';
import { buildWorkspaceLayout, buildInterAgentCommunication, buildPreviewInstructions } from './prompts/index.js';
import { getProvider } from './providers/registry.js';
import { createModuleLogger } from '../logger/index.js';
import { getProjectCloneDir } from '../git/project-clones.js';
import { join } from 'node:path';
import { unlink, readFile, writeFile as writeFileAsync } from 'node:fs/promises';
import { existsSync } from 'node:fs';
@@ -282,7 +283,15 @@ export class MultiProviderAgentManager implements AgentManager {
// 3a. Append inter-agent communication instructions with actual agent ID
prompt = prompt + buildInterAgentCommunication(agentId, mode);
// 3b. Write input files (after agent creation so we can include agentId/agentName)
// 3b. Append preview deployment instructions if applicable
if (['execute', 'refine', 'discuss'].includes(mode) && initiativeId) {
const shouldInject = await this.shouldInjectPreviewInstructions(initiativeId);
if (shouldInject) {
prompt = prompt + buildPreviewInstructions(agentId);
}
}
// 3c. Write input files (after agent creation so we can include agentId/agentName)
if (options.inputContext) {
await writeInputFiles({ agentWorkdir: agentCwd, ...options.inputContext, agentId, agentName: alias });
log.debug({ alias }, 'input files written');
@@ -1038,6 +1047,23 @@ export class MultiProviderAgentManager implements AgentManager {
}
}
/**
* Check whether preview instructions should be injected for this initiative.
* Returns true if exactly one project linked and it has .cw-preview.yml.
*/
private async shouldInjectPreviewInstructions(initiativeId: string): Promise<boolean> {
try {
const projects = await this.projectRepository.findProjectsByInitiativeId(initiativeId);
if (projects.length !== 1) return false;
const project = projects[0];
const cloneDir = join(this.workspaceRoot, getProjectCloneDir(project.name, project.id));
return existsSync(join(cloneDir, '.cw-preview.yml'));
} catch {
return false;
}
}
/**
* Convert database agent record to AgentInfo.
*/