@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 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.12";
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): Promise<Card>;
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.12';
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
- if (Date.now() - stat.mtimeMs < 10 * 60 * 1000) {
905
- return false; // Already running or recently processed
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({ card_id: card.id, started_at: new Date().toISOString() }), 'utf8');
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 diffSnippet = '';
1115
+ let rawDiff = '';
1019
1116
  try {
1020
1117
  const quotedFiles = modifiedFiles.map((f) => `"${f.replace(/"/g, '\\"')}"`).join(' ');
1021
- const rawDiff = execSync(`git diff -- ${quotedFiles}`, {
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
- `Implemented "${card.title}" autonomously across ${modifiedFiles.map((f) => `\`${f}\``).join(', ')}.`;
1034
- const summary = `${baseSummary}${diffSnippet}`;
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. Review the code diff above in ${modifiedFiles.join(', ')}\n2. Click **Approve & Move to Production** (or drag to Production) to automatically commit, push, and deploy!`;
1037
- await client.submitReview(card.id, provider, summary, verificationSteps);
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
- await client.addProgress(card.id, provider, `Task claimed and moved to **In Progress**. Inspected ${Object.keys(filesPayload)
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(', ')}. Ready for final IDE review.`);
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
- return false;
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({ card_id: card.id, title: card.title, started_at: new Date().toISOString() }), 'utf8');
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
- await client.addProgress(card.id, provider, `🚀 **[Auto-Deployed to Production]** Card approved into **${card.list_name || 'Production'}** — automatically committed and pushed \`${commitHash}\` to \`origin/${branch}\` to trigger the production CI/CD & container deployment pipeline!`);
1101
- sendDesktopNotification(`Agile Monster -> Production Deploy`, `Pushed "${card.title}" (${commitHash}) to origin/${branch}!`);
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
- await client.addProgress(card.id, provider, `🚀 **[Auto-Deployed to Production]** Triggered production container rebuild & restart via \`${commitHash}\` on \`origin/${branch}\`.`);
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
- await client.addProgress(card.id, provider, `🚀 **[Auto-Deployed to Production]** Verified latest commit \`${headHash}\` is pushed to \`origin/${branch}\` and live in the production CI/CD pipeline.`);
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 % 15 === 0;
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
@@ -30,6 +30,8 @@ export interface Card {
30
30
  board_name?: string;
31
31
  list_name?: string;
32
32
  created_by?: string;
33
+ created_at?: string;
34
+ updated_at?: string;
33
35
  bound_project_path?: string | null;
34
36
  bound_project_name?: string | null;
35
37
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agilemonster/mcp-server",
3
- "version": "1.0.12",
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",