Files
openclaw-workspace-2026/architecture/workflow-router.js
T

289 lines
8.0 KiB
JavaScript

#!/usr/bin/env node
/**
* Workflow Router - Intent Classification and Routing
*
* Before responding, classify intent and route to fixed workflow template
*/
const fs = require('fs');
const path = require('path');
class WorkflowRouter {
constructor() {
this.workflows = new Map();
this.loadWorkflows();
}
/**
* Load all workflow definitions from workflows/ directory
*/
loadWorkflows() {
const workflowsDir = path.join(__dirname, '..', 'workflows');
const workflowFiles = [
'coding.md',
'debug.md',
'deploy.md',
'audit.md',
'planning.md'
];
for (const file of workflowFiles) {
const workflowName = path.basename(file, '.md');
const filePath = path.join(workflowsDir, file);
try {
const content = fs.readFileSync(filePath, 'utf8');
this.workflows.set(workflowName, this.parseWorkflow(content));
} catch (err) {
console.warn(`Workflow ${file} not found, using defaults`);
this.workflows.set(workflowName, this.getDefaultWorkflow(workflowName));
}
}
}
/**
* Parse workflow markdown into structured object
*/
parseWorkflow(content) {
const workflow = {
name: '',
triggers: [],
outputFormat: '',
toolRules: [],
constraints: [],
requiredSections: []
};
// Extract name from first header
const nameMatch = content.match(/^#\s+Workflow:\s*(.+)$/m);
if (nameMatch) workflow.name = nameMatch[1].trim();
// Extract triggers
const triggersMatch = content.match(/\*\*Triggers:\*\*\s*(.+)/);
if (triggersMatch) {
workflow.triggers = triggersMatch[1].split(',').map(t => t.trim().toLowerCase());
}
// Extract output format (code block after "Fixed Output Format")
const formatMatch = content.match(/## Fixed Output Format\s*```markdown\s*([\s\S]*?)```/);
if (formatMatch) workflow.outputFormat = formatMatch[1].trim();
// Extract tool rules
const rulesSection = content.match(/## Tool Access Rules\s*([\s\S]*?)(?=##|$)/);
if (rulesSection) {
workflow.toolRules = rulesSection[1]
.split('\n')
.filter(line => /^\d+\./.test(line))
.map(line => line.replace(/^\d+\.\s*/, '').trim());
}
// Extract constraints
const constraintsSection = content.match(/## Constraints\s*([\s\S]*?)(?=##|$)/);
if (constraintsSection) {
workflow.constraints = constraintsSection[1]
.split('\n')
.filter(line => /^(NO|YES):/.test(line))
.map(line => line.trim());
}
// Extract required sections from format template
const sectionMatches = workflow.outputFormat.match(/^##\s+(.+)$/gm);
if (sectionMatches) {
workflow.requiredSections = sectionMatches.map(s => s.replace(/^##\s*/, '').trim());
}
return workflow;
}
/**
* Classify user intent and return matching workflow
*/
classifyIntent(userInput) {
const input = userInput.toLowerCase();
// Intent patterns with confidence scoring
const intentPatterns = {
'coding': {
patterns: [
/\b(code|implement|build|create|write|develop|feature|fix bug|add\s+\w+\s+to)\b/,
/\b(refactor|optimize|improve|clean up)\b/,
/\b(pull request|pr|commit|merge|branch)\b/,
/\b(api|endpoint|route|handler|controller|model)\b/
],
weight: 1.0
},
'debug': {
patterns: [
/\b(debug|fix|broken|error|bug|issue|problem|crash|fails?|not working)\b/,
/\b(troubleshoot|diagnose|investigate|trace|root cause)\b/,
/\b(exception|stack trace|log|error message)\b/
],
weight: 1.0
},
'deploy': {
patterns: [
/\b(deploy|release|push to|go live|production|staging)\b/,
/\b(docker|container|build|image|registry)\b/,
/\b(rollback|revert|restore|emergency)\b/,
/\b(install|upgrade|update|migrate)\b/
],
weight: 1.0
},
'audit': {
patterns: [
/\b(audit|review|assess|inventory|check|scan|inspect)\b/,
/\b(security|vulnerability|compliance|policy)\b/,
/\b(status|health|state|report|summary)\b/,
/\b(workspace|files|structure|organization)\b/
],
weight: 1.0
},
'planning': {
patterns: [
/\b(plan|design|architecture|strategy|roadmap|timeline)\b/,
/\b(requirement|spec|proposal|approach)\b/,
/\b(breakdown|estimate|scope|prioritize)\b/
],
weight: 0.8
}
};
let bestMatch = null;
let bestScore = 0;
for (const [workflowName, config] of Object.entries(intentPatterns)) {
let score = 0;
let matches = 0;
for (const pattern of config.patterns) {
if (pattern.test(input)) {
matches++;
score += config.weight;
}
}
// Boost score for multiple matches
if (matches > 1) score *= 1.2;
if (score > bestScore) {
bestScore = score;
bestMatch = workflowName;
}
}
// Default to coding if no strong match
if (bestScore < 0.5 || !bestMatch) {
bestMatch = 'coding';
bestScore = 0.3;
}
return {
workflow: bestMatch,
confidence: Math.min(bestScore / 2, 1.0), // Normalize
allScores: this.getAllScores(intentPatterns, input)
};
}
/**
* Get confidence scores for all workflows
*/
getAllScores(patterns, input) {
const scores = {};
for (const [name, config] of Object.entries(patterns)) {
let score = 0;
for (const pattern of config.patterns) {
if (pattern.test(input)) score += config.weight;
}
scores[name] = score;
}
return scores;
}
/**
* Get workflow by name
*/
getWorkflow(name) {
return this.workflows.get(name) || this.getDefaultWorkflow(name);
}
/**
* Get default workflow template
*/
getDefaultWorkflow(name) {
return {
name: name || 'default',
triggers: ['general'],
outputFormat: '## Summary\n[Response]\n\n## Details\n[Details]',
toolRules: ['Delegate when possible', 'Verify before reporting'],
constraints: ['NO: Assumptions without verification', 'YES: Clear status updates'],
requiredSections: ['Summary', 'Details']
};
}
/**
* Build context-aware system prompt for the classified workflow
*/
buildSystemPrompt(workflowName, context = {}) {
const workflow = this.getWorkflow(workflowName);
const parts = [
`You are in ${workflow.name} workflow mode.`,
'',
'## Required Output Sections',
...workflow.requiredSections.map(s => `- ${s}`),
'',
'## Tool Access Rules',
...workflow.toolRules.map((rule, i) => `${i + 1}. ${rule}`),
'',
'## Constraints',
...workflow.constraints,
'',
'## Format Template',
'```markdown',
workflow.outputFormat,
'```'
];
if (context.project) {
parts.push('', `## Current Project: ${context.project}`);
}
return parts.join('\n');
}
/**
* Main entry point: classify and route
*/
route(userInput, context = {}) {
const classification = this.classifyIntent(userInput);
const workflow = this.getWorkflow(classification.workflow);
const systemPrompt = this.buildSystemPrompt(classification.workflow, context);
return {
workflow: classification.workflow,
confidence: classification.confidence,
workflowConfig: workflow,
systemPrompt: systemPrompt,
context: context
};
}
}
// Export for use
module.exports = WorkflowRouter;
// CLI usage
if (require.main === module) {
const router = new WorkflowRouter();
const userInput = process.argv[2] || 'implement a new feature';
const result = router.route(userInput, { project: process.env.CURRENT_PROJECT });
console.log('=== Workflow Classification ===');
console.log(`Workflow: ${result.workflow}`);
console.log(`Confidence: ${(result.confidence * 100).toFixed(1)}%`);
console.log('\n=== System Prompt ===');
console.log(result.systemPrompt);
}