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.
- package/bin/auth/auth-compliance.js +7 -7
- package/bin/commands/agent-commands.js +15 -15
- package/bin/commands/auto-commands.js +3 -3
- package/bin/commands/command-aliases.js +13 -4
- package/bin/config/cli-config.js +15 -5
- package/bin/update/update-checker.js +5 -5
- package/bin/vibecodingmachine.js +2 -2
- package/package.json +2 -2
- package/src/commands/agents/add.js +5 -5
- package/src/commands/agents/check.js +19 -19
- package/src/commands/agents/list.js +24 -24
- package/src/commands/agents/remove.js +4 -4
- package/src/commands/agents-check.js +1 -1
- package/src/commands/analyze-file-sizes.js +43 -43
- package/src/commands/auto-direct/auto-provider-manager.js +19 -19
- package/src/commands/auto-direct/auto-start-phases.js +493 -0
- package/src/commands/auto-direct/auto-status-display.js +35 -35
- package/src/commands/auto-direct/auto-utils.js +50 -50
- package/src/commands/auto-direct/cline-installer.js +56 -0
- package/src/commands/auto-direct/code-processor.js +27 -27
- package/src/commands/auto-direct/file-scanner.js +19 -19
- package/src/commands/auto-direct/ide-completion-waiter.js +485 -0
- package/src/commands/auto-direct/ide-fallback-runner.js +226 -0
- package/src/commands/auto-direct/ide-provider-runner.js +103 -0
- package/src/commands/auto-direct/iteration-handlers.js +189 -0
- package/src/commands/auto-direct/iteration-runner.js +485 -0
- package/src/commands/auto-direct/provider-config.js +38 -7
- package/src/commands/auto-direct/provider-manager.js +132 -6
- package/src/commands/auto-direct/requirement-manager.js +169 -104
- package/src/commands/auto-direct/requirement-mover.js +350 -0
- package/src/commands/auto-direct/spec-handlers.js +155 -0
- package/src/commands/auto-direct/spec-ide-runner.js +318 -0
- package/src/commands/auto-direct/spec-processing.js +203 -0
- package/src/commands/auto-direct/status-display.js +9 -9
- package/src/commands/auto-direct/utils.js +83 -1
- package/src/commands/auto-direct-refactored.js +1 -413
- package/src/commands/auto-direct.js +127 -4119
- package/src/commands/auto-execution.js +21 -21
- package/src/commands/auto-status-helpers.js +0 -2
- package/src/commands/auto.js +22 -22
- package/src/commands/check-compliance.js +65 -65
- package/src/commands/computers.js +39 -39
- package/src/commands/continuous-scan.js +19 -19
- package/src/commands/ide.js +4 -4
- package/src/commands/locale.js +7 -7
- package/src/commands/refactor-file.js +59 -59
- package/src/commands/requirements/commands.js +17 -17
- package/src/commands/requirements/default-handlers.js +30 -30
- package/src/commands/requirements/disable.js +3 -3
- package/src/commands/requirements/enable.js +3 -3
- package/src/commands/requirements/utils.js +6 -6
- package/src/commands/requirements-refactored.js +3 -3
- package/src/commands/requirements-remote.js +38 -38
- package/src/commands/requirements.js +3 -3
- package/src/commands/settings.js +111 -0
- package/src/commands/specs/count.js +60 -0
- package/src/commands/specs/disable.js +3 -3
- package/src/commands/specs/enable.js +3 -3
- package/src/commands/status.js +10 -10
- package/src/commands/sync.js +25 -25
- package/src/commands/timeout.js +35 -35
- package/src/trui/TruiInterface.js +2 -2
- package/src/trui/agents/AgentInterface.js +4 -4
- package/src/trui/agents/handlers/CommandHandler.js +4 -4
- package/src/trui/agents/handlers/ContextManager.js +1 -1
- package/src/trui/agents/handlers/DisplayHandler.js +11 -11
- package/src/trui/agents/handlers/HelpHandler.js +1 -1
- package/src/utils/agent-selector.js +6 -6
- package/src/utils/antigravity-installer.js +4 -4
- package/src/utils/asset-cleanup.js +1 -1
- package/src/utils/auth.js +9 -12
- package/src/utils/clarification-actions.js +4 -4
- package/src/utils/cline-js-handler.js +5 -5
- package/src/utils/compliance-check.js +6 -6
- package/src/utils/config.js +12 -12
- package/src/utils/display-formatters-complete.js +2 -2
- package/src/utils/display-formatters-extracted.js +2 -2
- package/src/utils/display-formatters.js +2 -2
- package/src/utils/feedback-handler.js +2 -2
- package/src/utils/first-run.js +7 -7
- package/src/utils/ide-detection.js +1 -1
- package/src/utils/ide-handlers.js +6 -6
- package/src/utils/interactive/clarification-actions.js +3 -3
- package/src/utils/interactive/core-ui.js +7 -7
- package/src/utils/interactive/file-backup.js +6 -6
- package/src/utils/interactive/file-import-export.js +49 -49
- package/src/utils/interactive/file-operations.js +3 -3
- package/src/utils/interactive/file-validation.js +41 -41
- package/src/utils/interactive/interactive-prompts.js +41 -41
- package/src/utils/interactive/requirement-actions.js +5 -5
- package/src/utils/interactive/requirement-crud.js +4 -4
- package/src/utils/interactive/requirements-navigation.js +10 -10
- package/src/utils/interactive-broken.js +6 -6
- package/src/utils/interactive.js +37 -37
- package/src/utils/keyboard-handler.js +4 -4
- package/src/utils/prompt-helper.js +6 -6
- package/src/utils/provider-checker/agent-checker.js +1 -1
- package/src/utils/provider-checker/agent-runner.js +203 -314
- package/src/utils/provider-checker/agents-file-lock.js +134 -0
- package/src/utils/provider-checker/agents-manager.js +224 -36
- package/src/utils/provider-checker/cli-installer.js +28 -28
- package/src/utils/provider-checker/cli-utils.js +2 -2
- package/src/utils/provider-checker/cursor-approval-clicker.js +108 -0
- package/src/utils/provider-checker/format-utils.js +4 -4
- package/src/utils/provider-checker/ide-installer-helper.js +96 -0
- package/src/utils/provider-checker/ide-manager.js +19 -8
- package/src/utils/provider-checker/ide-quota-checker.js +120 -0
- package/src/utils/provider-checker/ide-utils.js +2 -2
- package/src/utils/provider-checker/node-detector.js +4 -4
- package/src/utils/provider-checker/node-utils.js +5 -5
- package/src/utils/provider-checker/opencode-checker.js +107 -73
- package/src/utils/provider-checker/process-utils.js +1 -1
- package/src/utils/provider-checker/provider-validator.js +11 -11
- package/src/utils/provider-checker/quota-checker.js +5 -5
- package/src/utils/provider-checker/quota-detector.js +5 -5
- package/src/utils/provider-checker/requirements-manager.js +6 -6
- package/src/utils/provider-checker/test-requirements.js +1 -1
- package/src/utils/provider-checker/vscode-approval-clicker.js +328 -0
- package/src/utils/provider-checker-new.js +6 -6
- package/src/utils/provider-checker.js +6 -6
- package/src/utils/provider-checkers/ide-manager.js +13 -13
- package/src/utils/provider-checkers/node-executable-finder.js +4 -4
- package/src/utils/provider-checkers/provider-checker-core.js +5 -5
- package/src/utils/provider-checkers/provider-checker-main.js +17 -17
- package/src/utils/provider-registry.js +5 -6
- package/src/utils/provider-utils.js +12 -12
- package/src/utils/quota-detectors.js +32 -32
- package/src/utils/requirement-action-handlers.js +12 -12
- package/src/utils/requirement-actions/requirement-operations.js +3 -3
- package/src/utils/requirement-actions.js +1 -1
- package/src/utils/requirement-file-operations.js +5 -5
- package/src/utils/requirement-helpers.js +1 -1
- package/src/utils/requirement-management.js +5 -5
- package/src/utils/requirement-navigation.js +2 -2
- package/src/utils/requirement-organization.js +3 -3
- package/src/utils/rui-trui-adapter.js +14 -14
- package/src/utils/simple-trui.js +3 -3
- package/src/utils/status-helpers-extracted.js +3 -3
- package/src/utils/trui-clarifications.js +11 -11
- package/src/utils/trui-debug.js +3 -2
- package/src/utils/trui-devin.js +217 -0
- package/src/utils/trui-feedback.js +7 -7
- package/src/utils/trui-kiro-integration.js +34 -34
- package/src/utils/trui-main-handlers.js +20 -21
- package/src/utils/trui-main-menu.js +19 -19
- package/src/utils/trui-nav-agents.js +59 -8
- package/src/utils/trui-nav-requirements.js +3 -3
- package/src/utils/trui-nav-settings.js +10 -10
- package/src/utils/trui-nav-specifications.js +1 -1
- package/src/utils/trui-navigation-backup.js +11 -11
- package/src/utils/trui-navigation.js +9 -9
- package/src/utils/trui-provider-health.js +25 -25
- package/src/utils/trui-provider-manager.js +28 -28
- package/src/utils/trui-quick-menu.js +2 -2
- package/src/utils/trui-req-actions-backup.js +21 -21
- package/src/utils/trui-req-actions.js +20 -20
- package/src/utils/trui-req-editor.js +10 -10
- package/src/utils/trui-req-file-ops.js +3 -3
- package/src/utils/trui-req-tree.js +7 -7
- package/src/utils/trui-windsurf.js +103 -103
- package/src/utils/user-tracking.js +15 -15
- 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
|
-
|
|
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
|
-
*
|
|
96
|
-
*
|
|
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;
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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., '
|
|
176
|
-
* @param {string} agentId - Agent ID (e.g., '
|
|
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
|
-
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
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...`);
|