scai
Version:
> **A local-first AI CLI for understanding, querying, and iterating on large codebases.** > **100% local • No token costs • No cloud • No prompt injection • Private by design**
108 lines (100 loc) • 4.46 kB
JavaScript
// File: src/agents/finalPlanGenStep.ts
import { generate } from '../lib/generate.js';
import { PLAN_ACTIONS } from '../utils/planActions.js';
import { logInputOutput } from '../utils/promptLogHelper.js';
import { cleanupModule } from '../pipeline/modules/cleanupModule.js';
/**
* FINAL PLAN GENERATOR
* Generates a single finalization step to wrap up and produce user-facing results.
*/
export const finalPlanGenStep = {
name: 'finalPlanGen',
description: 'Generates a single finalization step for the workflow.',
requires: ['analysis.intent', 'analysis.focus'],
produces: ['analysis.planSuggestion'],
async run(context) {
context.analysis || (context.analysis = {});
// Clear previous final plan
delete context.analysis.planSuggestion;
// Restrict actions to FINALIZE only
const effectiveActions = PLAN_ACTIONS.filter(a => a.groups?.includes('finalize'));
if (!effectiveActions.length) {
context.analysis.planSuggestion = { plan: { steps: [] } };
logInputOutput('finalPlanGen', 'output', { steps: [] });
return;
}
const actionsJson = JSON.stringify(effectiveActions, null, 2);
const intentText = context.analysis.intent?.normalizedQuery ?? '';
const intentCategory = context.analysis.intent?.intentCategory ?? '';
const prompt = `
You are an autonomous coding agent.
Produce exactly one structured step to finalize the workflow and generate the user-facing output.
Intent / task description:
${intentText}
Task category:
${intentCategory}
Allowed actions (finalize only):
${actionsJson}
Rules:
- Only produce a single finalize step.
- Step must include: "action", "targetFile" (optional), "description", "metadata".
- Do NOT invent new actions.
- Always ensure a user-facing result is produced.
Return strictly valid JSON representing one step:
{
"action": "stepName",
"targetFile": "optional/path.ts",
"description": "Generate final user-facing output summarizing results.",
"metadata": {}
}
`.trim();
try {
const genInput = { query: intentText, content: prompt };
const genOutput = await generate(genInput);
const raw = typeof genOutput.data === 'string' ? genOutput.data : JSON.stringify(genOutput.data ?? '{}');
const cleaned = await cleanupModule.run({ query: intentText, content: raw });
const jsonString = typeof cleaned.content === 'string' ? cleaned.content : JSON.stringify(cleaned.content ?? '{}');
let step = JSON.parse(jsonString);
// Validate structure
if (!step || typeof step.action !== 'string') {
step = {
id: 'finalize',
action: 'finalAnswer',
description: 'Generate final user-facing response summarizing results.',
metadata: {}
};
}
// Map groups & metadata
const actionDef = PLAN_ACTIONS.find(a => a.action === step.action);
step.groups = actionDef?.groups ?? ['finalize'];
const confidence = context.analysis?.routingDecision?.confidence;
step.metadata = {
...step.metadata,
...(typeof confidence === 'number' && confidence > 0
? { routingConfidence: confidence }
: {})
};
// Save single step to context
context.analysis.planSuggestion = { plan: { steps: [step] } };
logInputOutput('finalPlanGen', 'output', step);
}
catch (err) {
console.warn('⚠️ Failed to generate final step:', err);
// Fallback: always include finalAnswer
const confidence = context.analysis?.routingDecision?.confidence;
const step = {
id: 'finalize',
action: 'finalAnswer',
description: 'Generate final user-facing response summarizing results.',
metadata: {
...(typeof confidence === 'number' && confidence > 0
? { routingConfidence: confidence }
: {})
},
groups: ['finalize']
};
context.analysis.planSuggestion = { plan: { steps: [step] } };
logInputOutput('finalPlanGen', 'output', step);
}
}
};