dev-mcp-server 0.0.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/.env.example +23 -55
  2. package/README.md +609 -219
  3. package/cli.js +486 -160
  4. package/package.json +2 -2
  5. package/src/agents/BaseAgent.js +113 -0
  6. package/src/agents/dreamer.js +165 -0
  7. package/src/agents/improver.js +175 -0
  8. package/src/agents/specialists.js +202 -0
  9. package/src/agents/taskDecomposer.js +176 -0
  10. package/src/agents/teamCoordinator.js +153 -0
  11. package/src/api/routes/agents.js +172 -0
  12. package/src/api/routes/extras.js +115 -0
  13. package/src/api/routes/git.js +72 -0
  14. package/src/api/routes/ingest.js +60 -40
  15. package/src/api/routes/knowledge.js +59 -41
  16. package/src/api/routes/memory.js +41 -0
  17. package/src/api/routes/newRoutes.js +168 -0
  18. package/src/api/routes/pipelines.js +41 -0
  19. package/src/api/routes/planner.js +54 -0
  20. package/src/api/routes/query.js +24 -0
  21. package/src/api/routes/sessions.js +54 -0
  22. package/src/api/routes/tasks.js +67 -0
  23. package/src/api/routes/tools.js +85 -0
  24. package/src/api/routes/v5routes.js +196 -0
  25. package/src/api/server.js +133 -5
  26. package/src/context/compactor.js +151 -0
  27. package/src/context/contextEngineer.js +181 -0
  28. package/src/context/contextVisualizer.js +140 -0
  29. package/src/core/conversationEngine.js +231 -0
  30. package/src/core/indexer.js +169 -143
  31. package/src/core/ingester.js +141 -126
  32. package/src/core/queryEngine.js +286 -236
  33. package/src/cron/cronScheduler.js +260 -0
  34. package/src/dashboard/index.html +1181 -0
  35. package/src/lsp/symbolNavigator.js +220 -0
  36. package/src/memory/memoryManager.js +186 -0
  37. package/src/memory/teamMemory.js +111 -0
  38. package/src/messaging/messageBus.js +177 -0
  39. package/src/monitor/proactiveMonitor.js +337 -0
  40. package/src/pipelines/pipelineEngine.js +230 -0
  41. package/src/planner/plannerEngine.js +202 -0
  42. package/src/plugins/builtin/stats-plugin.js +29 -0
  43. package/src/plugins/pluginManager.js +144 -0
  44. package/src/prompts/promptEngineer.js +289 -0
  45. package/src/sessions/sessionManager.js +166 -0
  46. package/src/skills/skillsManager.js +263 -0
  47. package/src/storage/store.js +127 -105
  48. package/src/tasks/taskManager.js +151 -0
  49. package/src/tools/BashTool.js +154 -0
  50. package/src/tools/FileEditTool.js +280 -0
  51. package/src/tools/GitTool.js +212 -0
  52. package/src/tools/GrepTool.js +199 -0
  53. package/src/tools/registry.js +1380 -0
  54. package/src/utils/costTracker.js +69 -0
  55. package/src/utils/fileParser.js +176 -153
  56. package/src/utils/llmClient.js +355 -206
  57. package/src/watcher/fileWatcher.js +137 -0
  58. package/src/worktrees/worktreeManager.js +176 -0
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Breaks complex tasks into smaller, parallelizable subtasks and delegates
3
+ * each to the most appropriate specialist agent.
4
+ */
5
+
6
+ const llm = require('../utils/llmClient');
7
+ const costTracker = require('../utils/costTracker');
8
+ const logger = require('../utils/logger');
9
+ const indexer = require('../core/indexer');
10
+ const contextEngineer = require('../context/contextEngineer');
11
+
12
+ // Agent → what kinds of subtasks it handles
13
+ const AGENT_CAPABILITIES = {
14
+ DebugAgent: ['debug', 'error', 'exception', 'crash', 'fix', 'trace', 'failing'],
15
+ ArchitectureAgent: ['architecture', 'structure', 'design', 'coupling', 'module', 'dependency', 'overview'],
16
+ SecurityAgent: ['security', 'vulnerability', 'auth', 'injection', 'xss', 'csrf', 'secret', 'token'],
17
+ DocumentationAgent: ['document', 'docs', 'comment', 'readme', 'api', 'explain', 'describe'],
18
+ RefactorAgent: ['refactor', 'clean', 'improve', 'duplicate', 'simplify', 'rewrite', 'quality'],
19
+ PerformanceAgent: ['performance', 'slow', 'bottleneck', 'optimize', 'memory', 'leak', 'n+1', 'query'],
20
+ };
21
+
22
+ class TaskDecomposer {
23
+ /**
24
+ * Decompose a complex task into subtasks using
25
+ */
26
+ async decompose(task, sessionId = 'default') {
27
+ logger.info(`[Decomposer] Decomposing: "${task.slice(0, 80)}"`);
28
+
29
+ const response = await llm.chat({
30
+ model: llm.model('fast'),
31
+ max_tokens: 800,
32
+ system: `You are a task decomposition engine for a developer AI system.
33
+ Break the given task into 2-5 concrete, independent subtasks.
34
+ Each subtask should be assigned to ONE of these agents:
35
+ ${Object.entries(AGENT_CAPABILITIES).map(([a, k]) => `- ${a}: handles ${k.slice(0, 4).join(', ')} etc.`).join('\n')}
36
+
37
+ Return ONLY a JSON array:
38
+ [{"subtask": "...", "agent": "AgentName", "priority": 1, "rationale": "..."}]
39
+ Priority 1 = highest. No preamble, no markdown fences.`,
40
+ messages: [{ role: 'user', content: `Task: ${task}` }],
41
+ });
42
+
43
+ costTracker.record({
44
+ model: llm.model('fast'),
45
+ inputTokens: response.usage.input_tokens,
46
+ outputTokens: response.usage.output_tokens,
47
+ sessionId,
48
+ queryType: 'decompose',
49
+ });
50
+
51
+ try {
52
+ const subtasks = JSON.parse(response.content[0].text.trim());
53
+ logger.info(`[Decomposer] Generated ${subtasks.length} subtasks`);
54
+ return subtasks.sort((a, b) => a.priority - b.priority);
55
+ } catch {
56
+ // Fallback: single subtask routed by keyword matching
57
+ const agent = this._routeByKeyword(task);
58
+ return [{ subtask: task, agent, priority: 1, rationale: 'Keyword-based routing' }];
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Decompose AND execute all subtasks, optionally in parallel
64
+ */
65
+ async decomposeAndRun(task, options = {}) {
66
+ const { parallel = false, sessionId = 'default', maxSubtasks = 4 } = options;
67
+
68
+ const subtasks = await this.decompose(task, sessionId);
69
+ const limited = subtasks.slice(0, maxSubtasks);
70
+
71
+ logger.info(`[Decomposer] Executing ${limited.length} subtasks (parallel=${parallel})`);
72
+
73
+ // Retrieve shared context for all subtasks up front
74
+ const sharedDocs = indexer.search(task, 10);
75
+
76
+ const runSubtask = async (subtask) => {
77
+ const agents = require('./specialists');
78
+ const agent = agents[subtask.agent];
79
+
80
+ if (!agent) {
81
+ logger.warn(`[Decomposer] Unknown agent: ${subtask.agent}, using DebugAgent`);
82
+ return { subtask: subtask.subtask, agent: subtask.agent, error: 'Agent not found' };
83
+ }
84
+
85
+ // Get subtask-specific context on top of shared context
86
+ const specificDocs = indexer.search(subtask.subtask, 5);
87
+ const allDocs = this._mergeDedupe(sharedDocs, specificDocs);
88
+
89
+ logger.info(`[Decomposer] → ${subtask.agent}: "${subtask.subtask.slice(0, 50)}"`);
90
+
91
+ try {
92
+ const result = await agent.run(subtask.subtask, { context: allDocs, sessionId });
93
+ return {
94
+ subtask: subtask.subtask,
95
+ agent: subtask.agent,
96
+ rationale: subtask.rationale,
97
+ priority: subtask.priority,
98
+ result: result.answer,
99
+ toolResults: result.toolResults,
100
+ contextChunks: result.contextChunks,
101
+ };
102
+ } catch (err) {
103
+ logger.error(`[Decomposer] ${subtask.agent} failed: ${err.message}`);
104
+ return { subtask: subtask.subtask, agent: subtask.agent, error: err.message };
105
+ }
106
+ };
107
+
108
+ let results;
109
+ if (parallel) {
110
+ results = await Promise.all(limited.map(runSubtask));
111
+ } else {
112
+ results = [];
113
+ for (const subtask of limited) {
114
+ results.push(await runSubtask(subtask));
115
+ }
116
+ }
117
+
118
+ // Synthesize all results into a coherent final answer
119
+ const synthesis = await this._synthesize(task, results, sessionId);
120
+
121
+ return {
122
+ originalTask: task,
123
+ subtasks: limited,
124
+ results,
125
+ synthesis,
126
+ agentsUsed: [...new Set(limited.map(s => s.agent))],
127
+ };
128
+ }
129
+
130
+ /**
131
+ * Synthesize multiple agent results into one coherent answer
132
+ */
133
+ async _synthesize(originalTask, results, sessionId) {
134
+ const successful = results.filter(r => !r.error);
135
+ if (successful.length === 0) return 'All subtasks failed.';
136
+ if (successful.length === 1) return successful[0].result;
137
+
138
+ const parts = successful.map(r =>
139
+ `## ${r.agent} (re: ${r.subtask.slice(0, 60)})\n${r.result}`
140
+ ).join('\n\n---\n\n');
141
+
142
+ const response = await llm.chat({
143
+ model: llm.model('fast'),
144
+ max_tokens: 1000,
145
+ messages: [{
146
+ role: 'user',
147
+ content: `Original task: ${originalTask}\n\nAgent results:\n${parts}\n\nSynthesize these into ONE clear, developer-focused answer. Remove duplication. Keep all concrete details (file names, line numbers, code). Be concise.`,
148
+ }],
149
+ });
150
+
151
+ costTracker.record({
152
+ model: llm.model('fast'),
153
+ inputTokens: response.usage.input_tokens,
154
+ outputTokens: response.usage.output_tokens,
155
+ sessionId,
156
+ queryType: 'synthesize',
157
+ });
158
+
159
+ return response.content[0].text;
160
+ }
161
+
162
+ _routeByKeyword(task) {
163
+ const lower = task.toLowerCase();
164
+ for (const [agent, keywords] of Object.entries(AGENT_CAPABILITIES)) {
165
+ if (keywords.some(k => lower.includes(k))) return agent;
166
+ }
167
+ return 'DebugAgent'; // default
168
+ }
169
+
170
+ _mergeDedupe(a, b) {
171
+ const seen = new Set(a.map(d => d.id));
172
+ return [...a, ...b.filter(d => !seen.has(d.id))];
173
+ }
174
+ }
175
+
176
+ module.exports = new TaskDecomposer();
@@ -0,0 +1,153 @@
1
+ 'use strict';
2
+ /**
3
+ * 10 named agent teams covering every common dev workflow.
4
+ * Teams run sequentially (each agent sees prior results) or in parallel.
5
+ */
6
+
7
+ const llm = require('../utils/llmClient');
8
+ const costTracker = require('../utils/costTracker');
9
+ const indexer = require('../core/indexer');
10
+ const { MemoryManager } = require('../memory/memoryManager');
11
+ const logger = require('../utils/logger');
12
+
13
+ const TEAMS = {
14
+ 'full-audit': {
15
+ desc: 'Complete codebase audit: security + performance + quality + docs',
16
+ agents: ['SecurityAgent', 'PerformanceAgent', 'RefactorAgent', 'DocumentationAgent'],
17
+ sequential: true,
18
+ },
19
+ 'feature-review': {
20
+ desc: 'Review a new feature: architecture + security + tests + docs',
21
+ agents: ['ArchitectureAgent', 'SecurityAgent', 'TestAgent', 'DocumentationAgent'],
22
+ sequential: true,
23
+ },
24
+ 'bug-triage': {
25
+ desc: 'Triage a production bug: debug + perf impact + doc fix',
26
+ agents: ['DebugAgent', 'PerformanceAgent', 'DocumentationAgent'],
27
+ sequential: true,
28
+ },
29
+ 'onboarding': {
30
+ desc: 'Onboard a new developer: architecture + patterns + gotchas + tasks',
31
+ agents: ['ArchitectureAgent', 'DocumentationAgent', 'DebugAgent', 'PlannerAgent'],
32
+ sequential: false,
33
+ },
34
+ 'refactor-safe': {
35
+ desc: 'Plan a safe refactor: architecture + refactor + tests + security check',
36
+ agents: ['ArchitectureAgent', 'RefactorAgent', 'TestAgent', 'SecurityAgent'],
37
+ sequential: true,
38
+ },
39
+ 'release-prep': {
40
+ desc: 'Prepare a release: changelog + security + tests + deployment check',
41
+ agents: ['DocumentationAgent', 'SecurityAgent', 'TestAgent', 'DevOpsAgent'],
42
+ sequential: true,
43
+ },
44
+ 'data-audit': {
45
+ desc: 'Audit data layer: schemas + validation + query performance + security',
46
+ agents: ['DataAgent', 'SecurityAgent', 'PerformanceAgent'],
47
+ sequential: true,
48
+ },
49
+ 'ci-setup': {
50
+ desc: 'Set up or fix CI/CD: devops analysis + tests + linting + security',
51
+ agents: ['DevOpsAgent', 'TestAgent', 'SecurityAgent'],
52
+ sequential: true,
53
+ },
54
+ 'post-mortem': {
55
+ desc: 'Incident post-mortem: debug root cause + perf timeline + prevention plan',
56
+ agents: ['DebugAgent', 'PerformanceAgent', 'PlannerAgent', 'DocumentationAgent'],
57
+ sequential: true,
58
+ },
59
+ 'greenfield': {
60
+ desc: 'Plan a new feature or service from scratch: architecture + security + plan',
61
+ agents: ['ArchitectureAgent', 'SecurityAgent', 'PlannerAgent', 'TestAgent'],
62
+ sequential: true,
63
+ },
64
+ };
65
+
66
+ class TeamCoordinator {
67
+ async runTeam(teamName, task, opts = {}) {
68
+ const team = TEAMS[teamName];
69
+ if (!team) throw new Error(`Unknown team: ${teamName}. Available: ${Object.keys(TEAMS).join(', ')}`);
70
+ return this._run(team, task, { ...opts, teamName });
71
+ }
72
+
73
+ async runCustomTeam(agentNames, task, opts = {}) {
74
+ return this._run({ agents: agentNames, sequential: opts.sequential !== false, desc: 'Custom team' }, task, { ...opts, teamName: 'custom' });
75
+ }
76
+
77
+ async autoRun(task, opts = {}) {
78
+ const name = this._selectTeam(task);
79
+ logger.info(`[Team] Auto-selected: ${name}`);
80
+ return this.runTeam(name, task, opts);
81
+ }
82
+
83
+ async _run(team, task, opts = {}) {
84
+ const { sessionId = 'default', teamName = 'custom' } = opts;
85
+ const agents = require('./specialists');
86
+ const sharedCtx = indexer.search(task, 12);
87
+ const memories = MemoryManager.getRelevant(task, 5);
88
+ const memCtx = MemoryManager.formatAsContext(memories);
89
+
90
+ const results = [];
91
+ let priorSummary = '';
92
+
93
+ const runAgent = async (agentName, idx) => {
94
+ const agent = agents[agentName];
95
+ if (!agent) return { agent: agentName, error: 'Not found' };
96
+ const taskWithPrior = team.sequential && priorSummary
97
+ ? `${task}\n\n## Prior agent findings — build on these, don't repeat:\n${priorSummary}`
98
+ : task;
99
+ logger.info(`[Team:${teamName}] [${idx + 1}/${team.agents.length}] ${agentName}`);
100
+ try {
101
+ const r = await agent.run(taskWithPrior, { context: sharedCtx, extraSystem: memCtx, sessionId });
102
+ if (team.sequential) priorSummary += `\n\n### ${agentName}:\n${r.answer.slice(0, 600)}`;
103
+ return { agent: agentName, answer: r.answer, loops: r.loops, tools: r.toolResults?.length || 0 };
104
+ } catch (err) {
105
+ logger.error(`[Team:${teamName}] ${agentName} failed: ${err.message}`);
106
+ return { agent: agentName, error: err.message };
107
+ }
108
+ };
109
+
110
+ if (team.sequential) {
111
+ for (let i = 0; i < team.agents.length; i++) results.push(await runAgent(team.agents[i], i));
112
+ } else {
113
+ results.push(...await Promise.all(team.agents.map((a, i) => runAgent(a, i))));
114
+ }
115
+
116
+ const report = await this._consolidate(task, results, teamName, sessionId);
117
+ return { team: teamName, task, results, report, sequential: team.sequential, agentsRun: team.agents };
118
+ }
119
+
120
+ async _consolidate(task, results, teamName, sessionId) {
121
+ const successful = results.filter(r => !r.error);
122
+ if (!successful.length) return 'All agents failed.';
123
+ if (successful.length === 1) return successful[0].answer;
124
+
125
+ const parts = successful.map(r => `### ${r.agent}\n${r.answer}`).join('\n\n---\n\n');
126
+ const response = await llm.chat({
127
+ model: llm.model('fast'), max_tokens: 1200,
128
+ messages: [{ role: 'user', content: `Consolidate this multi-agent team report into a final developer summary.\n\nTask: ${task}\nTeam: ${teamName}\n\n${parts}\n\nWrite:\n## Executive Summary (3-4 sentences)\n## Critical Findings (numbered, most important first)\n## Action Items (concrete next steps)\n## Agent Breakdown (which agent found what)\n\nBe specific. Include file names and line references. No fluff.` }],
129
+ });
130
+ costTracker.record({ model: llm.model('fast'), inputTokens: response.usage.input_tokens, outputTokens: response.usage.output_tokens, sessionId, queryType: 'team-consolidate' });
131
+ return response.content[0].text;
132
+ }
133
+
134
+ _selectTeam(task) {
135
+ const t = task.toLowerCase();
136
+ if (/incident|outage|down|crash|post.?mortem/i.test(t)) return 'post-mortem';
137
+ if (/bug|error|exception|fail|crash/i.test(t)) return 'bug-triage';
138
+ if (/release|deploy|ship|version|changelog/i.test(t)) return 'release-prep';
139
+ if (/onboard|new dev|understand|explain codebase/i.test(t)) return 'onboarding';
140
+ if (/refactor|clean|restructure/i.test(t)) return 'refactor-safe';
141
+ if (/data|schema|model|database|query/i.test(t)) return 'data-audit';
142
+ if (/ci|cd|pipeline|docker|deploy/i.test(t)) return 'ci-setup';
143
+ if (/new (feature|service|module|system)/i.test(t)) return 'greenfield';
144
+ if (/audit|scan|review all|security/i.test(t)) return 'full-audit';
145
+ return 'feature-review';
146
+ }
147
+
148
+ getTeams() {
149
+ return Object.entries(TEAMS).map(([name, t]) => ({ name, description: t.desc, agents: t.agents, sequential: t.sequential }));
150
+ }
151
+ }
152
+
153
+ module.exports = new TeamCoordinator();
@@ -0,0 +1,172 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const specialists = require('../../agents/specialists');
4
+ const taskDecomposer = require('../../agents/taskDecomposer');
5
+ const teamCoordinator = require('../../agents/teamCoordinator');
6
+ const dreamer = require('../../agents/dreamer');
7
+ const improver = require('../../agents/improver');
8
+ const indexer = require('../../core/indexer');
9
+ const logger = require('../../utils/logger');
10
+
11
+ /** POST /api/agents/:name — run a specialist agent directly */
12
+ router.post('/:name', async (req, res) => {
13
+ const { name } = req.params;
14
+ const { task, topK = 8, sessionId } = req.body;
15
+ if (!task) return res.status(400).json({ error: 'task is required' });
16
+
17
+ const agentKey = Object.keys(specialists).find(
18
+ k => k.toLowerCase() === name.toLowerCase() || k.toLowerCase().replace('agent', '') === name.toLowerCase()
19
+ );
20
+ const agent = specialists[agentKey];
21
+ if (!agent) {
22
+ return res.status(404).json({
23
+ error: `Agent not found: ${name}`,
24
+ available: Object.keys(specialists),
25
+ });
26
+ }
27
+
28
+ try {
29
+ const context = indexer.search(task, topK);
30
+ const result = await agent.run(task, { context, sessionId });
31
+ res.json(result);
32
+ } catch (err) {
33
+ logger.error(`Agent ${name} error: ${err.message}`);
34
+ res.status(500).json({ error: err.message });
35
+ }
36
+ });
37
+
38
+ /** GET /api/agents — list all available agents */
39
+ router.get('/', (req, res) => {
40
+ res.json({
41
+ agents: Object.keys(specialists).map(name => ({
42
+ name,
43
+ role: specialists[name].role,
44
+ model: specialists[name].model,
45
+ stats: specialists[name].getStats(),
46
+ })),
47
+ });
48
+ });
49
+
50
+ /** POST /api/agents/:name/reset — reset agent history */
51
+ router.post('/:name/reset', (req, res) => {
52
+ const agent = specialists[req.params.name];
53
+ if (!agent) return res.status(404).json({ error: 'Agent not found' });
54
+ agent.reset();
55
+ res.json({ success: true });
56
+ });
57
+
58
+ // ── DECOMPOSER ──────────────────────────────────────────────────────────────────
59
+
60
+ /** POST /api/agents/decompose/run — decompose and run a complex task */
61
+ router.post('/decompose/run', async (req, res) => {
62
+ const { task, parallel = false, sessionId, maxSubtasks = 4 } = req.body;
63
+ if (!task) return res.status(400).json({ error: 'task is required' });
64
+ try {
65
+ const result = await taskDecomposer.decomposeAndRun(task, { parallel, sessionId, maxSubtasks });
66
+ res.json(result);
67
+ } catch (err) {
68
+ res.status(500).json({ error: err.message });
69
+ }
70
+ });
71
+
72
+ /** POST /api/agents/decompose/plan — decompose only, no execution */
73
+ router.post('/decompose/plan', async (req, res) => {
74
+ const { task, sessionId } = req.body;
75
+ if (!task) return res.status(400).json({ error: 'task is required' });
76
+ try {
77
+ const subtasks = await taskDecomposer.decompose(task, sessionId);
78
+ res.json({ task, subtasks });
79
+ } catch (err) {
80
+ res.status(500).json({ error: err.message });
81
+ }
82
+ });
83
+
84
+ // ── TEAMS ───────────────────────────────────────────────────────────────────────
85
+
86
+ /** GET /api/agents/teams/list */
87
+ router.get('/teams/list', (req, res) => {
88
+ res.json({ teams: teamCoordinator.getAvailableTeams() });
89
+ });
90
+
91
+ /** POST /api/agents/teams/:name — run a named team */
92
+ router.post('/teams/:name', async (req, res) => {
93
+ const { task, sessionId } = req.body;
94
+ if (!task) return res.status(400).json({ error: 'task is required' });
95
+ try {
96
+ const result = await teamCoordinator.runTeam(req.params.name, task, { sessionId });
97
+ res.json(result);
98
+ } catch (err) {
99
+ res.status(500).json({ error: err.message });
100
+ }
101
+ });
102
+
103
+ /** POST /api/agents/teams/auto — auto-select and run the best team */
104
+ router.post('/teams/auto', async (req, res) => {
105
+ const { task, sessionId } = req.body;
106
+ if (!task) return res.status(400).json({ error: 'task is required' });
107
+ try {
108
+ const result = await teamCoordinator.autoRun(task, { sessionId });
109
+ res.json(result);
110
+ } catch (err) {
111
+ res.status(500).json({ error: err.message });
112
+ }
113
+ });
114
+
115
+ /** POST /api/agents/teams/custom — run a custom team */
116
+ router.post('/teams/custom', async (req, res) => {
117
+ const { agents: agentNames, task, sessionId, sequential = true } = req.body;
118
+ if (!task || !agentNames?.length) return res.status(400).json({ error: 'task and agents[] are required' });
119
+ try {
120
+ const result = await teamCoordinator.runCustomTeam(agentNames, task, { sessionId, sequential });
121
+ res.json(result);
122
+ } catch (err) {
123
+ res.status(500).json({ error: err.message });
124
+ }
125
+ });
126
+
127
+ // ── DREAMER ─────────────────────────────────────────────────────────────────────
128
+
129
+ /** GET /api/agents/dreamer/status */
130
+ router.get('/dreamer/status', (req, res) => {
131
+ res.json(dreamer.getStatus());
132
+ });
133
+
134
+ /** POST /api/agents/dreamer/start — start background dreaming */
135
+ router.post('/dreamer/start', (req, res) => {
136
+ const { intervalMinutes = 30 } = req.body;
137
+ dreamer.start(intervalMinutes);
138
+ res.json({ success: true, message: `Dreamer started (every ${intervalMinutes} min)` });
139
+ });
140
+
141
+ /** POST /api/agents/dreamer/stop */
142
+ router.post('/dreamer/stop', (req, res) => {
143
+ dreamer.stop();
144
+ res.json({ success: true });
145
+ });
146
+
147
+ /** POST /api/agents/dreamer/now — run a dream cycle immediately */
148
+ router.post('/dreamer/now', async (req, res) => {
149
+ try {
150
+ const result = await dreamer.dreamNow();
151
+ res.json({ success: true, result });
152
+ } catch (err) {
153
+ res.status(500).json({ error: err.message });
154
+ }
155
+ });
156
+
157
+ // ── IMPROVER ────────────────────────────────────────────────────────────────────
158
+
159
+ /** GET /api/agents/improver/summary */
160
+ router.get('/improver/summary', (req, res) => {
161
+ res.json(improver.getSummary());
162
+ });
163
+
164
+ /** POST /api/agents/improver/feedback */
165
+ router.post('/improver/feedback', (req, res) => {
166
+ const { queryId, rating, comment } = req.body;
167
+ if (!queryId || rating === undefined) return res.status(400).json({ error: 'queryId and rating required' });
168
+ const entry = improver.recordFeedback(queryId, rating, comment);
169
+ res.json({ success: true, entry });
170
+ });
171
+
172
+ module.exports = router;
@@ -0,0 +1,115 @@
1
+ 'use strict';
2
+ const express = require('express');
3
+ const logger = require('../../utils/logger');
4
+
5
+ // ── TOOL REGISTRY ROUTES ──────────────────────────────────────────────────────
6
+ const toolsRegistryRouter = express.Router();
7
+ const registry = require('../../tools/registry');
8
+
9
+ toolsRegistryRouter.get('/', (req, res) => {
10
+ res.json({ count: registry.count, tools: registry.list() });
11
+ });
12
+
13
+ toolsRegistryRouter.get('/groups', (req, res) => {
14
+ const groups = {};
15
+ for (const t of registry.list()) {
16
+ (groups[t.group] = groups[t.group] || []).push(t.name);
17
+ }
18
+ res.json(groups);
19
+ });
20
+
21
+ toolsRegistryRouter.post('/execute', async (req, res) => {
22
+ const { tool, input } = req.body;
23
+ if (!tool || !input) return res.status(400).json({ error: 'tool and input required' });
24
+ try {
25
+ const result = await registry.execute(tool, input);
26
+ res.json({ tool, result });
27
+ } catch (e) {
28
+ res.status(500).json({ error: e.message });
29
+ }
30
+ });
31
+
32
+ module.exports.toolsRegistryRouter = toolsRegistryRouter;
33
+
34
+ // ── PROMPT ENGINEERING ROUTES ─────────────────────────────────────────────────
35
+ const promptsRouter = express.Router();
36
+ const pe = require('../../prompts/promptEngineer');
37
+
38
+ promptsRouter.get('/templates', (req, res) => {
39
+ res.json({ templates: pe.listTemplates() });
40
+ });
41
+ promptsRouter.get('/templates/:name', (req, res) => {
42
+ const t = pe.getTemplate(req.params.name);
43
+ if (!t) return res.status(404).json({ error: 'Template not found' });
44
+ res.json(t);
45
+ });
46
+ promptsRouter.post('/templates', (req, res) => {
47
+ const { name, description, template, variables } = req.body;
48
+ if (!name || !template) return res.status(400).json({ error: 'name and template required' });
49
+ try { res.json(pe.saveTemplate(name, description, template, variables)); }
50
+ catch (e) { res.status(400).json({ error: e.message }); }
51
+ });
52
+ promptsRouter.delete('/templates/:name', (req, res) => {
53
+ try { res.json({ success: pe.deleteTemplate(req.params.name) }); }
54
+ catch (e) { res.status(400).json({ error: e.message }); }
55
+ });
56
+ promptsRouter.post('/apply', (req, res) => {
57
+ const { template, variables } = req.body;
58
+ if (!template) return res.status(400).json({ error: 'template required' });
59
+ try { res.json({ result: pe.applyTemplate(template, variables || {}) }); }
60
+ catch (e) { res.status(400).json({ error: e.message }); }
61
+ });
62
+ promptsRouter.post('/cot', (req, res) => {
63
+ const { prompt, style } = req.body;
64
+ if (!prompt) return res.status(400).json({ error: 'prompt required' });
65
+ res.json({ result: pe.injectCoT(prompt, style) });
66
+ });
67
+ promptsRouter.post('/analyse', async (req, res) => {
68
+ const { prompt } = req.body;
69
+ if (!prompt) return res.status(400).json({ error: 'prompt required' });
70
+ try { res.json(await pe.analyse(prompt, req.body)); }
71
+ catch (e) { res.status(500).json({ error: e.message }); }
72
+ });
73
+ promptsRouter.post('/improve', async (req, res) => {
74
+ const { prompt, goal, style } = req.body;
75
+ if (!prompt) return res.status(400).json({ error: 'prompt required' });
76
+ try { res.json(await pe.improve(prompt, { goal, style, sessionId: req.body.sessionId })); }
77
+ catch (e) { res.status(500).json({ error: e.message }); }
78
+ });
79
+ promptsRouter.post('/generate', async (req, res) => {
80
+ const { task, type, model, includeExamples } = req.body;
81
+ if (!task) return res.status(400).json({ error: 'task required' });
82
+ try { res.json(await pe.generate(task, { type, model, includeExamples, sessionId: req.body.sessionId })); }
83
+ catch (e) { res.status(500).json({ error: e.message }); }
84
+ });
85
+ promptsRouter.post('/ab-test', async (req, res) => {
86
+ const { promptA, promptB, testInput } = req.body;
87
+ if (!promptA || !promptB || !testInput) return res.status(400).json({ error: 'promptA, promptB, testInput required' });
88
+ try { res.json(await pe.abTest(promptA, promptB, testInput, req.body)); }
89
+ catch (e) { res.status(500).json({ error: e.message }); }
90
+ });
91
+
92
+ module.exports.promptsRouter = promptsRouter;
93
+
94
+ // ── COMPACTOR ROUTES ──────────────────────────────────────────────────────────
95
+ const compactorRouter = express.Router();
96
+ const compactor = require('../../context/compactor');
97
+
98
+ compactorRouter.post('/compact', async (req, res) => {
99
+ const { messages, sessionId, force } = req.body;
100
+ if (!messages?.length) return res.status(400).json({ error: 'messages array required' });
101
+ try { res.json(await compactor.compact(messages, { sessionId, force })); }
102
+ catch (e) { res.status(500).json({ error: e.message }); }
103
+ });
104
+ compactorRouter.post('/deep-compact', async (req, res) => {
105
+ const { messages, sessionId } = req.body;
106
+ if (!messages?.length) return res.status(400).json({ error: 'messages array required' });
107
+ try { res.json(await compactor.deepCompact(messages, { sessionId })); }
108
+ catch (e) { res.status(500).json({ error: e.message }); }
109
+ });
110
+ compactorRouter.post('/check', (req, res) => {
111
+ const { messages } = req.body;
112
+ res.json(compactor.needsCompaction(messages || []));
113
+ });
114
+
115
+ module.exports.compactorRouter = compactorRouter;