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:
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -14,3 +14,4 @@ export { buildRefinePrompt } from './refine.js';
|
||||
export { buildChatPrompt } from './chat.js';
|
||||
export type { ChatHistoryEntry } from './chat.js';
|
||||
export { buildWorkspaceLayout } from './workspace.js';
|
||||
export { buildPreviewInstructions } from './preview.js';
|
||||
|
||||
37
apps/server/agent/prompts/preview.ts
Normal file
37
apps/server/agent/prompts/preview.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Preview Deployment Prompt Instructions
|
||||
*
|
||||
* Conditional prompt section injected when the agent's initiative has
|
||||
* a project with `.cw-preview.yml`. Provides prefilled commands using
|
||||
* the agent's own ID so the server can resolve everything else.
|
||||
*/
|
||||
|
||||
export function buildPreviewInstructions(agentId: string): string {
|
||||
return `
|
||||
|
||||
<preview_deployments>
|
||||
This project supports preview deployments via Docker. You can spin up a running
|
||||
instance to verify your changes visually or explore the app.
|
||||
|
||||
## Start a Preview
|
||||
cw preview start --agent ${agentId}
|
||||
|
||||
Automatically uses your worktree (dev mode with hot reload). The URL is printed
|
||||
to stdout (e.g. http://abc123.localhost:9100).
|
||||
|
||||
## Check Preview Status
|
||||
cw preview list
|
||||
|
||||
## Stop a Preview
|
||||
cw preview stop --agent ${agentId}
|
||||
|
||||
## When to Use
|
||||
- After implementing a UI change, spin up a preview to verify rendering.
|
||||
- After API changes, verify the frontend still works end-to-end.
|
||||
- To explore the existing app state via Chrome DevTools or browser.
|
||||
|
||||
## When NOT to Use
|
||||
- Backend-only changes with no visual component — run tests instead.
|
||||
- If Docker is not available (the command will fail gracefully).
|
||||
</preview_deployments>`;
|
||||
}
|
||||
@@ -1038,6 +1038,11 @@ export function createCli(serverHandler?: (port?: number) => Promise<void>): Com
|
||||
console.log(`Registered project: ${project.id}`);
|
||||
console.log(` Name: ${project.name}`);
|
||||
console.log(` URL: ${project.url}`);
|
||||
if (project.hasPreviewConfig) {
|
||||
console.log(' Preview: .cw-preview.yml detected — preview deployments ready');
|
||||
} else {
|
||||
console.log(' Preview: No .cw-preview.yml found. Run `cw preview setup` for instructions.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to register project:', (error as Error).message);
|
||||
process.exit(1);
|
||||
@@ -1335,23 +1340,36 @@ export function createCli(serverHandler?: (port?: number) => Promise<void>): Com
|
||||
.description('Manage Docker-based preview deployments');
|
||||
|
||||
// cw preview start --initiative <id> --project <id> --branch <branch> [--phase <id>]
|
||||
// cw preview start --agent <id> (agent-simplified: server resolves everything)
|
||||
previewCommand
|
||||
.command('start')
|
||||
.description('Start a preview deployment')
|
||||
.requiredOption('--initiative <id>', 'Initiative ID')
|
||||
.requiredOption('--project <id>', 'Project ID')
|
||||
.requiredOption('--branch <branch>', 'Branch to deploy')
|
||||
.option('--initiative <id>', 'Initiative ID')
|
||||
.option('--project <id>', 'Project ID')
|
||||
.option('--branch <branch>', 'Branch to deploy')
|
||||
.option('--phase <id>', 'Phase ID')
|
||||
.action(async (options: { initiative: string; project: string; branch: string; phase?: string }) => {
|
||||
.option('--agent <id>', 'Agent ID (server resolves initiative/project/branch)')
|
||||
.action(async (options: { initiative?: string; project?: string; branch?: string; phase?: string; agent?: string }) => {
|
||||
try {
|
||||
const client = createDefaultTrpcClient();
|
||||
console.log('Starting preview deployment...');
|
||||
const preview = await client.startPreview.mutate({
|
||||
|
||||
let preview;
|
||||
if (options.agent) {
|
||||
preview = await client.startPreviewForAgent.mutate({ agentId: options.agent });
|
||||
} else {
|
||||
if (!options.initiative || !options.project || !options.branch) {
|
||||
console.error('Either --agent or all of --initiative, --project, --branch are required');
|
||||
process.exit(1);
|
||||
}
|
||||
preview = await client.startPreview.mutate({
|
||||
initiativeId: options.initiative,
|
||||
projectId: options.project,
|
||||
branch: options.branch,
|
||||
phaseId: options.phase,
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`Preview started: ${preview.id}`);
|
||||
console.log(` URL: ${preview.url}`);
|
||||
console.log(` Branch: ${preview.branch}`);
|
||||
@@ -1365,14 +1383,24 @@ export function createCli(serverHandler?: (port?: number) => Promise<void>): Com
|
||||
});
|
||||
|
||||
// cw preview stop <previewId>
|
||||
// cw preview stop --agent <id>
|
||||
previewCommand
|
||||
.command('stop <previewId>')
|
||||
.command('stop [previewId]')
|
||||
.description('Stop a preview deployment')
|
||||
.action(async (previewId: string) => {
|
||||
.option('--agent <id>', 'Stop preview by agent ID')
|
||||
.action(async (previewId: string | undefined, options: { agent?: string }) => {
|
||||
try {
|
||||
const client = createDefaultTrpcClient();
|
||||
if (options.agent) {
|
||||
await client.stopPreviewByAgent.mutate({ agentId: options.agent });
|
||||
console.log(`Previews for agent '${options.agent}' stopped`);
|
||||
} else if (previewId) {
|
||||
await client.stopPreview.mutate({ previewId });
|
||||
console.log(`Preview '${previewId}' stopped`);
|
||||
} else {
|
||||
console.error('Either <previewId> or --agent <id> is required');
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to stop preview:', (error as Error).message);
|
||||
process.exit(1);
|
||||
@@ -1434,6 +1462,138 @@ export function createCli(serverHandler?: (port?: number) => Promise<void>): Com
|
||||
}
|
||||
});
|
||||
|
||||
// cw preview setup [--auto --project <id>]
|
||||
previewCommand
|
||||
.command('setup')
|
||||
.description('Show preview deployment setup instructions')
|
||||
.option('--auto', 'Auto-create initiative and spawn agent to set up preview config')
|
||||
.option('--project <id>', 'Project ID (required with --auto)')
|
||||
.option('--provider <name>', 'Agent provider (optional, with --auto)')
|
||||
.action(async (options: { auto?: boolean; project?: string; provider?: string }) => {
|
||||
if (!options.auto) {
|
||||
console.log(`Preview Deployment Setup
|
||||
========================
|
||||
|
||||
Prerequisites:
|
||||
- Docker installed and running
|
||||
- Project registered in Codewalkers (cw project register)
|
||||
|
||||
Step 1: Add .cw-preview.yml to your project root
|
||||
|
||||
Minimal (single service with Dockerfile):
|
||||
|
||||
version: 1
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
port: 3000
|
||||
|
||||
Multi-service (frontend + API + database):
|
||||
|
||||
version: 1
|
||||
services:
|
||||
frontend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: apps/web/Dockerfile
|
||||
port: 3000
|
||||
route: /
|
||||
healthcheck:
|
||||
path: /
|
||||
backend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: packages/api/Dockerfile
|
||||
port: 8080
|
||||
route: /api
|
||||
healthcheck:
|
||||
path: /health
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
internal: true
|
||||
env:
|
||||
POSTGRES_PASSWORD: preview
|
||||
|
||||
Step 2: Commit .cw-preview.yml to your repo
|
||||
|
||||
Step 3: Start a preview
|
||||
cw preview start --initiative <id> --project <id> --branch <branch>
|
||||
|
||||
Without .cw-preview.yml, previews auto-discover:
|
||||
1. docker-compose.yml / compose.yml
|
||||
2. Dockerfile (single service, port 3000)
|
||||
|
||||
Full reference: docs/preview.md`);
|
||||
return;
|
||||
}
|
||||
|
||||
// --auto mode
|
||||
if (!options.project) {
|
||||
console.error('--project <id> is required with --auto');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
const client = createDefaultTrpcClient();
|
||||
|
||||
// Look up project
|
||||
const projects = await client.listProjects.query();
|
||||
const project = projects.find((p: { id: string }) => p.id === options.project);
|
||||
if (!project) {
|
||||
console.error(`Project '${options.project}' not found`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Create initiative
|
||||
const initiative = await client.createInitiative.mutate({
|
||||
name: `Preview setup: ${project.name}`,
|
||||
projectIds: [project.id],
|
||||
});
|
||||
|
||||
// Create root page with setup guide
|
||||
const rootPage = await client.getRootPage.query({ initiativeId: initiative.id });
|
||||
|
||||
const { markdownToTiptapJson } = await import('../agent/markdown-to-tiptap.js');
|
||||
const setupMarkdown = `# Preview Setup for ${project.name}
|
||||
|
||||
Analyze the project structure and create a \`.cw-preview.yml\` configuration file for Docker-based preview deployments.
|
||||
|
||||
## What to do
|
||||
|
||||
1. Examine the project's build system, frameworks, and service architecture
|
||||
2. Identify all services (frontend, backend, database, etc.)
|
||||
3. Create a \`.cw-preview.yml\` at the project root with appropriate service definitions
|
||||
4. Include dev mode configuration for hot-reload support
|
||||
5. Add healthcheck endpoints where applicable
|
||||
6. Commit the file to the repository
|
||||
|
||||
## Reference
|
||||
|
||||
See the Codewalkers documentation for .cw-preview.yml format and options.`;
|
||||
|
||||
await client.updatePage.mutate({
|
||||
id: rootPage.id,
|
||||
content: JSON.stringify(markdownToTiptapJson(setupMarkdown)),
|
||||
});
|
||||
|
||||
// Spawn refine agent
|
||||
const spawnInput: { initiativeId: string; instruction: string; provider?: string } = {
|
||||
initiativeId: initiative.id,
|
||||
instruction: 'Analyze the project and create a .cw-preview.yml for preview deployments. Follow the setup guide in the initiative pages.',
|
||||
};
|
||||
if (options.provider) {
|
||||
spawnInput.provider = options.provider;
|
||||
}
|
||||
const agent = await client.spawnArchitectRefine.mutate(spawnInput);
|
||||
|
||||
console.log(`Created initiative: ${initiative.id}`);
|
||||
console.log(`Spawned agent: ${agent.id} (${agent.name})`);
|
||||
} catch (error) {
|
||||
console.error('Failed to set up preview:', (error as Error).message);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// =========================================================================
|
||||
// Inter-agent conversation commands
|
||||
// =========================================================================
|
||||
|
||||
@@ -128,6 +128,7 @@ export function generateLabels(opts: {
|
||||
gatewayPort: number;
|
||||
previewId: string;
|
||||
mode: 'preview' | 'dev';
|
||||
agentId?: string;
|
||||
}): Record<string, string> {
|
||||
const labels: Record<string, string> = {
|
||||
[PREVIEW_LABELS.preview]: 'true',
|
||||
@@ -143,5 +144,9 @@ export function generateLabels(opts: {
|
||||
labels[PREVIEW_LABELS.phaseId] = opts.phaseId;
|
||||
}
|
||||
|
||||
if (opts.agentId) {
|
||||
labels[PREVIEW_LABELS.agentId] = opts.agentId;
|
||||
}
|
||||
|
||||
return labels;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,9 @@ import type {
|
||||
PreviewStoppedEvent,
|
||||
PreviewFailedEvent,
|
||||
PhasePendingReviewEvent,
|
||||
AgentStoppedEvent,
|
||||
} from '../events/types.js';
|
||||
import type { AgentManager } from '../agent/types.js';
|
||||
|
||||
const log = createModuleLogger('preview');
|
||||
|
||||
@@ -156,6 +158,7 @@ export class PreviewManager {
|
||||
gatewayPort,
|
||||
previewId: id,
|
||||
mode,
|
||||
agentId: options.agentId,
|
||||
});
|
||||
|
||||
const composeYaml = generateComposeFile(config, {
|
||||
@@ -429,6 +432,60 @@ export class PreviewManager {
|
||||
await this.gatewayManager.stopGateway().catch(() => {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a preview for an agent, resolving initiative/project/branch automatically.
|
||||
* The agent's worktree determines the source path and branch.
|
||||
*/
|
||||
async startForAgent(agentId: string, agentManager: AgentManager): Promise<PreviewStatus> {
|
||||
const agent = await agentManager.get(agentId);
|
||||
if (!agent) throw new Error(`Agent '${agentId}' not found`);
|
||||
if (!agent.initiativeId) throw new Error('Agent has no initiative');
|
||||
|
||||
const projects = await this.projectRepository.findProjectsByInitiativeId(agent.initiativeId);
|
||||
if (projects.length !== 1) {
|
||||
throw new Error(`Expected 1 project for initiative, found ${projects.length}`);
|
||||
}
|
||||
const project = projects[0];
|
||||
|
||||
const agentWorkdir = join(this.workspaceRoot, 'agent-workdirs', agent.worktreeId);
|
||||
const worktreePath = join(agentWorkdir, project.name);
|
||||
|
||||
const { simpleGit } = await import('simple-git');
|
||||
const git = simpleGit(worktreePath);
|
||||
const { current: branch } = await git.branchLocal();
|
||||
|
||||
return this.start({
|
||||
initiativeId: agent.initiativeId,
|
||||
projectId: project.id,
|
||||
branch: branch || 'main',
|
||||
mode: 'dev',
|
||||
worktreePath,
|
||||
agentId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop all previews associated with a specific agent ID (best-effort).
|
||||
*/
|
||||
async stopByAgentId(agentId: string): Promise<void> {
|
||||
try {
|
||||
const projects = await listPreviewProjects();
|
||||
for (const project of projects) {
|
||||
if (project.Name === 'cw-preview-gateway') continue;
|
||||
const labels = await getContainerLabels(project.Name);
|
||||
if (labels[PREVIEW_LABELS.agentId] === agentId) {
|
||||
const previewId = labels[PREVIEW_LABELS.previewId] ?? project.Name.replace(COMPOSE_PROJECT_PREFIX, '');
|
||||
log.info({ previewId, agentId }, 'stopping preview for agent');
|
||||
await this.stop(previewId).catch((err) => {
|
||||
log.warn({ previewId, agentId, err }, 'failed to stop preview for agent');
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
log.warn({ agentId, err }, 'stopByAgentId failed (best-effort)');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register event listener for auto-starting previews on phase:pending_review.
|
||||
*/
|
||||
@@ -479,6 +536,11 @@ export class PreviewManager {
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Auto-teardown previews when their owning agent stops
|
||||
this.eventBus.on<AgentStoppedEvent>('agent:stopped', async (event) => {
|
||||
await this.stopByAgentId(event.payload.agentId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -74,6 +74,7 @@ export const PREVIEW_LABELS = {
|
||||
port: `${PREVIEW_LABEL_PREFIX}.port`,
|
||||
previewId: `${PREVIEW_LABEL_PREFIX}.preview-id`,
|
||||
mode: `${PREVIEW_LABEL_PREFIX}.mode`,
|
||||
agentId: `${PREVIEW_LABEL_PREFIX}.agent-id`,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
@@ -97,6 +98,7 @@ export interface StartPreviewOptions {
|
||||
branch: string;
|
||||
mode?: 'preview' | 'dev';
|
||||
worktreePath?: string;
|
||||
agentId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import { z } from 'zod';
|
||||
import type { ProcedureBuilder } from '../trpc.js';
|
||||
import { requirePreviewManager } from './_helpers.js';
|
||||
import { requirePreviewManager, requireAgentManager } from './_helpers.js';
|
||||
|
||||
export function previewProcedures(publicProcedure: ProcedureBuilder) {
|
||||
return {
|
||||
@@ -16,12 +16,29 @@ export function previewProcedures(publicProcedure: ProcedureBuilder) {
|
||||
branch: z.string().min(1),
|
||||
mode: z.enum(['preview', 'dev']).default('preview'),
|
||||
worktreePath: z.string().optional(),
|
||||
agentId: z.string().min(1).optional(),
|
||||
}))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const previewManager = requirePreviewManager(ctx);
|
||||
return previewManager.start(input);
|
||||
}),
|
||||
|
||||
startPreviewForAgent: publicProcedure
|
||||
.input(z.object({ agentId: z.string().min(1) }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const previewManager = requirePreviewManager(ctx);
|
||||
const agentManager = requireAgentManager(ctx);
|
||||
return previewManager.startForAgent(input.agentId, agentManager);
|
||||
}),
|
||||
|
||||
stopPreviewByAgent: publicProcedure
|
||||
.input(z.object({ agentId: z.string().min(1) }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const previewManager = requirePreviewManager(ctx);
|
||||
await previewManager.stopByAgentId(input.agentId);
|
||||
return { success: true };
|
||||
}),
|
||||
|
||||
stopPreview: publicProcedure
|
||||
.input(z.object({
|
||||
previewId: z.string().min(1),
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { z } from 'zod';
|
||||
import { join } from 'node:path';
|
||||
import { rm } from 'node:fs/promises';
|
||||
import { rm, access } from 'node:fs/promises';
|
||||
import type { ProcedureBuilder } from '../trpc.js';
|
||||
import { requireProjectRepository, requireProjectSyncManager } from './_helpers.js';
|
||||
import { cloneProject } from '../../git/clone.js';
|
||||
@@ -69,7 +69,17 @@ export function projectProcedures(publicProcedure: ProcedureBuilder) {
|
||||
}
|
||||
}
|
||||
|
||||
return project;
|
||||
// Check for preview config
|
||||
let hasPreviewConfig = false;
|
||||
if (ctx.workspaceRoot) {
|
||||
const clonePath = join(ctx.workspaceRoot, getProjectCloneDir(input.name, project.id));
|
||||
try {
|
||||
await access(join(clonePath, '.cw-preview.yml'));
|
||||
hasPreviewConfig = true;
|
||||
} catch { /* no config */ }
|
||||
}
|
||||
|
||||
return { ...project, hasPreviewConfig };
|
||||
}),
|
||||
|
||||
listProjects: publicProcedure
|
||||
|
||||
@@ -268,6 +268,7 @@ All preview containers get `cw.*` labels for metadata retrieval:
|
||||
| `cw.port` | Gateway port |
|
||||
| `cw.preview-id` | Nanoid for this deployment |
|
||||
| `cw.mode` | `"preview"` or `"dev"` |
|
||||
| `cw.agent-id` | Agent ID (optional, set when started via `--agent`) |
|
||||
|
||||
### Compose Project Naming
|
||||
|
||||
@@ -321,24 +322,62 @@ See [Setting Up Preview Deployments](#setting-up-preview-deployments-for-a-proje
|
||||
3. Branch is derived from `phaseBranchName(initiative.branch, phase.name)`
|
||||
4. Errors are caught and logged (best-effort, never blocks the phase transition)
|
||||
|
||||
## Agent Integration
|
||||
|
||||
Agents can spin up and tear down preview deployments using simplified commands that only require their agent ID — the server resolves initiative, project, branch, and mode automatically.
|
||||
|
||||
### Agent-Simplified Commands
|
||||
|
||||
When an agent has a `<preview_deployments>` section in its prompt (injected automatically if the initiative's project has `.cw-preview.yml`), it can use:
|
||||
|
||||
```
|
||||
cw preview start --agent <agentId> # Server resolves everything, starts dev mode
|
||||
cw preview stop --agent <agentId> # Stops all previews for this agent
|
||||
```
|
||||
|
||||
### Prompt Injection
|
||||
|
||||
Preview instructions are automatically appended to agent prompts when all conditions are met:
|
||||
1. Agent mode is `execute`, `refine`, or `discuss`
|
||||
2. Agent has an `initiativeId`
|
||||
3. Initiative has exactly one linked project
|
||||
4. Project clone directory contains `.cw-preview.yml`
|
||||
|
||||
The injected `<preview_deployments>` block includes prefilled `cw preview start/stop --agent <agentId>` commands.
|
||||
|
||||
### Agent ID Label
|
||||
|
||||
Previews started with `--agent` receive a `cw.agent-id` Docker container label. This enables:
|
||||
- **Auto-teardown**: When an agent stops (`agent:stopped` event), all previews labeled with its ID are automatically torn down (best-effort).
|
||||
- **Agent-scoped stop**: `cw preview stop --agent <id>` finds and stops previews by label.
|
||||
|
||||
### Setup Command
|
||||
|
||||
`cw preview setup` prints inline setup instructions for `.cw-preview.yml`. With `--auto --project <id>`, it creates an initiative and spawns a refine agent to analyze the project and generate the config file.
|
||||
|
||||
## tRPC Procedures
|
||||
|
||||
| Procedure | Type | Input |
|
||||
|-----------|------|-------|
|
||||
| `startPreview` | mutation | `{initiativeId, phaseId?, projectId, branch, mode?, worktreePath?}` |
|
||||
| `startPreview` | mutation | `{initiativeId, phaseId?, projectId, branch, mode?, worktreePath?, agentId?}` |
|
||||
| `startPreviewForAgent` | mutation | `{agentId}` |
|
||||
| `stopPreview` | mutation | `{previewId}` |
|
||||
| `stopPreviewByAgent` | mutation | `{agentId}` |
|
||||
| `listPreviews` | query | `{initiativeId?}` |
|
||||
| `getPreviewStatus` | query | `{previewId}` |
|
||||
|
||||
`mode` defaults to `'preview'`. Set to `'dev'` with a `worktreePath` for dev mode.
|
||||
`mode` defaults to `'preview'`. Set to `'dev'` with a `worktreePath` for dev mode. `startPreviewForAgent` always uses dev mode.
|
||||
|
||||
## CLI Commands
|
||||
|
||||
```
|
||||
cw preview start --initiative <id> --project <id> --branch <branch> [--phase <id>] [--mode preview|dev]
|
||||
cw preview start --initiative <id> --project <id> --branch <branch> [--phase <id>]
|
||||
cw preview start --agent <id>
|
||||
cw preview stop <previewId>
|
||||
cw preview stop --agent <id>
|
||||
cw preview list [--initiative <id>]
|
||||
cw preview status <previewId>
|
||||
cw preview setup [--auto --project <id> [--provider <name>]]
|
||||
```
|
||||
|
||||
## Frontend
|
||||
|
||||
Reference in New Issue
Block a user