refactor: replace console.log/chalk with ActivityLogger across services

- Add ActivityLogger interface wrapping Temporal's Context.current().log
- Thread logger parameter through claude-executor, message-handlers, git-manager, prompt-manager, reporting, and agent validators
- Remove chalk dependency from all service/activity files; CLI files keep console.log for terminal output
- Replace colorFn: ChalkInstance parameter with structured logger.info/warn/error calls
- Use replay-safe `log` import from @temporalio/workflow in workflows.ts
This commit is contained in:
ajmallesh
2026-02-16 17:16:27 -08:00
parent d3816a29fa
commit bb89d6f458
17 changed files with 322 additions and 296 deletions
+40 -44
View File
@@ -28,7 +28,6 @@
import { Connection, Client, WorkflowNotFoundError } from '@temporalio/client';
import dotenv from 'dotenv';
import chalk from 'chalk';
import { displaySplashScreen } from '../splash-screen.js';
import { sanitizeHostname } from '../audit/utils.js';
import { readJson, fileExists } from '../audit/utils.js';
@@ -89,18 +88,18 @@ async function terminateExistingWorkflows(
const description = await handle.describe();
if (description.status.name === 'RUNNING') {
console.log(chalk.yellow(`Terminating running workflow: ${wfId}`));
console.log(`Terminating running workflow: ${wfId}`);
await handle.terminate('Superseded by resume workflow');
terminated.push(wfId);
console.log(chalk.green(`Terminated: ${wfId}`));
console.log(`Terminated: ${wfId}`);
} else {
console.log(chalk.gray(`Workflow already ${description.status.name}: ${wfId}`));
console.log(`Workflow already ${description.status.name}: ${wfId}`);
}
} catch (error) {
if (error instanceof WorkflowNotFoundError) {
console.log(chalk.gray(`Workflow not found (already cleaned up): ${wfId}`));
console.log(`Workflow not found (already cleaned up): ${wfId}`);
} else {
console.log(chalk.red(`Failed to terminate ${wfId}: ${error}`));
console.log(`Failed to terminate ${wfId}: ${error}`);
// Continue anyway - don't block resume on termination failure
}
}
@@ -118,13 +117,13 @@ function isValidWorkspaceName(name: string): boolean {
}
function showUsage(): void {
console.log(chalk.cyan.bold('\nShannon Temporal Client'));
console.log(chalk.gray('Start a pentest pipeline workflow\n'));
console.log(chalk.yellow('Usage:'));
console.log('\nShannon Temporal Client');
console.log('Start a pentest pipeline workflow\n');
console.log('Usage:');
console.log(
' node dist/temporal/client.js <webUrl> <repoPath> [options]\n'
);
console.log(chalk.yellow('Options:'));
console.log('Options:');
console.log(' --config <path> Configuration file path');
console.log(' --output <path> Output directory for audit logs');
console.log(' --pipeline-testing Use minimal prompts for fast testing');
@@ -133,7 +132,7 @@ function showUsage(): void {
' --workflow-id <id> Custom workflow ID (default: shannon-<timestamp>)'
);
console.log(' --wait Wait for workflow completion with progress polling\n');
console.log(chalk.yellow('Examples:'));
console.log('Examples:');
console.log(' node dist/temporal/client.js https://example.com /path/to/repo');
console.log(
' node dist/temporal/client.js https://example.com /path/to/repo --config config.yaml\n'
@@ -205,7 +204,7 @@ async function startPipeline(): Promise<void> {
}
if (!webUrl || !repoPath) {
console.log(chalk.red('Error: webUrl and repoPath are required'));
console.log('Error: webUrl and repoPath are required');
showUsage();
process.exit(1);
}
@@ -214,7 +213,7 @@ async function startPipeline(): Promise<void> {
await displaySplashScreen();
const address = process.env.TEMPORAL_ADDRESS || 'localhost:7233';
console.log(chalk.gray(`Connecting to Temporal at ${address}...`));
console.log(`Connecting to Temporal at ${address}...`);
const connection = await Connection.connect({ address });
const client = new Client({ connection });
@@ -232,21 +231,21 @@ async function startPipeline(): Promise<void> {
if (workspaceExists) {
// === Resume Mode: existing workspace ===
isResume = true;
console.log(chalk.cyan('=== RESUME MODE ==='));
console.log('=== RESUME MODE ===');
console.log(`Workspace: ${resumeFromWorkspace}\n`);
// Terminate any running workflows for this workspace
terminatedWorkflows = await terminateExistingWorkflows(client, resumeFromWorkspace);
if (terminatedWorkflows.length > 0) {
console.log(chalk.yellow(`Terminated ${terminatedWorkflows.length} previous workflow(s)\n`));
console.log(`Terminated ${terminatedWorkflows.length} previous workflow(s)\n`);
}
// Validate URL matches workspace
const session = await readJson<SessionJson>(sessionPath);
if (session.session.webUrl !== webUrl) {
console.error(chalk.red('ERROR: URL mismatch with workspace'));
console.error('ERROR: URL mismatch with workspace');
console.error(` Workspace URL: ${session.session.webUrl}`);
console.error(` Provided URL: ${webUrl}`);
process.exit(1);
@@ -258,12 +257,12 @@ async function startPipeline(): Promise<void> {
} else {
// === New Named Workspace ===
if (!isValidWorkspaceName(resumeFromWorkspace)) {
console.error(chalk.red(`ERROR: Invalid workspace name: "${resumeFromWorkspace}"`));
console.error(chalk.gray(' Must be 1-128 characters, alphanumeric/hyphens/underscores, starting with alphanumeric'));
console.error(`ERROR: Invalid workspace name: "${resumeFromWorkspace}"`);
console.error(' Must be 1-128 characters, alphanumeric/hyphens/underscores, starting with alphanumeric');
process.exit(1);
}
console.log(chalk.cyan('=== NEW NAMED WORKSPACE ==='));
console.log('=== NEW NAMED WORKSPACE ===');
console.log(`Workspace: ${resumeFromWorkspace}\n`);
workflowId = `${resumeFromWorkspace}_shannon-${Date.now()}`;
@@ -293,22 +292,22 @@ async function startPipeline(): Promise<void> {
const effectiveDisplayPath = displayOutputPath || outputPath || './audit-logs';
const outputDir = `${effectiveDisplayPath}/${sessionId}`;
console.log(chalk.green.bold(`✓ Workflow started: ${workflowId}`));
console.log(`✓ Workflow started: ${workflowId}`);
if (isResume) {
console.log(chalk.gray(` (Resuming workspace: ${sessionId})`));
console.log(` (Resuming workspace: ${sessionId})`);
}
console.log();
console.log(chalk.white(' Target: ') + chalk.cyan(webUrl));
console.log(chalk.white(' Repository: ') + chalk.cyan(repoPath));
console.log(chalk.white(' Workspace: ') + chalk.cyan(sessionId));
console.log(` Target: ${webUrl}`);
console.log(` Repository: ${repoPath}`);
console.log(` Workspace: ${sessionId}`);
if (configPath) {
console.log(chalk.white(' Config: ') + chalk.cyan(configPath));
console.log(` Config: ${configPath}`);
}
if (displayOutputPath) {
console.log(chalk.white(' Output: ') + chalk.cyan(displayOutputPath));
console.log(` Output: ${displayOutputPath}`);
}
if (pipelineTestingMode) {
console.log(chalk.white(' Mode: ') + chalk.yellow('Pipeline Testing'));
console.log(` Mode: Pipeline Testing`);
}
console.log();
@@ -323,12 +322,12 @@ async function startPipeline(): Promise<void> {
);
if (!waitForCompletion) {
console.log(chalk.bold('Monitor progress:'));
console.log(chalk.white(' Web UI: ') + chalk.blue(`http://localhost:8233/namespaces/default/workflows/${workflowId}`));
console.log(chalk.white(' Logs: ') + chalk.gray(`./shannon logs ID=${workflowId}`));
console.log('Monitor progress:');
console.log(` Web UI: http://localhost:8233/namespaces/default/workflows/${workflowId}`);
console.log(` Logs: ./shannon logs ID=${workflowId}`);
console.log();
console.log(chalk.bold('Output:'));
console.log(chalk.white(' Reports: ') + chalk.cyan(outputDir));
console.log('Output:');
console.log(` Reports: ${outputDir}`);
console.log();
return;
}
@@ -339,10 +338,7 @@ async function startPipeline(): Promise<void> {
const progress = await handle.query<PipelineProgress>(PROGRESS_QUERY);
const elapsed = Math.floor(progress.elapsedMs / 1000);
console.log(
chalk.gray(`[${elapsed}s]`),
chalk.cyan(`Phase: ${progress.currentPhase || 'unknown'}`),
chalk.gray(`| Agent: ${progress.currentAgent || 'none'}`),
chalk.gray(`| Completed: ${progress.completedAgents.length}/13`)
`[${elapsed}s] Phase: ${progress.currentPhase || 'unknown'} | Agent: ${progress.currentAgent || 'none'} | Completed: ${progress.completedAgents.length}/13`
);
} catch {
// Workflow may have completed
@@ -353,12 +349,12 @@ async function startPipeline(): Promise<void> {
const result = await handle.result();
clearInterval(progressInterval);
console.log(chalk.green.bold('\nPipeline completed successfully!'));
console.log('\nPipeline completed successfully!');
if (result.summary) {
console.log(chalk.gray(`Duration: ${Math.floor(result.summary.totalDurationMs / 1000)}s`));
console.log(chalk.gray(`Agents completed: ${result.summary.agentCount}`));
console.log(chalk.gray(`Total turns: ${result.summary.totalTurns}`));
console.log(chalk.gray(`Run cost: $${result.summary.totalCostUsd.toFixed(4)}`));
console.log(`Duration: ${Math.floor(result.summary.totalDurationMs / 1000)}s`);
console.log(`Agents completed: ${result.summary.agentCount}`);
console.log(`Total turns: ${result.summary.totalTurns}`);
console.log(`Run cost: $${result.summary.totalCostUsd.toFixed(4)}`);
// Show cumulative cost from session.json (includes all resume attempts)
if (isResume) {
@@ -366,7 +362,7 @@ async function startPipeline(): Promise<void> {
const session = await readJson<SessionJson>(
path.join('./audit-logs', sessionId, 'session.json')
);
console.log(chalk.gray(`Cumulative cost: $${session.metrics.total_cost_usd.toFixed(4)}`));
console.log(`Cumulative cost: $${session.metrics.total_cost_usd.toFixed(4)}`);
} catch {
// Non-fatal, skip cumulative cost display
}
@@ -374,7 +370,7 @@ async function startPipeline(): Promise<void> {
}
} catch (error) {
clearInterval(progressInterval);
console.error(chalk.red.bold('\nPipeline failed:'), error);
console.error('\nPipeline failed:', error);
process.exit(1);
}
} finally {
@@ -383,6 +379,6 @@ async function startPipeline(): Promise<void> {
}
startPipeline().catch((err) => {
console.error(chalk.red('Client error:'), err);
console.error('Client error:', err);
process.exit(1);
});