Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/backends/progressModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { AgentBuilder, LLMist, type ModelSpec } from 'llmist';

import { getAgentLabel } from '../config/agentMessages.js';
import type { Todo } from '../gadgets/todo/storage.js';

export interface ProgressContext {
Expand All @@ -21,7 +22,7 @@ export interface ProgressContext {
completedTasks?: { subject: string; summary: string; timestamp: number }[];
}

const PROGRESS_SYSTEM_PROMPT = `You are a progress reporter for an AI coding agent called CASCADE. Write a brief, informative progress update based on the agent's current state. Be concise (3-5 sentences max). Focus on what has been accomplished, what's currently in progress, and what remains. Synthesize the agent's own commentary, tool call details (file paths, commands), and completed task summaries into a coherent narrative β€” do not just list tool names. Use markdown formatting. Write in first person (e.g. "I'm implementing...", "I've completed...", "I'm currently working on..."). Start with a bold header like "**Progress update** (X min)". Do not include a progress bar β€” the system adds that separately.`;
const PROGRESS_SYSTEM_PROMPT = `You are a progress reporter for an AI coding agent called CASCADE. Write a brief, informative progress update based on the agent's current state. Be concise (3-5 sentences max). Focus on what has been accomplished, what's currently in progress, and what remains. Synthesize the agent's own commentary, tool call details (file paths, commands), and completed task summaries into a coherent narrative β€” do not just list tool names. Use markdown formatting. Write in first person (e.g. "I'm implementing...", "I've completed...", "I'm currently working on..."). Start with a bold header using the exact header provided in the user prompt context (e.g. "**πŸ§‘β€πŸ’» Implementation Update** (X min)"). Do not include a progress bar β€” the system adds that separately.`;

function formatProgressUserPrompt(context: ProgressContext): string {
const {
Expand All @@ -35,8 +36,11 @@ function formatProgressUserPrompt(context: ProgressContext): string {
completedTasks,
} = context;

const { emoji, label } = getAgentLabel(agentType);

const sections: string[] = [
`Agent: ${agentType}`,
`Progress header: **${emoji} ${label}** (${Math.round(elapsedMinutes)} min)`,
`Task: ${taskDescription.slice(0, 500)}`,
`Time elapsed: ${Math.round(elapsedMinutes)} minutes`,
`Iterations: ${iteration}`,
Expand Down
27 changes: 27 additions & 0 deletions src/config/agentMessages.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,30 @@
/**
* Agent-specific emoji and label for progress update headers.
*
* Used by:
* - progressModel.ts β€” LLM prompt to produce correct header
* - statusUpdateConfig.ts β€” template fallback header
*/
export const AGENT_LABELS: Record<string, { emoji: string; label: string }> = {
briefing: { emoji: 'πŸ“‹', label: 'Briefing Update' },
planning: { emoji: 'πŸ—ΊοΈ', label: 'Planning Update' },
implementation: { emoji: 'πŸ§‘β€πŸ’»', label: 'Implementation Update' },
review: { emoji: 'πŸ”', label: 'Code Review Update' },
'respond-to-planning-comment': { emoji: 'πŸ’¬', label: 'Planning Response Update' },
'respond-to-review': { emoji: 'πŸ”§', label: 'Review Response Update' },
'respond-to-pr-comment': { emoji: 'πŸ’¬', label: 'PR Comment Response Update' },
'respond-to-ci': { emoji: 'πŸ”§', label: 'CI Fix Update' },
debug: { emoji: 'πŸ›', label: 'Debug Update' },
};

/**
* Get the emoji and label for a given agent type.
* Falls back to a generic label for unknown agent types.
*/
export function getAgentLabel(agentType: string): { emoji: string; label: string } {
return AGENT_LABELS[agentType] ?? { emoji: 'βš™οΈ', label: 'Progress Update' };
}

/**
* Human-readable initial messages per agent type.
*
Expand Down
5 changes: 4 additions & 1 deletion src/config/statusUpdateConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import { formatTodoList, loadTodos } from '../gadgets/todo/storage.js';
import { getAgentLabel } from './agentMessages.js';

/**
* Configuration for periodic status updates.
Expand Down Expand Up @@ -70,8 +71,10 @@ export function formatStatusMessage(
const doneCount = todos.filter((t) => t.status === 'done').length;
const totalCount = todos.length;

const { emoji, label } = getAgentLabel(agentType);

const lines = [
`**I'm making progress** (${agentType})`,
`**${emoji} ${label}** (${agentType})`,
'',
`${progressBar} ${progress}% (iteration ${iteration}/${maxIterations})`,
];
Expand Down
59 changes: 56 additions & 3 deletions tests/unit/config/statusUpdateConfig.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { afterEach, describe, expect, it, vi } from 'vitest';

import { getAgentLabel } from '../../../src/config/agentMessages.js';
import {
formatGitHubProgressComment,
formatStatusMessage,
Expand Down Expand Up @@ -54,13 +55,65 @@ describe('config/statusUpdateConfig', () => {
});
});

describe('getAgentLabel', () => {
it('returns correct emoji and label for implementation', () => {
const result = getAgentLabel('implementation');
expect(result).toEqual({ emoji: 'πŸ§‘β€πŸ’»', label: 'Implementation Update' });
});

it('returns correct emoji and label for review', () => {
const result = getAgentLabel('review');
expect(result).toEqual({ emoji: 'πŸ”', label: 'Code Review Update' });
});

it('returns correct emoji and label for briefing', () => {
const result = getAgentLabel('briefing');
expect(result).toEqual({ emoji: 'πŸ“‹', label: 'Briefing Update' });
});

it('returns correct emoji and label for planning', () => {
const result = getAgentLabel('planning');
expect(result).toEqual({ emoji: 'πŸ—ΊοΈ', label: 'Planning Update' });
});

it('returns correct emoji and label for respond-to-review', () => {
const result = getAgentLabel('respond-to-review');
expect(result).toEqual({ emoji: 'πŸ”§', label: 'Review Response Update' });
});

it('returns correct emoji and label for respond-to-ci', () => {
const result = getAgentLabel('respond-to-ci');
expect(result).toEqual({ emoji: 'πŸ”§', label: 'CI Fix Update' });
});

it('returns correct emoji and label for respond-to-pr-comment', () => {
const result = getAgentLabel('respond-to-pr-comment');
expect(result).toEqual({ emoji: 'πŸ’¬', label: 'PR Comment Response Update' });
});

it('returns correct emoji and label for respond-to-planning-comment', () => {
const result = getAgentLabel('respond-to-planning-comment');
expect(result).toEqual({ emoji: 'πŸ’¬', label: 'Planning Response Update' });
});

it('returns correct emoji and label for debug', () => {
const result = getAgentLabel('debug');
expect(result).toEqual({ emoji: 'πŸ›', label: 'Debug Update' });
});

it('returns default fallback for unknown agent types', () => {
const result = getAgentLabel('future-unknown-agent');
expect(result).toEqual({ emoji: 'βš™οΈ', label: 'Progress Update' });
});
});

describe('formatStatusMessage', () => {
it('includes agent type and progress bar', () => {
it('includes agent-specific emoji/label and progress bar', () => {
vi.mocked(loadTodos).mockReturnValue([]);

const message = formatStatusMessage(5, 20, 'implementation');

expect(message).toContain("**I'm making progress**");
expect(message).toContain('**πŸ§‘β€πŸ’» Implementation Update**');
expect(message).toContain('implementation');
expect(message).toContain('25%'); // (5/20) * 100
expect(message).toContain('iteration 5/20');
Expand Down Expand Up @@ -151,7 +204,7 @@ describe('config/statusUpdateConfig', () => {
const message = formatStatusMessage(10, 20, 'implementation');

const lines = message.split('\n');
expect(lines[0]).toBe("**I'm making progress** (implementation)");
expect(lines[0]).toBe('**πŸ§‘β€πŸ’» Implementation Update** (implementation)');
expect(lines[1]).toBe('');
expect(lines[2]).toContain('[β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘]');
});
Expand Down