@agilemonster/mcp-server 1.0.12 → 1.0.14
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/dist/client.d.ts +19 -2
- package/dist/client.js +132 -2
- package/dist/server.js +193 -23
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AgileMonsterProjectConfig, AutonomousStepResponse, Board, BoardAgentConfig, Card, ContextResponse, DiscoveredProject, PulseResponse } from './types.js';
|
|
2
|
-
export declare const MCP_SERVER_VERSION = "1.0.
|
|
2
|
+
export declare const MCP_SERVER_VERSION = "1.0.14";
|
|
3
3
|
export declare class AgileMonsterClient {
|
|
4
4
|
private apiKey;
|
|
5
5
|
private baseUrl;
|
|
@@ -36,7 +36,20 @@ export declare class AgileMonsterClient {
|
|
|
36
36
|
projectName?: string;
|
|
37
37
|
projectPath?: string;
|
|
38
38
|
discoveredProjects?: DiscoveredProject[];
|
|
39
|
+
discoveredModels?: Record<string, {
|
|
40
|
+
default_model?: string | null;
|
|
41
|
+
models: Record<string, string> | string[];
|
|
42
|
+
}>;
|
|
39
43
|
}): Promise<PulseResponse>;
|
|
44
|
+
/**
|
|
45
|
+
* Discover live models installed/configured across the developer's local AI coding agents
|
|
46
|
+
* (Codex, Cursor, Claude Code, Hermes, Antigravity) so the Agile Monster app & website
|
|
47
|
+
* always display the real models available for each selected agent.
|
|
48
|
+
*/
|
|
49
|
+
discoverLocalAgentModels(): Record<string, {
|
|
50
|
+
default_model?: string | null;
|
|
51
|
+
models: Record<string, string>;
|
|
52
|
+
}>;
|
|
40
53
|
/**
|
|
41
54
|
* Scan the developer's local workspace roots (sibling folders in parent dev dir, ~/dev, ~/Projects, etc.)
|
|
42
55
|
* to discover real project folders so the web UI can auto-match and let the user pick from a dropdown.
|
|
@@ -51,7 +64,11 @@ export declare class AgileMonsterClient {
|
|
|
51
64
|
addProgress(cardId: string, provider: string, update: string, tokensUsed?: number, costUsd?: number): Promise<Card>;
|
|
52
65
|
recordSpend(cardId: string, provider: string, tokens: number, costUsd: number, note?: string): Promise<Card>;
|
|
53
66
|
setBudget(cardId: string, maxBudgetUsd?: number | null, maxTokens?: number | null): Promise<Card>;
|
|
54
|
-
submitReview(cardId: string, provider: string, summary: string, verificationSteps?: string, targetList?: string, assignToUserId?: string
|
|
67
|
+
submitReview(cardId: string, provider: string, summary: string, verificationSteps?: string, targetList?: string, assignToUserId?: string, extra?: {
|
|
68
|
+
diffText?: string;
|
|
69
|
+
logText?: string;
|
|
70
|
+
filesModified?: string;
|
|
71
|
+
}): Promise<Card>;
|
|
55
72
|
completeTask(cardId: string, provider: string, comment?: string): Promise<Card>;
|
|
56
73
|
blockTask(cardId: string, provider: string, comment?: string): Promise<Card>;
|
|
57
74
|
createFollowupTasks(cardId: string, provider: string, tasks: Array<{
|
package/dist/client.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { execSync } from 'node:child_process';
|
|
4
|
-
export const MCP_SERVER_VERSION = '1.0.
|
|
4
|
+
export const MCP_SERVER_VERSION = '1.0.14';
|
|
5
5
|
export class AgileMonsterClient {
|
|
6
6
|
apiKey = '';
|
|
7
7
|
baseUrl;
|
|
@@ -196,9 +196,136 @@ export class AgileMonsterClient {
|
|
|
196
196
|
project_path: options.projectPath,
|
|
197
197
|
mcp_version: MCP_SERVER_VERSION,
|
|
198
198
|
discovered_projects: options.discoveredProjects,
|
|
199
|
+
discovered_models: options.discoveredModels,
|
|
199
200
|
}),
|
|
200
201
|
});
|
|
201
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Discover live models installed/configured across the developer's local AI coding agents
|
|
205
|
+
* (Codex, Cursor, Claude Code, Hermes, Antigravity) so the Agile Monster app & website
|
|
206
|
+
* always display the real models available for each selected agent.
|
|
207
|
+
*/
|
|
208
|
+
discoverLocalAgentModels() {
|
|
209
|
+
const homedir = process.env.HOME || process.env.USERPROFILE || '';
|
|
210
|
+
const result = {};
|
|
211
|
+
if (!homedir)
|
|
212
|
+
return result;
|
|
213
|
+
// 1. Codex (~/.codex/models_cache.json & ~/.codex/config.toml)
|
|
214
|
+
try {
|
|
215
|
+
const codexCachePath = path.join(homedir, '.codex', 'models_cache.json');
|
|
216
|
+
const codexConfigPath = path.join(homedir, '.codex', 'config.toml');
|
|
217
|
+
let defaultCodex = null;
|
|
218
|
+
if (fs.existsSync(codexConfigPath)) {
|
|
219
|
+
const toml = fs.readFileSync(codexConfigPath, 'utf8');
|
|
220
|
+
const m = toml.match(/^\s*model\s*=\s*["']([^"']+)["']/m);
|
|
221
|
+
if (m?.[1])
|
|
222
|
+
defaultCodex = m[1].trim();
|
|
223
|
+
}
|
|
224
|
+
if (fs.existsSync(codexCachePath)) {
|
|
225
|
+
const raw = JSON.parse(fs.readFileSync(codexCachePath, 'utf8'));
|
|
226
|
+
const list = Array.isArray(raw?.models) ? raw.models : [];
|
|
227
|
+
const models = {};
|
|
228
|
+
for (const item of list) {
|
|
229
|
+
const slug = String(item?.slug || item?.id || '').trim();
|
|
230
|
+
if (!slug)
|
|
231
|
+
continue;
|
|
232
|
+
const label = String(item?.display_name || slug).trim();
|
|
233
|
+
models[slug] = label;
|
|
234
|
+
}
|
|
235
|
+
if (Object.keys(models).length > 0) {
|
|
236
|
+
result.codex = { default_model: defaultCodex, models };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch { }
|
|
241
|
+
// 2. Cursor (~/Library/Application Support/Cursor/User/globalStorage/state.vscdb)
|
|
242
|
+
try {
|
|
243
|
+
const cursorDb = path.join(homedir, 'Library', 'Application Support', 'Cursor', 'User', 'globalStorage', 'state.vscdb');
|
|
244
|
+
if (fs.existsSync(cursorDb)) {
|
|
245
|
+
const out = execSync(`sqlite3 "${cursorDb}" "SELECT value FROM ItemTable WHERE key = 'src.vs.platform.reactivestorage.browser.reactiveStorageServiceImpl.persistentStorage.applicationUser' LIMIT 1;"`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 4000 }).trim();
|
|
246
|
+
if (out) {
|
|
247
|
+
const data = JSON.parse(out);
|
|
248
|
+
const aiSettings = data?.aiSettings || {};
|
|
249
|
+
const defaultCursor = aiSettings?.modelConfig?.composer?.modelName ||
|
|
250
|
+
aiSettings?.openAIModel ||
|
|
251
|
+
null;
|
|
252
|
+
const avail = Array.isArray(data?.availableDefaultModels2) ? data.availableDefaultModels2 : [];
|
|
253
|
+
const models = {};
|
|
254
|
+
for (const m of avail) {
|
|
255
|
+
const name = String(m?.name || '').trim();
|
|
256
|
+
if (!name)
|
|
257
|
+
continue;
|
|
258
|
+
const label = String(m?.clientDisplayName || m?.serverModelName || name).trim();
|
|
259
|
+
models[name] = label;
|
|
260
|
+
}
|
|
261
|
+
if (Object.keys(models).length > 0) {
|
|
262
|
+
result.cursor = { default_model: defaultCursor, models };
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
catch { }
|
|
268
|
+
// 3. Claude Code (~/.claude.json)
|
|
269
|
+
try {
|
|
270
|
+
const claudePath = path.join(homedir, '.claude.json');
|
|
271
|
+
if (fs.existsSync(claudePath)) {
|
|
272
|
+
const data = JSON.parse(fs.readFileSync(claudePath, 'utf8'));
|
|
273
|
+
const usageKeys = Object.keys(data?.CachedGrowthBookFeatures?.tengu_model_config || {});
|
|
274
|
+
const models = {
|
|
275
|
+
'claude-opus-5-5': 'Claude Opus 5.5',
|
|
276
|
+
'claude-sonnet-5-5': 'Claude Sonnet 5.5',
|
|
277
|
+
'claude-haiku-5-5': 'Claude Haiku 5.5',
|
|
278
|
+
'claude-opus-5': 'Claude Opus 5',
|
|
279
|
+
'claude-sonnet-5': 'Claude Sonnet 5',
|
|
280
|
+
'claude-fable-5-1': 'Claude Fable 5.1',
|
|
281
|
+
'claude-opus-4-8': 'Claude Opus 4.8',
|
|
282
|
+
'claude-opus-4-7': 'Claude Opus 4.7',
|
|
283
|
+
'claude-opus-4-6': 'Claude Opus 4.6',
|
|
284
|
+
'claude-sonnet-4-6': 'Claude Sonnet 4.6',
|
|
285
|
+
'claude-haiku-4-5': 'Claude Haiku 4.5',
|
|
286
|
+
};
|
|
287
|
+
for (const k of usageKeys) {
|
|
288
|
+
if (k.startsWith('claude-') && !models[k]) {
|
|
289
|
+
models[k] = k;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
result.claude_code = { default_model: 'claude-sonnet-5-5', models };
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
catch { }
|
|
296
|
+
// 4. Hermes (~/.hermes/config.yaml)
|
|
297
|
+
try {
|
|
298
|
+
const hermesPath = path.join(homedir, '.hermes', 'config.yaml');
|
|
299
|
+
if (fs.existsSync(hermesPath)) {
|
|
300
|
+
const yaml = fs.readFileSync(hermesPath, 'utf8');
|
|
301
|
+
const defMatch = yaml.match(/^\s*default\s*:\s*([^\r\n#]+)/m);
|
|
302
|
+
const defModel = defMatch?.[1] ? defMatch[1].trim().replace(/^["']|["']$/g, '') : 'qwen/qwen3.8-27b';
|
|
303
|
+
const models = {};
|
|
304
|
+
if (defModel)
|
|
305
|
+
models[defModel] = `${defModel} (Default)`;
|
|
306
|
+
models['qwen/qwen3.8-27b'] = 'Qwen 3.8 27B (LM Studio)';
|
|
307
|
+
models['qwen3.5:397b'] = 'Qwen 3.5 397B';
|
|
308
|
+
models['glm-4.7'] = 'GLM 4.7';
|
|
309
|
+
models['theta-1'] = 'Theta 1';
|
|
310
|
+
models['claude-opus-4-6'] = 'Claude Opus 4.6';
|
|
311
|
+
models['google/gemini-3-flash-preview'] = 'Gemini 3 Flash Preview';
|
|
312
|
+
result.hermes = { default_model: defModel, models };
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
catch { }
|
|
316
|
+
// 5. Antigravity
|
|
317
|
+
result.antigravity = {
|
|
318
|
+
default_model: 'gemini-3.1-pro',
|
|
319
|
+
models: {
|
|
320
|
+
'gemini-3.1-pro': 'Gemini 3.1 Pro',
|
|
321
|
+
'gemini-3.0-flash': 'Gemini 3.0 Flash',
|
|
322
|
+
'claude-opus-5-5': 'Claude Opus 5.5',
|
|
323
|
+
'claude-sonnet-5-5': 'Claude Sonnet 5.5',
|
|
324
|
+
'gpt-6.1-sol': 'GPT-6.1 Sol',
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
return result;
|
|
328
|
+
}
|
|
202
329
|
/**
|
|
203
330
|
* Scan the developer's local workspace roots (sibling folders in parent dev dir, ~/dev, ~/Projects, etc.)
|
|
204
331
|
* to discover real project folders so the web UI can auto-match and let the user pick from a dropdown.
|
|
@@ -344,7 +471,7 @@ export class AgileMonsterClient {
|
|
|
344
471
|
}),
|
|
345
472
|
});
|
|
346
473
|
}
|
|
347
|
-
async submitReview(cardId, provider, summary, verificationSteps, targetList, assignToUserId) {
|
|
474
|
+
async submitReview(cardId, provider, summary, verificationSteps, targetList, assignToUserId, extra) {
|
|
348
475
|
return this.request(`/api/agent/cards/${cardId}/submit-review`, {
|
|
349
476
|
method: 'POST',
|
|
350
477
|
body: JSON.stringify({
|
|
@@ -355,6 +482,9 @@ export class AgileMonsterClient {
|
|
|
355
482
|
target_list_id: targetList,
|
|
356
483
|
target_list_name: targetList,
|
|
357
484
|
assign_to_user_id: assignToUserId,
|
|
485
|
+
diff_text: extra?.diffText,
|
|
486
|
+
log_text: extra?.logText,
|
|
487
|
+
files_modified: extra?.filesModified,
|
|
358
488
|
}),
|
|
359
489
|
});
|
|
360
490
|
}
|
package/dist/server.js
CHANGED
|
@@ -892,20 +892,79 @@ function getAgileMonsterLocksDir() {
|
|
|
892
892
|
catch { }
|
|
893
893
|
return dir;
|
|
894
894
|
}
|
|
895
|
+
/**
|
|
896
|
+
* Append a live, human-readable IDE activity entry to `<projectPath>/.agilemonster/ACTIVITY.md`
|
|
897
|
+
* (and exclude `.agilemonster/` in `.git/info/exclude` so it never pollutes `git status`).
|
|
898
|
+
* Developers can open `.agilemonster/ACTIVITY.md` in their IDE at any time to see full agent
|
|
899
|
+
* troubleshooting logs, files inspected, build verification output, and diffs.
|
|
900
|
+
*/
|
|
901
|
+
function appendIdeActivityLog(projectPath, entry) {
|
|
902
|
+
try {
|
|
903
|
+
const amDir = path.join(projectPath, '.agilemonster');
|
|
904
|
+
if (!fs.existsSync(amDir)) {
|
|
905
|
+
fs.mkdirSync(amDir, { recursive: true });
|
|
906
|
+
}
|
|
907
|
+
const gitExclude = path.join(projectPath, '.git', 'info', 'exclude');
|
|
908
|
+
if (fs.existsSync(gitExclude)) {
|
|
909
|
+
const exContent = fs.readFileSync(gitExclude, 'utf8');
|
|
910
|
+
if (!exContent.includes('.agilemonster/')) {
|
|
911
|
+
fs.appendFileSync(gitExclude, '\n.agilemonster/\n.agilemonster.json\n', 'utf8');
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
const logPath = path.join(amDir, 'ACTIVITY.md');
|
|
915
|
+
const timestamp = new Date().toISOString().replace('T', ' ').slice(0, 19) + ' UTC';
|
|
916
|
+
const header = !fs.existsSync(logPath)
|
|
917
|
+
? `# 🤖 Agile Monster — Live IDE Agent Activity Log\n\nThis file is automatically updated by the Agile Monster agent daemon so you can inspect every task, model, file change, and build check directly in your IDE.\n\n---\n\n`
|
|
918
|
+
: '';
|
|
919
|
+
const lines = [
|
|
920
|
+
`## [${timestamp}] ${entry.cardTitle} (\`${entry.cardId.slice(0, 8)}\`)`,
|
|
921
|
+
`- **Agent**: ${entry.agentName} | **Model**: \`${entry.model || 'auto'}\` | **Status**: \`${entry.status}\``,
|
|
922
|
+
];
|
|
923
|
+
if (entry.files && entry.files.length > 0) {
|
|
924
|
+
lines.push(`- **Files**: ${entry.files.map((f) => `\`${f}\``).join(', ')}`);
|
|
925
|
+
}
|
|
926
|
+
lines.push(`- **Summary**: ${entry.summary}`);
|
|
927
|
+
if (entry.diff && entry.diff.trim().length > 0) {
|
|
928
|
+
const clippedDiff = entry.diff.length > 4000 ? entry.diff.slice(0, 4000) + '\n... (truncated)' : entry.diff;
|
|
929
|
+
lines.push('', '```diff', clippedDiff.trim(), '```');
|
|
930
|
+
}
|
|
931
|
+
lines.push('', '---', '');
|
|
932
|
+
fs.appendFileSync(logPath, header + lines.join('\n'), 'utf8');
|
|
933
|
+
}
|
|
934
|
+
catch { }
|
|
935
|
+
}
|
|
895
936
|
export async function executeQueuedCardAutonomously(client, card, projectPath) {
|
|
896
937
|
if (!projectPath || !fs.existsSync(projectPath))
|
|
897
938
|
return false;
|
|
898
939
|
if (activeAutonomousCards.has(card.id))
|
|
899
940
|
return false;
|
|
900
941
|
const lockFile = path.join(getAgileMonsterLocksDir(), `task-${card.id.slice(0, 8)}.lock`);
|
|
942
|
+
const cardUpdatedAt = card.updated_at || '';
|
|
901
943
|
try {
|
|
902
944
|
if (fs.existsSync(lockFile)) {
|
|
903
945
|
const stat = fs.statSync(lockFile);
|
|
904
|
-
|
|
905
|
-
|
|
946
|
+
let existingLock = {};
|
|
947
|
+
try {
|
|
948
|
+
existingLock = JSON.parse(fs.readFileSync(lockFile, 'utf8'));
|
|
949
|
+
}
|
|
950
|
+
catch { }
|
|
951
|
+
const sameRevision = cardUpdatedAt !== '' && existingLock?.card_updated_at === cardUpdatedAt;
|
|
952
|
+
if (existingLock?.status === 'running' && Date.now() - stat.mtimeMs < 3 * 60 * 1000 && sameRevision) {
|
|
953
|
+
return false; // Currently running on this exact revision
|
|
954
|
+
}
|
|
955
|
+
if (existingLock?.status === 'completed' && sameRevision && Date.now() - stat.mtimeMs < 10 * 60 * 1000) {
|
|
956
|
+
return false; // Already completed this exact revision
|
|
957
|
+
}
|
|
958
|
+
if (!cardUpdatedAt && Date.now() - stat.mtimeMs < 3 * 60 * 1000) {
|
|
959
|
+
return false;
|
|
906
960
|
}
|
|
907
961
|
}
|
|
908
|
-
fs.writeFileSync(lockFile, JSON.stringify({
|
|
962
|
+
fs.writeFileSync(lockFile, JSON.stringify({
|
|
963
|
+
card_id: card.id,
|
|
964
|
+
card_updated_at: cardUpdatedAt,
|
|
965
|
+
status: 'running',
|
|
966
|
+
started_at: new Date().toISOString(),
|
|
967
|
+
}), 'utf8');
|
|
909
968
|
}
|
|
910
969
|
catch {
|
|
911
970
|
return false;
|
|
@@ -990,7 +1049,7 @@ export async function executeQueuedCardAutonomously(client, card, projectPath) {
|
|
|
990
1049
|
continue;
|
|
991
1050
|
}
|
|
992
1051
|
fs.writeFileSync(fullPath, updatedContent, 'utf8');
|
|
993
|
-
// Pre-submit static analysis gate for Dart/Flutter files
|
|
1052
|
+
// Pre-submit static analysis gate for Dart/Flutter and PHP files
|
|
994
1053
|
if (relPath.endsWith('.dart')) {
|
|
995
1054
|
try {
|
|
996
1055
|
execSync(`dart analyze "${fullPath}"`, {
|
|
@@ -1008,44 +1067,121 @@ export async function executeQueuedCardAutonomously(client, card, projectPath) {
|
|
|
1008
1067
|
}
|
|
1009
1068
|
}
|
|
1010
1069
|
}
|
|
1070
|
+
else if (relPath.endsWith('.php')) {
|
|
1071
|
+
try {
|
|
1072
|
+
execSync(`php -l "${fullPath}"`, {
|
|
1073
|
+
cwd: projectPath,
|
|
1074
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1075
|
+
timeout: 10000,
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
catch {
|
|
1079
|
+
fs.writeFileSync(fullPath, currentContent, 'utf8');
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1011
1083
|
if (!modifiedFiles.includes(relPath)) {
|
|
1012
1084
|
modifiedFiles.push(relPath);
|
|
1013
1085
|
}
|
|
1014
1086
|
}
|
|
1015
1087
|
}
|
|
1016
1088
|
}
|
|
1089
|
+
// Pre-submit frontend build verification gate for JS/TS/React/Vite/Next.js projects
|
|
1090
|
+
const hasFrontendEdits = modifiedFiles.some((f) => /\.(tsx?|jsx?|vue)$/i.test(f));
|
|
1091
|
+
const pkgJsonPath = path.join(projectPath, 'package.json');
|
|
1092
|
+
if (hasFrontendEdits && fs.existsSync(pkgJsonPath)) {
|
|
1093
|
+
try {
|
|
1094
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
|
|
1095
|
+
if (pkg?.scripts?.build && fs.existsSync(path.join(projectPath, 'node_modules'))) {
|
|
1096
|
+
execSync('npm run build', {
|
|
1097
|
+
cwd: projectPath,
|
|
1098
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1099
|
+
timeout: 45000,
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
catch (buildErr) {
|
|
1104
|
+
// Revert broken frontend edits so we never leave the project with a broken build
|
|
1105
|
+
for (const [fPath, orig] of originalContents.entries()) {
|
|
1106
|
+
try {
|
|
1107
|
+
fs.writeFileSync(fPath, orig, 'utf8');
|
|
1108
|
+
}
|
|
1109
|
+
catch { }
|
|
1110
|
+
}
|
|
1111
|
+
modifiedFiles.length = 0;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1017
1114
|
if (modifiedFiles.length > 0) {
|
|
1018
|
-
let
|
|
1115
|
+
let rawDiff = '';
|
|
1019
1116
|
try {
|
|
1020
1117
|
const quotedFiles = modifiedFiles.map((f) => `"${f.replace(/"/g, '\\"')}"`).join(' ');
|
|
1021
|
-
|
|
1118
|
+
rawDiff = execSync(`git diff -- ${quotedFiles}`, {
|
|
1022
1119
|
cwd: projectPath,
|
|
1023
1120
|
encoding: 'utf8',
|
|
1024
1121
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
1025
1122
|
}).trim();
|
|
1026
|
-
if (rawDiff) {
|
|
1027
|
-
const clipped = rawDiff.length > 2500 ? rawDiff.slice(0, 2500) + '\n... (diff truncated)' : rawDiff;
|
|
1028
|
-
diffSnippet = `\n\n### Code Changes (\`git diff\`)\n\`\`\`diff\n${clipped}\n\`\`\``;
|
|
1029
|
-
}
|
|
1030
1123
|
}
|
|
1031
1124
|
catch { }
|
|
1032
1125
|
const baseSummary = editsRes.summary ||
|
|
1033
|
-
`
|
|
1034
|
-
|
|
1126
|
+
`Updated ${modifiedFiles.join(', ')} to complete "${card.title}".`;
|
|
1127
|
+
// Keep card comment concise plain English (no raw code diffs on the card!)
|
|
1128
|
+
const cleanPlainSummary = baseSummary
|
|
1129
|
+
.replace(/```[\s\S]*?```/g, '')
|
|
1130
|
+
.trim();
|
|
1035
1131
|
const verificationSteps = editsRes.verification_steps ||
|
|
1036
|
-
`1.
|
|
1037
|
-
|
|
1132
|
+
`1. Check the updated behavior for "${card.title}".\n2. Click **Approve & Move to Done** to deploy and confirm.`;
|
|
1133
|
+
const logLines = [
|
|
1134
|
+
`Claimed card "${card.title}" (${card.id}) in ${projectPath}`,
|
|
1135
|
+
`Agent: ${workerLabel} | Model: ${card.agent_model || 'auto'}`,
|
|
1136
|
+
`Selected files: ${Object.keys(filesPayload).join(', ')}`,
|
|
1137
|
+
`Modified & verified files: ${modifiedFiles.join(', ')}`,
|
|
1138
|
+
`Summary: ${cleanPlainSummary}`,
|
|
1139
|
+
];
|
|
1140
|
+
appendIdeActivityLog(projectPath, {
|
|
1141
|
+
cardId: card.id,
|
|
1142
|
+
cardTitle: card.title,
|
|
1143
|
+
agentName: workerLabel,
|
|
1144
|
+
model: card.agent_model || 'auto',
|
|
1145
|
+
status: 'review',
|
|
1146
|
+
summary: cleanPlainSummary,
|
|
1147
|
+
files: modifiedFiles,
|
|
1148
|
+
diff: rawDiff,
|
|
1149
|
+
});
|
|
1150
|
+
await client.submitReview(card.id, provider, cleanPlainSummary, verificationSteps, undefined, undefined, {
|
|
1151
|
+
diffText: rawDiff,
|
|
1152
|
+
logText: logLines.join('\n'),
|
|
1153
|
+
filesModified: modifiedFiles.join(', '),
|
|
1154
|
+
});
|
|
1155
|
+
try {
|
|
1156
|
+
fs.writeFileSync(lockFile, JSON.stringify({
|
|
1157
|
+
card_id: card.id,
|
|
1158
|
+
card_updated_at: cardUpdatedAt,
|
|
1159
|
+
status: 'completed',
|
|
1160
|
+
completed_at: new Date().toISOString(),
|
|
1161
|
+
}), 'utf8');
|
|
1162
|
+
}
|
|
1163
|
+
catch { }
|
|
1038
1164
|
sendDesktopNotification(`Agile Monster -> Ready for Review`, `Completed "${card.title}" (${modifiedFiles.length} file(s) updated) and moved to Testing / Review!`);
|
|
1039
1165
|
return true;
|
|
1040
1166
|
}
|
|
1041
1167
|
else {
|
|
1042
|
-
|
|
1168
|
+
try {
|
|
1169
|
+
if (fs.existsSync(lockFile))
|
|
1170
|
+
fs.unlinkSync(lockFile);
|
|
1171
|
+
}
|
|
1172
|
+
catch { }
|
|
1173
|
+
await client.askQuestion(card.id, provider, `Inspected ${Object.keys(filesPayload)
|
|
1043
1174
|
.map((f) => `\`${f}\``)
|
|
1044
|
-
.join(', ')}.
|
|
1175
|
+
.join(', ')}, but needed more specific details to apply a verified code change automatically. Reply in the comments with additional details (or open your IDE agent) to continue!`);
|
|
1045
1176
|
return false;
|
|
1046
1177
|
}
|
|
1047
1178
|
}
|
|
1048
1179
|
catch (err) {
|
|
1180
|
+
try {
|
|
1181
|
+
if (fs.existsSync(lockFile))
|
|
1182
|
+
fs.unlinkSync(lockFile);
|
|
1183
|
+
}
|
|
1184
|
+
catch { }
|
|
1049
1185
|
try {
|
|
1050
1186
|
await client.addProgress(card.id, provider, `Autonomous runner note: ${err?.message || String(err)}`);
|
|
1051
1187
|
}
|
|
@@ -1061,13 +1197,27 @@ export async function deployApprovedCardIfNeeded(client, card, projectPath) {
|
|
|
1061
1197
|
return false;
|
|
1062
1198
|
const shortId = card.id.slice(0, 8);
|
|
1063
1199
|
const deployLock = path.join(getAgileMonsterLocksDir(), `deployed-${shortId}.json`);
|
|
1200
|
+
const cardUpdatedAt = card.updated_at || '';
|
|
1064
1201
|
if (fs.existsSync(deployLock)) {
|
|
1065
|
-
|
|
1202
|
+
try {
|
|
1203
|
+
const existing = JSON.parse(fs.readFileSync(deployLock, 'utf8'));
|
|
1204
|
+
if (!cardUpdatedAt || existing?.card_updated_at === cardUpdatedAt) {
|
|
1205
|
+
return false;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
catch {
|
|
1209
|
+
return false;
|
|
1210
|
+
}
|
|
1066
1211
|
}
|
|
1067
1212
|
const provider = card.agent_provider || 'cursor';
|
|
1068
1213
|
const cleanTitle = (card.title || 'update').replace(/["`\r\n]+/g, ' ').trim();
|
|
1069
1214
|
try {
|
|
1070
|
-
fs.writeFileSync(deployLock, JSON.stringify({
|
|
1215
|
+
fs.writeFileSync(deployLock, JSON.stringify({
|
|
1216
|
+
card_id: card.id,
|
|
1217
|
+
card_updated_at: cardUpdatedAt,
|
|
1218
|
+
title: card.title,
|
|
1219
|
+
started_at: new Date().toISOString(),
|
|
1220
|
+
}), 'utf8');
|
|
1071
1221
|
const branch = execSync('git rev-parse --abbrev-ref HEAD', {
|
|
1072
1222
|
cwd: projectPath,
|
|
1073
1223
|
encoding: 'utf8',
|
|
@@ -1097,8 +1247,17 @@ export async function deployApprovedCardIfNeeded(client, card, projectPath) {
|
|
|
1097
1247
|
encoding: 'utf8',
|
|
1098
1248
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
1099
1249
|
}).trim();
|
|
1100
|
-
|
|
1101
|
-
|
|
1250
|
+
const msg = `🚀 **[Auto-Deployed to Production]** Deployment confirmed! Committed and pushed \`${commitHash}\` to \`origin/${branch}\`, and moved card to **Done**.`;
|
|
1251
|
+
await client.completeTask(card.id, provider, msg);
|
|
1252
|
+
appendIdeActivityLog(projectPath, {
|
|
1253
|
+
cardId: card.id,
|
|
1254
|
+
cardTitle: card.title,
|
|
1255
|
+
agentName: card.agent_display_name || provider,
|
|
1256
|
+
model: card.agent_model || 'auto',
|
|
1257
|
+
status: 'done',
|
|
1258
|
+
summary: `Deployment confirmed (${commitHash} -> origin/${branch}). Moved card to Done.`,
|
|
1259
|
+
});
|
|
1260
|
+
sendDesktopNotification(`Agile Monster -> Production Confirmed`, `Deployed "${card.title}" (${commitHash}) and moved to Done!`);
|
|
1102
1261
|
return true;
|
|
1103
1262
|
}
|
|
1104
1263
|
else if (isContainerRestartCard) {
|
|
@@ -1115,7 +1274,16 @@ export async function deployApprovedCardIfNeeded(client, card, projectPath) {
|
|
|
1115
1274
|
encoding: 'utf8',
|
|
1116
1275
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
1117
1276
|
}).trim();
|
|
1118
|
-
|
|
1277
|
+
const msg = `🚀 **[Auto-Deployed to Production]** Triggered production container rebuild & restart (\`${commitHash}\` on \`origin/${branch}\`) and confirmed move to **Done**.`;
|
|
1278
|
+
await client.completeTask(card.id, provider, msg);
|
|
1279
|
+
appendIdeActivityLog(projectPath, {
|
|
1280
|
+
cardId: card.id,
|
|
1281
|
+
cardTitle: card.title,
|
|
1282
|
+
agentName: card.agent_display_name || provider,
|
|
1283
|
+
model: card.agent_model || 'auto',
|
|
1284
|
+
status: 'done',
|
|
1285
|
+
summary: `Triggered container restart (${commitHash}) and moved card to Done.`,
|
|
1286
|
+
});
|
|
1119
1287
|
sendDesktopNotification(`Agile Monster -> Container Redeploy`, `Triggered container restart (${commitHash}) on origin/${branch}!`);
|
|
1120
1288
|
return true;
|
|
1121
1289
|
}
|
|
@@ -1125,7 +1293,8 @@ export async function deployApprovedCardIfNeeded(client, card, projectPath) {
|
|
|
1125
1293
|
encoding: 'utf8',
|
|
1126
1294
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
1127
1295
|
}).trim();
|
|
1128
|
-
|
|
1296
|
+
const msg = `🚀 **[Auto-Deployed to Production]** Confirmed latest commit \`${headHash}\` is pushed to \`origin/${branch}\` and live in production. Moved card to **Done**.`;
|
|
1297
|
+
await client.completeTask(card.id, provider, msg);
|
|
1129
1298
|
return true;
|
|
1130
1299
|
}
|
|
1131
1300
|
}
|
|
@@ -1204,7 +1373,7 @@ export function startBuiltInPullPulse(server, client, intervalMs = 20000) {
|
|
|
1204
1373
|
if (!config?.board_id || !apiKey) {
|
|
1205
1374
|
return;
|
|
1206
1375
|
}
|
|
1207
|
-
const includeDiscovery = pulseCount === 0 || pulseCount %
|
|
1376
|
+
const includeDiscovery = pulseCount === 0 || pulseCount % 3 === 0;
|
|
1208
1377
|
pulseCount++;
|
|
1209
1378
|
const res = await client.pulse({
|
|
1210
1379
|
boardId: config.board_id,
|
|
@@ -1212,6 +1381,7 @@ export function startBuiltInPullPulse(server, client, intervalMs = 20000) {
|
|
|
1212
1381
|
projectName: config.project_name || path.basename(process.cwd()),
|
|
1213
1382
|
projectPath: config.project_path || process.cwd(),
|
|
1214
1383
|
discoveredProjects: includeDiscovery ? client.discoverLocalProjects() : undefined,
|
|
1384
|
+
discoveredModels: includeDiscovery ? client.discoverLocalAgentModels() : undefined,
|
|
1215
1385
|
});
|
|
1216
1386
|
if (res.saved_bindings && res.saved_bindings.length > 0) {
|
|
1217
1387
|
autoProvisionSavedBindings(apiKey, res.saved_bindings);
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agilemonster/mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Official Model Context Protocol (MCP) server & Orchestrator for Agile Monster (Cursor, Claude Code, Antigravity, Codex, Hermes, Windsurf)",
|
|
6
6
|
"main": "dist/server.js",
|