vibecodingmachine-cli 2026.3.14-1537 → 2026.6.17-1956

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 (162) hide show
  1. package/bin/auth/auth-compliance.js +7 -7
  2. package/bin/commands/agent-commands.js +15 -15
  3. package/bin/commands/auto-commands.js +3 -3
  4. package/bin/commands/command-aliases.js +13 -4
  5. package/bin/config/cli-config.js +15 -5
  6. package/bin/update/update-checker.js +5 -5
  7. package/bin/vibecodingmachine.js +2 -2
  8. package/package.json +2 -2
  9. package/src/commands/agents/add.js +5 -5
  10. package/src/commands/agents/check.js +19 -19
  11. package/src/commands/agents/list.js +24 -24
  12. package/src/commands/agents/remove.js +4 -4
  13. package/src/commands/agents-check.js +1 -1
  14. package/src/commands/analyze-file-sizes.js +43 -43
  15. package/src/commands/auto-direct/auto-provider-manager.js +19 -19
  16. package/src/commands/auto-direct/auto-start-phases.js +493 -0
  17. package/src/commands/auto-direct/auto-status-display.js +35 -35
  18. package/src/commands/auto-direct/auto-utils.js +50 -50
  19. package/src/commands/auto-direct/cline-installer.js +56 -0
  20. package/src/commands/auto-direct/code-processor.js +27 -27
  21. package/src/commands/auto-direct/file-scanner.js +19 -19
  22. package/src/commands/auto-direct/ide-completion-waiter.js +485 -0
  23. package/src/commands/auto-direct/ide-fallback-runner.js +226 -0
  24. package/src/commands/auto-direct/ide-provider-runner.js +103 -0
  25. package/src/commands/auto-direct/iteration-handlers.js +189 -0
  26. package/src/commands/auto-direct/iteration-runner.js +485 -0
  27. package/src/commands/auto-direct/provider-config.js +38 -7
  28. package/src/commands/auto-direct/provider-manager.js +132 -6
  29. package/src/commands/auto-direct/requirement-manager.js +169 -104
  30. package/src/commands/auto-direct/requirement-mover.js +350 -0
  31. package/src/commands/auto-direct/spec-handlers.js +155 -0
  32. package/src/commands/auto-direct/spec-ide-runner.js +318 -0
  33. package/src/commands/auto-direct/spec-processing.js +203 -0
  34. package/src/commands/auto-direct/status-display.js +9 -9
  35. package/src/commands/auto-direct/utils.js +83 -1
  36. package/src/commands/auto-direct-refactored.js +1 -413
  37. package/src/commands/auto-direct.js +127 -4119
  38. package/src/commands/auto-execution.js +21 -21
  39. package/src/commands/auto-status-helpers.js +0 -2
  40. package/src/commands/auto.js +22 -22
  41. package/src/commands/check-compliance.js +65 -65
  42. package/src/commands/computers.js +39 -39
  43. package/src/commands/continuous-scan.js +19 -19
  44. package/src/commands/ide.js +4 -4
  45. package/src/commands/locale.js +7 -7
  46. package/src/commands/refactor-file.js +59 -59
  47. package/src/commands/requirements/commands.js +17 -17
  48. package/src/commands/requirements/default-handlers.js +30 -30
  49. package/src/commands/requirements/disable.js +3 -3
  50. package/src/commands/requirements/enable.js +3 -3
  51. package/src/commands/requirements/utils.js +6 -6
  52. package/src/commands/requirements-refactored.js +3 -3
  53. package/src/commands/requirements-remote.js +38 -38
  54. package/src/commands/requirements.js +3 -3
  55. package/src/commands/settings.js +111 -0
  56. package/src/commands/specs/count.js +60 -0
  57. package/src/commands/specs/disable.js +3 -3
  58. package/src/commands/specs/enable.js +3 -3
  59. package/src/commands/status.js +10 -10
  60. package/src/commands/sync.js +25 -25
  61. package/src/commands/timeout.js +35 -35
  62. package/src/trui/TruiInterface.js +2 -2
  63. package/src/trui/agents/AgentInterface.js +4 -4
  64. package/src/trui/agents/handlers/CommandHandler.js +4 -4
  65. package/src/trui/agents/handlers/ContextManager.js +1 -1
  66. package/src/trui/agents/handlers/DisplayHandler.js +11 -11
  67. package/src/trui/agents/handlers/HelpHandler.js +1 -1
  68. package/src/utils/agent-selector.js +6 -6
  69. package/src/utils/antigravity-installer.js +4 -4
  70. package/src/utils/asset-cleanup.js +1 -1
  71. package/src/utils/auth.js +9 -12
  72. package/src/utils/clarification-actions.js +4 -4
  73. package/src/utils/cline-js-handler.js +5 -5
  74. package/src/utils/compliance-check.js +6 -6
  75. package/src/utils/config.js +12 -12
  76. package/src/utils/display-formatters-complete.js +2 -2
  77. package/src/utils/display-formatters-extracted.js +2 -2
  78. package/src/utils/display-formatters.js +2 -2
  79. package/src/utils/feedback-handler.js +2 -2
  80. package/src/utils/first-run.js +7 -7
  81. package/src/utils/ide-detection.js +1 -1
  82. package/src/utils/ide-handlers.js +6 -6
  83. package/src/utils/interactive/clarification-actions.js +3 -3
  84. package/src/utils/interactive/core-ui.js +7 -7
  85. package/src/utils/interactive/file-backup.js +6 -6
  86. package/src/utils/interactive/file-import-export.js +49 -49
  87. package/src/utils/interactive/file-operations.js +3 -3
  88. package/src/utils/interactive/file-validation.js +41 -41
  89. package/src/utils/interactive/interactive-prompts.js +41 -41
  90. package/src/utils/interactive/requirement-actions.js +5 -5
  91. package/src/utils/interactive/requirement-crud.js +4 -4
  92. package/src/utils/interactive/requirements-navigation.js +10 -10
  93. package/src/utils/interactive-broken.js +6 -6
  94. package/src/utils/interactive.js +37 -37
  95. package/src/utils/keyboard-handler.js +4 -4
  96. package/src/utils/prompt-helper.js +6 -6
  97. package/src/utils/provider-checker/agent-checker.js +1 -1
  98. package/src/utils/provider-checker/agent-runner.js +203 -314
  99. package/src/utils/provider-checker/agents-file-lock.js +134 -0
  100. package/src/utils/provider-checker/agents-manager.js +224 -36
  101. package/src/utils/provider-checker/cli-installer.js +28 -28
  102. package/src/utils/provider-checker/cli-utils.js +2 -2
  103. package/src/utils/provider-checker/cursor-approval-clicker.js +108 -0
  104. package/src/utils/provider-checker/format-utils.js +4 -4
  105. package/src/utils/provider-checker/ide-installer-helper.js +96 -0
  106. package/src/utils/provider-checker/ide-manager.js +19 -8
  107. package/src/utils/provider-checker/ide-quota-checker.js +120 -0
  108. package/src/utils/provider-checker/ide-utils.js +2 -2
  109. package/src/utils/provider-checker/node-detector.js +4 -4
  110. package/src/utils/provider-checker/node-utils.js +5 -5
  111. package/src/utils/provider-checker/opencode-checker.js +107 -73
  112. package/src/utils/provider-checker/process-utils.js +1 -1
  113. package/src/utils/provider-checker/provider-validator.js +11 -11
  114. package/src/utils/provider-checker/quota-checker.js +5 -5
  115. package/src/utils/provider-checker/quota-detector.js +5 -5
  116. package/src/utils/provider-checker/requirements-manager.js +6 -6
  117. package/src/utils/provider-checker/test-requirements.js +1 -1
  118. package/src/utils/provider-checker/vscode-approval-clicker.js +328 -0
  119. package/src/utils/provider-checker-new.js +6 -6
  120. package/src/utils/provider-checker.js +6 -6
  121. package/src/utils/provider-checkers/ide-manager.js +13 -13
  122. package/src/utils/provider-checkers/node-executable-finder.js +4 -4
  123. package/src/utils/provider-checkers/provider-checker-core.js +5 -5
  124. package/src/utils/provider-checkers/provider-checker-main.js +17 -17
  125. package/src/utils/provider-registry.js +5 -6
  126. package/src/utils/provider-utils.js +12 -12
  127. package/src/utils/quota-detectors.js +32 -32
  128. package/src/utils/requirement-action-handlers.js +12 -12
  129. package/src/utils/requirement-actions/requirement-operations.js +3 -3
  130. package/src/utils/requirement-actions.js +1 -1
  131. package/src/utils/requirement-file-operations.js +5 -5
  132. package/src/utils/requirement-helpers.js +1 -1
  133. package/src/utils/requirement-management.js +5 -5
  134. package/src/utils/requirement-navigation.js +2 -2
  135. package/src/utils/requirement-organization.js +3 -3
  136. package/src/utils/rui-trui-adapter.js +14 -14
  137. package/src/utils/simple-trui.js +3 -3
  138. package/src/utils/status-helpers-extracted.js +3 -3
  139. package/src/utils/trui-clarifications.js +11 -11
  140. package/src/utils/trui-debug.js +3 -2
  141. package/src/utils/trui-devin.js +217 -0
  142. package/src/utils/trui-feedback.js +7 -7
  143. package/src/utils/trui-kiro-integration.js +34 -34
  144. package/src/utils/trui-main-handlers.js +20 -21
  145. package/src/utils/trui-main-menu.js +19 -19
  146. package/src/utils/trui-nav-agents.js +59 -8
  147. package/src/utils/trui-nav-requirements.js +3 -3
  148. package/src/utils/trui-nav-settings.js +10 -10
  149. package/src/utils/trui-nav-specifications.js +1 -1
  150. package/src/utils/trui-navigation-backup.js +11 -11
  151. package/src/utils/trui-navigation.js +9 -9
  152. package/src/utils/trui-provider-health.js +25 -25
  153. package/src/utils/trui-provider-manager.js +28 -28
  154. package/src/utils/trui-quick-menu.js +2 -2
  155. package/src/utils/trui-req-actions-backup.js +21 -21
  156. package/src/utils/trui-req-actions.js +20 -20
  157. package/src/utils/trui-req-editor.js +10 -10
  158. package/src/utils/trui-req-file-ops.js +3 -3
  159. package/src/utils/trui-req-tree.js +7 -7
  160. package/src/utils/trui-windsurf.js +103 -103
  161. package/src/utils/user-tracking.js +15 -15
  162. package/src/utils/trui-req-tree-old.js +0 -719
