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.
- package/.env.example +23 -55
- package/README.md +609 -219
- package/cli.js +486 -160
- package/package.json +2 -2
- package/src/agents/BaseAgent.js +113 -0
- package/src/agents/dreamer.js +165 -0
- package/src/agents/improver.js +175 -0
- package/src/agents/specialists.js +202 -0
- package/src/agents/taskDecomposer.js +176 -0
- package/src/agents/teamCoordinator.js +153 -0
- package/src/api/routes/agents.js +172 -0
- package/src/api/routes/extras.js +115 -0
- package/src/api/routes/git.js +72 -0
- package/src/api/routes/ingest.js +60 -40
- package/src/api/routes/knowledge.js +59 -41
- package/src/api/routes/memory.js +41 -0
- package/src/api/routes/newRoutes.js +168 -0
- package/src/api/routes/pipelines.js +41 -0
- package/src/api/routes/planner.js +54 -0
- package/src/api/routes/query.js +24 -0
- package/src/api/routes/sessions.js +54 -0
- package/src/api/routes/tasks.js +67 -0
- package/src/api/routes/tools.js +85 -0
- package/src/api/routes/v5routes.js +196 -0
- package/src/api/server.js +133 -5
- package/src/context/compactor.js +151 -0
- package/src/context/contextEngineer.js +181 -0
- package/src/context/contextVisualizer.js +140 -0
- package/src/core/conversationEngine.js +231 -0
- package/src/core/indexer.js +169 -143
- package/src/core/ingester.js +141 -126
- package/src/core/queryEngine.js +286 -236
- package/src/cron/cronScheduler.js +260 -0
- package/src/dashboard/index.html +1181 -0
- package/src/lsp/symbolNavigator.js +220 -0
- package/src/memory/memoryManager.js +186 -0
- package/src/memory/teamMemory.js +111 -0
- package/src/messaging/messageBus.js +177 -0
- package/src/monitor/proactiveMonitor.js +337 -0
- package/src/pipelines/pipelineEngine.js +230 -0
- package/src/planner/plannerEngine.js +202 -0
- package/src/plugins/builtin/stats-plugin.js +29 -0
- package/src/plugins/pluginManager.js +144 -0
- package/src/prompts/promptEngineer.js +289 -0
- package/src/sessions/sessionManager.js +166 -0
- package/src/skills/skillsManager.js +263 -0
- package/src/storage/store.js +127 -105
- package/src/tasks/taskManager.js +151 -0
- package/src/tools/BashTool.js +154 -0
- package/src/tools/FileEditTool.js +280 -0
- package/src/tools/GitTool.js +212 -0
- package/src/tools/GrepTool.js +199 -0
- package/src/tools/registry.js +1380 -0
- package/src/utils/costTracker.js +69 -0
- package/src/utils/fileParser.js +176 -153
- package/src/utils/llmClient.js +355 -206
- package/src/watcher/fileWatcher.js +137 -0
- package/src/worktrees/worktreeManager.js +176 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Schedule any agent task to run automatically on a cron schedule.
|
|
4
|
+
* Results are saved to memory + create tasks for any action items found.
|
|
5
|
+
*
|
|
6
|
+
* Schedule format: standard cron (minute hour day month weekday)
|
|
7
|
+
* Examples:
|
|
8
|
+
* "0 9 * * 1-5" — 9am weekdays
|
|
9
|
+
* "0 0 * * *" — midnight daily
|
|
10
|
+
* "0/30 * * * *" — every 30 minutes
|
|
11
|
+
* "@daily" — once a day
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const logger = require('../utils/logger');
|
|
17
|
+
|
|
18
|
+
const CRON_FILE = path.join(process.cwd(), 'data', 'cron-jobs.json');
|
|
19
|
+
const RUN_LOG = path.join(process.cwd(), 'data', 'cron-runs.json');
|
|
20
|
+
|
|
21
|
+
// Minimal cron parser — converts cron expr to ms interval
|
|
22
|
+
function parseCronInterval(schedule) {
|
|
23
|
+
const presets = {
|
|
24
|
+
'@hourly': 3600000,
|
|
25
|
+
'@daily': 86400000,
|
|
26
|
+
'@weekly': 604800000,
|
|
27
|
+
'@monthly': 2592000000,
|
|
28
|
+
};
|
|
29
|
+
if (presets[schedule]) return presets[schedule];
|
|
30
|
+
|
|
31
|
+
// For simplicity, support "every N minutes" patterns
|
|
32
|
+
const parts = schedule.split(' ');
|
|
33
|
+
if (parts.length === 5) {
|
|
34
|
+
const minute = parts[0];
|
|
35
|
+
if (minute.startsWith('*/')) return parseInt(minute.slice(2)) * 60000;
|
|
36
|
+
if (minute.startsWith('0/')) return parseInt(minute.slice(2)) * 60000;
|
|
37
|
+
// Default: run once per day
|
|
38
|
+
return 86400000;
|
|
39
|
+
}
|
|
40
|
+
return 3600000; // fallback: hourly
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
class CronScheduler {
|
|
44
|
+
constructor() {
|
|
45
|
+
this._jobs = this._loadJobs();
|
|
46
|
+
this._handles = new Map(); // jobName → setInterval handle
|
|
47
|
+
this._running = false;
|
|
48
|
+
this._runLog = this._loadRunLog();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
_loadJobs() {
|
|
52
|
+
try { if (fs.existsSync(CRON_FILE)) return JSON.parse(fs.readFileSync(CRON_FILE, 'utf-8')); } catch { }
|
|
53
|
+
return {};
|
|
54
|
+
}
|
|
55
|
+
_saveJobs() { fs.writeFileSync(CRON_FILE, JSON.stringify(this._jobs, null, 2)); }
|
|
56
|
+
|
|
57
|
+
_loadRunLog() {
|
|
58
|
+
try { if (fs.existsSync(RUN_LOG)) return JSON.parse(fs.readFileSync(RUN_LOG, 'utf-8')); } catch { }
|
|
59
|
+
return { runs: [] };
|
|
60
|
+
}
|
|
61
|
+
_saveRunLog() {
|
|
62
|
+
if (this._runLog.runs.length > 200) this._runLog.runs = this._runLog.runs.slice(-200);
|
|
63
|
+
fs.writeFileSync(RUN_LOG, JSON.stringify(this._runLog, null, 2));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Create and register a new cron job
|
|
68
|
+
*/
|
|
69
|
+
create(opts = {}) {
|
|
70
|
+
const {
|
|
71
|
+
name,
|
|
72
|
+
schedule,
|
|
73
|
+
task,
|
|
74
|
+
agent = 'DebugAgent',
|
|
75
|
+
enabled = true,
|
|
76
|
+
description = '',
|
|
77
|
+
team, // use a team instead of single agent
|
|
78
|
+
pipeline, // use a pipeline instead
|
|
79
|
+
} = opts;
|
|
80
|
+
|
|
81
|
+
if (!name || !schedule || !task) throw new Error('name, schedule, task are required');
|
|
82
|
+
if (this._jobs[name]) throw new Error(`Job "${name}" already exists`);
|
|
83
|
+
|
|
84
|
+
const job = {
|
|
85
|
+
name,
|
|
86
|
+
schedule,
|
|
87
|
+
task,
|
|
88
|
+
agent: team ? null : (pipeline ? null : agent),
|
|
89
|
+
team: team || null,
|
|
90
|
+
pipeline: pipeline || null,
|
|
91
|
+
description,
|
|
92
|
+
enabled,
|
|
93
|
+
createdAt: new Date().toISOString(),
|
|
94
|
+
lastRun: null,
|
|
95
|
+
lastStatus: null,
|
|
96
|
+
runCount: 0,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
this._jobs[name] = job;
|
|
100
|
+
this._saveJobs();
|
|
101
|
+
|
|
102
|
+
if (enabled && this._running) {
|
|
103
|
+
this._schedule(job);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
logger.info(`[Cron] Created: ${name} (${schedule}) → ${agent || team || pipeline}`);
|
|
107
|
+
return job;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Start the scheduler — register all enabled jobs
|
|
112
|
+
*/
|
|
113
|
+
start() {
|
|
114
|
+
if (this._running) return;
|
|
115
|
+
this._running = true;
|
|
116
|
+
let scheduled = 0;
|
|
117
|
+
for (const job of Object.values(this._jobs)) {
|
|
118
|
+
if (job.enabled) { this._schedule(job); scheduled++; }
|
|
119
|
+
}
|
|
120
|
+
logger.info(`[Cron] ⏰ Started: ${scheduled} job(s) scheduled`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
stop() {
|
|
124
|
+
for (const handle of this._handles.values()) clearInterval(handle);
|
|
125
|
+
this._handles.clear();
|
|
126
|
+
this._running = false;
|
|
127
|
+
logger.info('[Cron] Stopped');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
_schedule(job) {
|
|
131
|
+
if (this._handles.has(job.name)) return;
|
|
132
|
+
const intervalMs = parseCronInterval(job.schedule);
|
|
133
|
+
const handle = setInterval(() => this._run(job), intervalMs);
|
|
134
|
+
this._handles.set(job.name, handle);
|
|
135
|
+
logger.info(`[Cron] Scheduled: ${job.name} every ${intervalMs / 60000}min`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Run a job immediately (manual trigger)
|
|
140
|
+
*/
|
|
141
|
+
async runNow(name) {
|
|
142
|
+
const job = this._jobs[name];
|
|
143
|
+
if (!job) throw new Error(`Job not found: ${name}`);
|
|
144
|
+
return this._run(job);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async _run(job) {
|
|
148
|
+
const startTime = Date.now();
|
|
149
|
+
logger.info(`[Cron] Running: ${job.name}`);
|
|
150
|
+
|
|
151
|
+
const runRecord = {
|
|
152
|
+
jobName: job.name,
|
|
153
|
+
startedAt: new Date().toISOString(),
|
|
154
|
+
status: 'running',
|
|
155
|
+
durationMs: 0,
|
|
156
|
+
result: null,
|
|
157
|
+
error: null,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
let result;
|
|
162
|
+
|
|
163
|
+
if (job.pipeline) {
|
|
164
|
+
const pipelineEngine = require('../pipelines/pipelineEngine');
|
|
165
|
+
result = await pipelineEngine.run(job.pipeline, { task: job.task });
|
|
166
|
+
runRecord.result = result.finalOutput?.answer?.slice(0, 300) || 'Pipeline complete';
|
|
167
|
+
} else if (job.team) {
|
|
168
|
+
const teamCoordinator = require('../agents/teamCoordinator');
|
|
169
|
+
result = await teamCoordinator.runTeam(job.team, job.task, { sessionId: `cron_${job.name}` });
|
|
170
|
+
runRecord.result = result.report?.slice(0, 300) || 'Team run complete';
|
|
171
|
+
} else {
|
|
172
|
+
const agents = require('../agents/specialists');
|
|
173
|
+
const agent = agents[job.agent] || agents['DebugAgent'];
|
|
174
|
+
const indexer = require('../core/indexer');
|
|
175
|
+
const context = indexer.search(job.task, 6);
|
|
176
|
+
result = await agent.run(job.task, { context, sessionId: `cron_${job.name}` });
|
|
177
|
+
runRecord.result = result.answer?.slice(0, 300);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
runRecord.status = 'success';
|
|
181
|
+
runRecord.durationMs = Date.now() - startTime;
|
|
182
|
+
|
|
183
|
+
// Update job metadata
|
|
184
|
+
this._jobs[job.name].lastRun = runRecord.startedAt;
|
|
185
|
+
this._jobs[job.name].lastStatus = 'success';
|
|
186
|
+
this._jobs[job.name].runCount = (this._jobs[job.name].runCount || 0) + 1;
|
|
187
|
+
this._saveJobs();
|
|
188
|
+
|
|
189
|
+
// Save outcome to memory for the dreamer
|
|
190
|
+
const { MemoryManager } = require('../memory/memoryManager');
|
|
191
|
+
MemoryManager.add(
|
|
192
|
+
`Cron job "${job.name}" result: ${runRecord.result}`,
|
|
193
|
+
'fact',
|
|
194
|
+
['cron', job.name]
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
} catch (err) {
|
|
198
|
+
runRecord.status = 'error';
|
|
199
|
+
runRecord.error = err.message;
|
|
200
|
+
runRecord.durationMs = Date.now() - startTime;
|
|
201
|
+
this._jobs[job.name].lastStatus = 'error';
|
|
202
|
+
this._saveJobs();
|
|
203
|
+
logger.error(`[Cron] ${job.name} failed: ${err.message}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
this._runLog.runs.push(runRecord);
|
|
207
|
+
this._saveRunLog();
|
|
208
|
+
return runRecord;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
update(name, updates = {}) {
|
|
212
|
+
const job = this._jobs[name];
|
|
213
|
+
if (!job) throw new Error(`Job not found: ${name}`);
|
|
214
|
+
const allowed = ['schedule', 'task', 'agent', 'enabled', 'description'];
|
|
215
|
+
for (const k of allowed) { if (updates[k] !== undefined) job[k] = updates[k]; }
|
|
216
|
+
this._saveJobs();
|
|
217
|
+
|
|
218
|
+
// Re-schedule if running state changed
|
|
219
|
+
if (updates.enabled === true && this._running && !this._handles.has(name)) {
|
|
220
|
+
this._schedule(job);
|
|
221
|
+
} else if (updates.enabled === false) {
|
|
222
|
+
clearInterval(this._handles.get(name));
|
|
223
|
+
this._handles.delete(name);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return job;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
delete(name) {
|
|
230
|
+
if (!this._jobs[name]) throw new Error(`Job not found: ${name}`);
|
|
231
|
+
clearInterval(this._handles.get(name));
|
|
232
|
+
this._handles.delete(name);
|
|
233
|
+
delete this._jobs[name];
|
|
234
|
+
this._saveJobs();
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
list() {
|
|
239
|
+
return Object.values(this._jobs);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
getRunHistory(name, limit = 20) {
|
|
243
|
+
return this._runLog.runs
|
|
244
|
+
.filter(r => !name || r.jobName === name)
|
|
245
|
+
.slice(-limit)
|
|
246
|
+
.reverse();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
getStats() {
|
|
250
|
+
const jobs = Object.values(this._jobs);
|
|
251
|
+
return {
|
|
252
|
+
total: jobs.length,
|
|
253
|
+
enabled: jobs.filter(j => j.enabled).length,
|
|
254
|
+
running: this._running,
|
|
255
|
+
totalRuns: this._runLog.runs.length,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
module.exports = new CronScheduler();
|