scai
Version:
> **AI-powered CLI for local code analysis, commit message suggestions, and natural-language queries.** > **100% local • No token cost • Private by design • GDPR-friendly** — made in Denmark/EU with ❤️.
129 lines (122 loc) • 5.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';
const MAX_STEPS = 100;
/**
* FINAL PLAN GENERATOR
* Generates steps that finalize results, commit changes, or prepare outputs.
*/
export const finalPlanGenStep = {
name: 'finalPlanGen',
description: 'Generates finalization steps to wrap up and commit results.',
requires: ['analysis.intent', 'analysis.focus'],
produces: ['analysis.planSuggestion'],
async run(context) {
var _a, _b;
context.analysis || (context.analysis = {});
(_a = context.analysis).planSuggestion || (_a.planSuggestion = {});
(_b = context.analysis.planSuggestion).plan || (_b.plan = { steps: [] });
// Only allow actions in the FINALIZE group
const effectiveActions = PLAN_ACTIONS.filter(a => a.groups?.includes('finalize'));
const actionsJson = JSON.stringify(effectiveActions, null, 2);
const intentText = context.analysis.intent?.normalizedQuery ?? '';
const intentCategory = context.analysis.intent?.intentCategory ?? '';
// Simplified prompt — focus purely on allowed finalization actions
const prompt = `
You are an autonomous coding agent.
Your task is to produce a JSON plan for the FINALIZATION phase only.
Each step should describe a module to run to finalize the workflow and produce a response to the user.
Intent / task description:
${intentText}
Task category:
${intentCategory}
Allowed actions (finalize only):
${actionsJson}
⚡ Important instructions:
- Only use actions listed in the allowed actions.
- Every plan must produce a user-facing response.
- Each step must include: "action", "targetFile" (optional), "description", "metadata".
- Do NOT invent new actions.
- Return strictly valid JSON.
Example format:
{
"steps": [
{ "action": "finalAnswer", "targetFile": null, "description": "Generate final user-facing response.", "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 });
let plan = null;
if (cleaned.data && typeof cleaned.data === 'object') {
// ✅ cleanup succeeded
plan = cleaned.data;
}
else if (typeof cleaned.data === 'string') {
// ⚠️ fallback: try extracting JSON
const match = cleaned.data.match(/\{[\s\S]*\}/);
if (match) {
plan = JSON.parse(match[0]);
}
}
if (!plan || !Array.isArray(plan.steps)) {
throw new Error('Invalid final plan structure');
}
if (!plan || !Array.isArray(plan.steps))
throw new Error('Invalid final plan structure');
if (plan.steps.length > MAX_STEPS)
plan.steps = plan.steps.slice(0, MAX_STEPS);
// Map groups & metadata
plan.steps = plan.steps.map(step => {
const actionDef = PLAN_ACTIONS.find(a => a.action === step.action);
return {
...step,
metadata: {
...step.metadata,
routingConfidence: context.analysis?.routingDecision?.confidence ?? 0
},
groups: actionDef?.groups ?? ['finalize']
};
});
// Ensure at least one finalAnswer step if plan is empty
if (!plan.steps.some(s => s.action === 'finalAnswer')) {
plan.steps.push({
action: 'finalAnswer',
description: 'Generate final user-facing response summarizing results.',
metadata: {
routingConfidence: context.analysis?.routingDecision?.confidence ?? 0
},
groups: ['finalize']
});
}
// Replace existing finalize steps in planSuggestion
context.analysis.planSuggestion.plan.steps = [
...context.analysis.planSuggestion.plan.steps.filter(s => !s.groups?.includes('finalize')),
...plan.steps
];
logInputOutput('finalPlanGen', 'output', plan);
}
catch (err) {
console.warn('⚠️ Failed to generate final plan:', err);
// Fallback: always include finalAnswer
context.analysis.planSuggestion.plan.steps = [
{
action: 'finalAnswer',
description: 'Generate final user-facing response summarizing results.',
metadata: {
routingConfidence: context.analysis?.routingDecision?.confidence ?? 0
},
groups: ['finalize']
}
];
logInputOutput('finalPlanGen', 'output', context.analysis.planSuggestion.plan);
}
}
};