@@ -0,0 +1,134 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+
7
+ /**
8
+ * File locking mechanism for agents.json to prevent race conditions
9
+ * Uses atomic file operations and retry logic with exponential backoff
10
+ */
11
+
12
+ class AgentsFileLock {
13
+ constructor() {
14
+ this.lockDir = path.join(os.homedir(), '.vibecodingmachine', 'locks');
15
+ this.lockFile = path.join(this.lockDir, 'agents.json.lock');
16
+ this.maxRetries = 5;
17
+ this.baseDelay = 50; // ms
18
+ this.maxDelay = 1000; // ms
19
+ }
20
+
21
+ /**
22
+ * Ensure lock directory exists
23
+ */
24
+ ensureLockDir() {
25
+ try {
26
+ if (!fs.existsSync(this.lockDir)) {
27
+ fs.mkdirSync(this.lockDir, { recursive: true });
28
+ }
29
+ } catch (err) {
30
+ console.warn(`[FILE LOCK] Could not create lock directory: ${err.message}`);
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Acquire file lock with retry logic
36
+ * @param {number} retryCount - Current retry attempt
37
+ * @returns {Promise<boolean>} - True if lock acquired
38
+ */
39
+ async acquireLock(retryCount = 0) {
40
+ this.ensureLockDir();
41
+
42
+ try {
43
+ // Try to create lock file atomically
44
+ const lockInfo = {
45
+ pid: process.pid,
46
+ timestamp: Date.now(),
47
+ hostname: os.hostname(),
48
+ retryCount
49
+ };
50
+
51
+ fs.writeFileSync(this.lockFile, JSON.stringify(lockInfo, null, 2), { flag: 'wx' });
52
+ return true;
53
+ } catch (err) {
54
+ if (err.code === 'EEXIST') {
55
+ // Lock file exists, check if it's stale
56
+ if (this.isLockStale()) {
57
+ try {
58
+ fs.unlinkSync(this.lockFile);
59
+ console.log(`[FILE LOCK] Removed stale lock file`);
60
+ return this.acquireLock(retryCount);
61
+ } catch (unlinkErr) {
62
+ console.warn(`[FILE LOCK] Could not remove stale lock: ${unlinkErr.message}`);
63
+ }
64
+ }
65
+
66
+ // Lock is active, retry with exponential backoff
67
+ if (retryCount < this.maxRetries) {
68
+ const delay = Math.min(this.baseDelay * Math.pow(2, retryCount), this.maxDelay);
69
+ console.log(`[FILE LOCK] Lock busy, retry ${retryCount + 1}/${this.maxRetries} in ${delay}ms`);
70
+ await new Promise(resolve => setTimeout(resolve, delay));
71
+ return this.acquireLock(retryCount + 1);
72
+ }
73
+
74
+ console.warn(`[FILE LOCK] Could not acquire lock after ${this.maxRetries} retries`);
75
+ return false;
76
+ }
77
+
78
+ console.error(`[FILE LOCK] Error acquiring lock: ${err.message}`);
79
+ return false;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Release file lock
85
+ */
86
+ releaseLock() {
87
+ try {
88
+ if (fs.existsSync(this.lockFile)) {
89
+ fs.unlinkSync(this.lockFile);
90
+ console.log(`[FILE LOCK] Released lock`);
91
+ }
92
+ } catch (err) {
93
+ console.warn(`[FILE LOCK] Error releasing lock: ${err.message}`);
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Check if lock file is stale (older than 30 seconds)
99
+ * @returns {boolean} - True if lock is stale
100
+ */
101
+ isLockStale() {
102
+ try {
103
+ if (!fs.existsSync(this.lockFile)) {
104
+ return true;
105
+ }
106
+
107
+ const stats = fs.statSync(this.lockFile);
108
+ const age = Date.now() - stats.mtime.getTime();
109
+ return age > 30000; // 30 seconds
110
+ } catch (err) {
111
+ return true; // If we can't check, assume it's stale
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Execute operation with file lock
117
+ * @param {Function} operation - Function to execute while holding lock
118
+ * @returns {Promise<any>} - Result of operation
119
+ */
120
+ async withLock(operation) {
121
+ const lockAcquired = await this.acquireLock();
122
+ if (!lockAcquired) {
123
+ throw new Error('Could not acquire file lock for agents.json');
124
+ }
125
+
126
+ try {
127
+ return await operation();
128
+ } finally {
129
+ this.releaseLock();
130
+ }
131
+ }
132
+ }
133
+
134
+ module.exports = AgentsFileLock;
@@ -3,6 +3,7 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const os = require('os');
6
+ const AgentsFileLock = require('./agents-file-lock');
6
7
 
7
8
  /**
8
9
  * Parse relative time string (e.g., "5h 30m", "2h", "45m") to absolute Date
@@ -77,27 +78,180 @@ function readAgentsFile() {
77
78
  }
78
79
 
79
80
  /**
80
- * Write the agents.json file
81
+ * Write the agents.json file atomically with conflict detection
82
+ * @param {object} agentsData - Data to write
83
+ * @param {number} retryCount - Current retry attempt
84
+ * @returns {boolean} - True if successful
81
85
  */
82
- function writeAgentsFile(agentsData) {
86
+ function writeAgentsFile(agentsData, retryCount = 0) {
83
87
  const agentsPath = getAgentsFilePath();
88
+ const fileLock = new AgentsFileLock();
89
+
90
+ // Helper function to perform the actual write operation
91
+ const performWrite = () => {
92
+ try {
93
+ // Read current file content to detect conflicts
94
+ let currentData = null;
95
+ let currentMtime = null;
96
+
97
+ if (fs.existsSync(agentsPath)) {
98
+ const content = fs.readFileSync(agentsPath, 'utf8');
99
+ currentData = JSON.parse(content);
100
+ currentMtime = fs.statSync(agentsPath).mtime.getTime();
101
+ }
102
+
103
+ // Create temporary file for atomic write
104
+ const tempPath = `${agentsPath}.tmp.${process.pid}.${Date.now()}`;
105
+
106
+ // Write to temporary file first
107
+ fs.writeFileSync(tempPath, JSON.stringify(agentsData, null, 2), 'utf8');
108
+
109
+ // Check for conflicts before moving temp file
110
+ if (currentData && fs.existsSync(agentsPath)) {
111
+ const newMtime = fs.statSync(agentsPath).mtime.getTime();
112
+ if (newMtime !== currentMtime) {
113
+ // File was modified since we read it - check for conflicts
114
+ const newContent = fs.readFileSync(agentsPath, 'utf8');
115
+ const newData = JSON.parse(newContent);
116
+
117
+ // Merge strategy: preserve newer timestamps and operational status
118
+ const mergedData = mergeAgentData(currentData, newData, agentsData);
119
+
120
+ // Update temp file with merged data
121
+ fs.writeFileSync(tempPath, JSON.stringify(mergedData, null, 2), 'utf8');
122
+ console.log(`[AGENTS] Detected conflict, merged changes gracefully`);
123
+ }
124
+ }
125
+
126
+ // Atomic move from temp to final file
127
+ fs.renameSync(tempPath, agentsPath);
128
+
129
+ console.log(`[AGENTS] Successfully wrote agents file`);
130
+ return true;
131
+ } catch (err) {
132
+ // Clean up temp file if it exists
133
+ const tempPath = `${agentsPath}.tmp.${process.pid}.${Date.now()}`;
134
+ if (fs.existsSync(tempPath)) {
135
+ try { fs.unlinkSync(tempPath); } catch { }
136
+ }
137
+ throw err;
138
+ }
139
+ };
84
140
 
85
141
  try {
86
- fs.writeFileSync(agentsPath, JSON.stringify(agentsData, null, 2), 'utf8');
87
- return true;
142
+ return fileLock.withLock(performWrite);
88
143
  } catch (err) {
89
144
  console.error(`[AGENTS] Error writing agents file: ${err.message}`);
145
+
146
+ // Retry logic for transient errors
147
+ if (retryCount < 3 && (err.code === 'EAGAIN' || err.code === 'EBUSY' || err.message.includes('lock'))) {
148
+ const delay = 100 * Math.pow(2, retryCount);
149
+ console.log(`[AGENTS] Retrying write in ${delay}ms (attempt ${retryCount + 1}/3)`);
150
+ setTimeout(() => {
151
+ return writeAgentsFile(agentsData, retryCount + 1);
152
+ }, delay);
153
+ return false;
154
+ }
155
+
90
156
  return false;
91
157
  }
92
158
  }
93
159
 
94
160
  /**
95
- * Update agent status
96
- * @param {string} agentId - Agent ID (e.g., 'windsurf', 'cursor', 'antigravity')
161
+ * Merge agent data to resolve conflicts
162
+ * Prioritizes: operational status > checking status > error status
163
+ * Preserves newest timestamps and rate limit info
164
+ */
165
+ function mergeAgentData(oldData, currentFileData, newData) {
166
+ const merged = { agents: {} };
167
+
168
+ // Get all agent IDs from all sources
169
+ const allAgentIds = new Set([
170
+ ...Object.keys(oldData.agents || {}),
171
+ ...Object.keys(currentFileData.agents || {}),
172
+ ...Object.keys(newData.agents || {})
173
+ ]);
174
+
175
+ for (const agentId of allAgentIds) {
176
+ const oldAgent = oldData.agents?.[agentId] || {};
177
+ const currentAgent = currentFileData.agents?.[agentId] || {};
178
+ const newAgent = newData.agents?.[agentId] || {};
179
+
180
+ // Start with new data as base
181
+ merged.agents[agentId] = { ...newAgent };
182
+
183
+ // Preserve ID if missing
184
+ if (!merged.agents[agentId].id) {
185
+ merged.agents[agentId].id = agentId;
186
+ }
187
+
188
+ // Status priority: operational > checking > error > unknown
189
+ const statuses = [newAgent.status, currentAgent.status, oldAgent.status].filter(Boolean);
190
+ const statusPriority = {
191
+ 'operational': 3,
192
+ 'checking': 2,
193
+ 'rate_limited': 2,
194
+ 'error': 1,
195
+ 'unknown': 0
196
+ };
197
+
198
+ let bestStatus = 'unknown';
199
+ let bestPriority = -1;
200
+ for (const status of statuses) {
201
+ if (statusPriority[status] > bestPriority) {
202
+ bestStatus = status;
203
+ bestPriority = statusPriority[status];
204
+ }
205
+ }
206
+ merged.agents[agentId].status = bestStatus;
207
+
208
+ // Preserve newest timestamp
209
+ const timestamps = [
210
+ newAgent.lastChecked && new Date(newAgent.lastChecked).getTime(),
211
+ currentAgent.lastChecked && new Date(currentAgent.lastChecked).getTime(),
212
+ oldAgent.lastChecked && new Date(oldAgent.lastChecked).getTime()
213
+ ].filter(Boolean);
214
+
215
+ if (timestamps.length > 0) {
216
+ const newestTimestamp = Math.max(...timestamps);
217
+ merged.agents[agentId].lastChecked = new Date(newestTimestamp).toISOString();
218
+ }
219
+
220
+ // Preserve rate limit info from any source
221
+ const rateLimitInfo = [newAgent, currentAgent, oldAgent].find(agent =>
222
+ agent.rateLimitResume || agent.rateLimitResumeAt
223
+ );
224
+ if (rateLimitInfo) {
225
+ if (rateLimitInfo.rateLimitResume) {
226
+ merged.agents[agentId].rateLimitResume = rateLimitInfo.rateLimitResume;
227
+ }
228
+ if (rateLimitInfo.rateLimitResumeAt) {
229
+ merged.agents[agentId].rateLimitResumeAt = rateLimitInfo.rateLimitResumeAt;
230
+ }
231
+ }
232
+
233
+ // Preserve error info if status is error
234
+ if (bestStatus === 'error') {
235
+ const errorInfo = [newAgent, currentAgent, oldAgent].find(agent =>
236
+ agent.error && agent.status === 'error'
237
+ );
238
+ if (errorInfo) {
239
+ merged.agents[agentId].error = errorInfo.error;
240
+ }
241
+ }
242
+ }
243
+
244
+ return merged;
245
+ }
246
+
247
+ /**
248
+ * Update agent status with improved conflict handling
249
+ * @param {string} agentId - Agent ID (e.g., 'devin', 'cursor', 'antigravity')
97
250
  * @param {string} status - Status: 'unknown', 'checking', 'operational', 'rate_limited', 'error'
98
251
  * @param {object} metadata - Optional metadata (e.g., { rateLimitResume: '5m30s', error: 'message' })
252
+ * @returns {Promise<object>} - Updated agent data
99
253
  */
100
- function updateAgentStatus(agentId, status, metadata = {}) {
254
+ async function updateAgentStatus(agentId, status, metadata = {}) {
101
255
  const agentsData = readAgentsFile();
102
256
 
103
257
  // Ensure agents object exists
@@ -119,28 +273,46 @@ function updateAgentStatus(agentId, status, metadata = {}) {
119
273
 
120
274
  // Add metadata
121
275
  if (status === 'rate_limited' && metadata.rateLimitResume) {
122
- // Convert relative time (e.g., "5h 30m") to absolute timestamp
123
276
  const resumeTime = parseRelativeTimeToAbsolute(metadata.rateLimitResume);
124
277
  agentsData.agents[agentId].rateLimitResumeAt = resumeTime.toISOString();
125
- agentsData.agents[agentId].rateLimitResume = metadata.rateLimitResume; // Keep original for reference
278
+ agentsData.agents[agentId].rateLimitResume = metadata.rateLimitResume;
279
+ }
280
+ if (status === 'rate_limited' && metadata.rateLimitResumeAt) {
281
+ agentsData.agents[agentId].rateLimitResumeAt = metadata.rateLimitResumeAt;
126
282
  }
127
283
 
128
284
  if (status === 'error' && metadata.error) {
129
285
  agentsData.agents[agentId].error = metadata.error;
130
286
  }
131
287
 
288
+ if (metadata.failureReason) {
289
+ agentsData.agents[agentId].failureReason = metadata.failureReason;
290
+ } else if (status !== 'error' && status !== 'rate_limited') {
291
+ delete agentsData.agents[agentId].failureReason;
292
+ }
293
+
294
+ if (metadata.userAction) {
295
+ agentsData.agents[agentId].userAction = metadata.userAction;
296
+ } else if (status !== 'error' && status !== 'rate_limited') {
297
+ delete agentsData.agents[agentId].userAction;
298
+ }
299
+
132
300
  // Clear rate limit fields if no longer rate limited
133
301
  if (status !== 'rate_limited') {
134
302
  delete agentsData.agents[agentId].rateLimitResumeAt;
135
303
  delete agentsData.agents[agentId].rateLimitResume;
136
304
  }
137
305
 
138
- // Write file
139
- writeAgentsFile(agentsData);
306
+ // Write file with conflict resolution
307
+ const success = writeAgentsFile(agentsData);
140
308
 
141
- console.log(`[AGENTS] Updated ${agentId} status to: ${status}`);
142
-
143
- return agentsData.agents[agentId];
309
+ if (success) {
310
+ console.log(`[AGENTS] Updated ${agentId} status to: ${status}`);
311
+ return agentsData.agents[agentId];
312
+ } else {
313
+ console.error(`[AGENTS] Failed to update ${agentId} status`);
314
+ throw new Error(`Failed to update agent status for ${agentId}`);
315
+ }
144
316
  }
145
317
 
146
318
  /**
@@ -172,30 +344,45 @@ function getAllAgents() {
172
344
 
173
345
  /**
174
346
  * Generate the connectivity test message for IDE agents
175
- * @param {string} agentName - Human-readable agent name (e.g., 'Windsurf', 'Cursor')
176
- * @param {string} agentId - Agent ID (e.g., 'windsurf', 'cursor')
347
+ * @param {string} agentName - Human-readable agent name (e.g., 'Devin', 'Cursor')
348
+ * @param {string} agentId - Agent ID (e.g., 'devin', 'cursor')
349
+ * @param {string} repoPath - Optional repo path for project-relative messages
177
350
  * @returns {string} Test message to send to IDE
178
351
  */
179
- function generateConnectivityTestMessage(agentName, agentId) {
180
- const agentsPath = getAgentsFilePath();
181
- const relativeAgentsPath = agentsPath.replace(os.homedir(), '~');
182
-
183
- return `# VCM Agent Connectivity Check
184
-
185
- ACTION REQUIRED - VCM Connectivity Test:
186
-
187
- VCM is testing communication with your IDE. To complete this test, please:
188
-
189
- 1. Open the file at: ${relativeAgentsPath}
190
- 2. Find the "${agentId}" entry
191
- 3. Change the "status" field from "checking" to "operational"
192
- 4. Save the file
193
-
194
- Example:
195
- Before: "status": "checking"
196
- After: "status": "operational"
352
+ function generateConnectivityTestMessage(agentName, agentId, repoPath = null) {
353
+ // Use absolute path so IDEs with different working directories write to the correct location.
354
+ const resultPath = repoPath
355
+ ? getResultFilePath(repoPath)
356
+ : path.join(os.homedir(), '.vibecodingmachine', 'temp', 'TEMP_agent_check.txt');
357
+ // Include timestamp so each test run is treated as a unique request by IDEs with conversation history.
358
+ const ts = new Date().toISOString().replace(/[:.]/g, '-');
359
+ const dir = path.dirname(resultPath);
360
+ // Kiro: send a shell command that runs in Kiro's built-in terminal (Ctrl+backtick).
361
+ // Kiro's AI chat refuses file-write requests from automated systems, but the terminal works.
362
+ if (agentId === 'kiro') {
363
+ return `mkdir -p '${dir}' && echo 'VCM_CHECK_OK' > '${resultPath}' && echo done`;
364
+ }
365
+ // Explicit shell command avoids ambiguity (Amazon Q/other IDEs otherwise simulate Python instead of writing files)
366
+ return `VCM connectivity check ${ts}: Run this exact shell command: mkdir -p '${dir}' && echo 'VCM_CHECK_OK' > '${resultPath}' && echo done`;
367
+ }
197
368
 
198
- VCM will detect the change within seconds and complete the test.`;
369
+ /**
370
+ * Format a rate limit resume timestamp as a human-readable string.
371
+ * Returns "will reset at 2:34 PM MST" (future) or "reset at 2:10 PM MST" (past).
372
+ * @param {string|null} rateLimitResumeAt - ISO timestamp
373
+ * @returns {string|null}
374
+ */
375
+ function formatRateLimitTime(rateLimitResumeAt) {
376
+ if (!rateLimitResumeAt) return null;
377
+ try {
378
+ const resumeDate = new Date(rateLimitResumeAt);
379
+ if (isNaN(resumeDate.getTime())) return null;
380
+ const now = new Date();
381
+ const timeStr = resumeDate.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit', timeZoneName: 'short' });
382
+ return resumeDate > now ? `Rate limit will reset at ${timeStr}` : `Rate limited — reset at ${timeStr}`;
383
+ } catch {
384
+ return null;
385
+ }
199
386
  }
200
387
 
201
388
  module.exports = {
@@ -206,5 +393,6 @@ module.exports = {
206
393
  updateAgentStatus,
207
394
  getAgentStatus,
208
395
  getAllAgents,
209
- generateConnectivityTestMessage
396
+ generateConnectivityTestMessage,
397
+ formatRateLimitTime
210
398
  };
@@ -91,7 +91,7 @@ function isCLIAvailable(cmd, providerId) {
91
91
  */
92
92
  async function installCLI(providerId) {
93
93
  console.log(`[CLI INSTALLER] Starting installation for provider: ${providerId}`);
94
-
94
+
95
95
  const config = CLI_AUTO_INSTALL[providerId];
96
96
  if (!config) {
97
97
  console.log(`[CLI INSTALLER] No auto-install config found for ${providerId}`);
@@ -121,11 +121,11 @@ async function installCLI(providerId) {
121
121
  let out = '';
122
122
  let hasStartedDownload = false;
123
123
  let hasStartedInstall = false;
124
-
125
- proc.stdout.on('data', d => {
126
- out += d.toString();
124
+
125
+ proc.stdout.on('data', d => {
126
+ out += d.toString();
127
127
  console.log(`[CLI INSTALLER] STDOUT: ${d.toString().trim()}`);
128
-
128
+
129
129
  // Detect actual progress from output
130
130
  const output = d.toString().toLowerCase();
131
131
  if (output.includes('download') || output.includes('fetch')) {
@@ -137,17 +137,17 @@ async function installCLI(providerId) {
137
137
  // We could emit a progress event here if we had access to onProgress
138
138
  }
139
139
  });
140
-
141
- proc.stderr.on('data', d => {
142
- out += d.toString();
140
+
141
+ proc.stderr.on('data', d => {
142
+ out += d.toString();
143
143
  console.log(`[CLI INSTALLER] STDERR: ${d.toString().trim()}`);
144
144
  });
145
-
145
+
146
146
  proc.on('error', (err) => {
147
147
  console.log(`[CLI INSTALLER] Process error for ${providerId}: ${err.message}`);
148
148
  resolve({ installed: false, note: `Failed to run install script for ${providerId}` });
149
149
  });
150
-
150
+
151
151
  proc.on('close', (code) => {
152
152
  console.log(`[CLI INSTALLER] Install script for ${providerId} exited with code: ${code}`);
153
153
  console.log(`[CLI INSTALLER] Checking if ${providerId} is now available...`);
@@ -166,11 +166,11 @@ async function installCLI(providerId) {
166
166
  let out = '';
167
167
  let hasStartedDownload = false;
168
168
  let hasStartedInstall = false;
169
-
170
- proc.stdout.on('data', d => {
171
- out += d.toString();
169
+
170
+ proc.stdout.on('data', d => {
171
+ out += d.toString();
172
172
  console.log(`[CLI INSTALLER] STDOUT: ${d.toString().trim()}`);
173
-
173
+
174
174
  // Detect actual progress from output
175
175
  const output = d.toString().toLowerCase();
176
176
  if (output.includes('download') || output.includes('fetch')) {
@@ -180,17 +180,17 @@ async function installCLI(providerId) {
180
180
  hasStartedInstall = true;
181
181
  }
182
182
  });
183
-
184
- proc.stderr.on('data', d => {
185
- out += d.toString();
183
+
184
+ proc.stderr.on('data', d => {
185
+ out += d.toString();
186
186
  console.log(`[CLI INSTALLER] STDERR: ${d.toString().trim()}`);
187
187
  });
188
-
188
+
189
189
  proc.on('error', (err) => {
190
190
  console.log(`[CLI INSTALLER] Process error for ${providerId}: ${err.message}`);
191
191
  resolve({ installed: false, note: `Failed to run homebrew install for ${providerId}` });
192
192
  });
193
-
193
+
194
194
  proc.on('close', (code) => {
195
195
  console.log(`[CLI INSTALLER] Homebrew install for ${providerId} exited with code: ${code}`);
196
196
  console.log(`[CLI INSTALLER] Checking if ${providerId} is now available...`);
@@ -215,11 +215,11 @@ async function installCLI(providerId) {
215
215
  let out = '';
216
216
  let hasStartedDownload = false;
217
217
  let hasStartedInstall = false;
218
-
219
- proc.stdout.on('data', d => {
220
- out += d.toString();
218
+
219
+ proc.stdout.on('data', d => {
220
+ out += d.toString();
221
221
  console.log(`[CLI INSTALLER] STDOUT: ${d.toString().trim()}`);
222
-
222
+
223
223
  // Detect actual progress from output
224
224
  const output = d.toString().toLowerCase();
225
225
  if (output.includes('download') || output.includes('fetch')) {
@@ -229,17 +229,17 @@ async function installCLI(providerId) {
229
229
  hasStartedInstall = true;
230
230
  }
231
231
  });
232
-
233
- proc.stderr.on('data', d => {
234
- out += d.toString();
232
+
233
+ proc.stderr.on('data', d => {
234
+ out += d.toString();
235
235
  console.log(`[CLI INSTALLER] STDERR: ${d.toString().trim()}`);
236
236
  });
237
-
237
+
238
238
  proc.on('error', (err) => {
239
239
  console.log(`[CLI INSTALLER] Process error for ${providerId}: ${err.message}`);
240
240
  resolve({ installed: false, note: `Failed to run npm install -g ${config.pkg}` });
241
241
  });
242
-
242
+
243
243
  proc.on('close', (code) => {
244
244
  console.log(`[CLI INSTALLER] npm install for ${providerId} exited with code: ${code}`);
245
245
  console.log(`[CLI INSTALLER] Checking if ${providerId} is now available...`);
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * CLI Utilities Module
3
- *
3
+ *
4
4
  * Contains CLI command availability and installation utilities.
5
- *
5
+ *
6
6
  * NOTE: This module re-exports from cli-installer.js to avoid duplication.
7
7
  */
8
8