@mindexec/cli 0.2.92 → 0.2.94
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/package.json +2 -2
- package/remote-hub.js +87 -7
- package/server.js +86 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mindexec/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.94",
|
|
4
4
|
"description": "MindExec local runtime and bridge CLI",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"type": "module",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"node": ">=20"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@mindexec/remote": "^0.1.
|
|
50
|
+
"@mindexec/remote": "^0.1.15",
|
|
51
51
|
"@openai/codex-sdk": "^0.137.0",
|
|
52
52
|
"chokidar": "^3.6.0",
|
|
53
53
|
"cors": "^2.8.5",
|
package/remote-hub.js
CHANGED
|
@@ -77,10 +77,72 @@ function isPrivateIPv4(value) {
|
|
|
77
77
|
|| (parts[0] === 192 && parts[1] === 168);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
function
|
|
80
|
+
function isPreferredPhysicalInterfaceName(value) {
|
|
81
|
+
const name = String(value || '').toLowerCase();
|
|
82
|
+
return /(^|[\s_\-()])((wi[\s_\-]?fi)|wifi|wireless|wlan|ethernet|lan|이더넷|en\d+|eth\d+)(?=$|[\s_\-()])/i.test(name);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isVirtualInterfaceName(value) {
|
|
86
|
+
const name = String(value || '').toLowerCase();
|
|
87
|
+
return /virtual|vethernet|hyper-v|hyperv|vmware|virtualbox|vbox|docker|wsl|container|bluetooth|loopback|npcap|isatap|teredo/i.test(name);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function getIPv4LastOctet(value) {
|
|
91
|
+
const parts = String(value || '').split('.').map(part => Number(part));
|
|
92
|
+
if (parts.length !== 4 || parts.some(part => !Number.isInteger(part))) {
|
|
93
|
+
return -1;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return parts[3];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function scoreReachableIPv4Candidate(candidate) {
|
|
100
|
+
const address = String(candidate?.address || '');
|
|
101
|
+
const name = String(candidate?.name || '');
|
|
102
|
+
let score = 0;
|
|
103
|
+
|
|
104
|
+
if (isPrivateIPv4(address)) {
|
|
105
|
+
score += 1000;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (/^192\.168\./.test(address)) {
|
|
109
|
+
score += 90;
|
|
110
|
+
} else if (/^10\./.test(address)) {
|
|
111
|
+
score += 80;
|
|
112
|
+
} else if (/^172\.(1[6-9]|2\d|3[0-1])\./.test(address)) {
|
|
113
|
+
score += 70;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (isPreferredPhysicalInterfaceName(name)) {
|
|
117
|
+
score += 500;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (isVirtualInterfaceName(name)) {
|
|
121
|
+
score -= 1200;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (/^169\.254\./.test(address)) {
|
|
125
|
+
score -= 2000;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const lastOctet = getIPv4LastOctet(address);
|
|
129
|
+
if (lastOctet === 1) {
|
|
130
|
+
score -= 60;
|
|
131
|
+
} else if (lastOctet === 0 || lastOctet === 255) {
|
|
132
|
+
score -= 100;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (candidate?.mac && candidate.mac === '00:00:00:00:00:00') {
|
|
136
|
+
score -= 50;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return score;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function getReachableIPv4CandidateEntries() {
|
|
81
143
|
const candidates = [];
|
|
82
144
|
const interfaces = os.networkInterfaces();
|
|
83
|
-
for (const entries of Object.
|
|
145
|
+
for (const [name, entries] of Object.entries(interfaces)) {
|
|
84
146
|
for (const entry of entries || []) {
|
|
85
147
|
if (entry?.family !== 'IPv4' || entry.internal) {
|
|
86
148
|
continue;
|
|
@@ -91,13 +153,28 @@ function getReachableIPv4Candidates() {
|
|
|
91
153
|
continue;
|
|
92
154
|
}
|
|
93
155
|
|
|
94
|
-
candidates.push(
|
|
156
|
+
candidates.push({
|
|
157
|
+
address,
|
|
158
|
+
name: safeString(name, 128),
|
|
159
|
+
mac: safeString(entry.mac, 32),
|
|
160
|
+
score: 0,
|
|
161
|
+
virtual: isVirtualInterfaceName(name),
|
|
162
|
+
preferred: isPreferredPhysicalInterfaceName(name)
|
|
163
|
+
});
|
|
95
164
|
}
|
|
96
165
|
}
|
|
97
166
|
|
|
98
167
|
return candidates
|
|
99
|
-
.
|
|
100
|
-
|
|
168
|
+
.map(candidate => ({
|
|
169
|
+
...candidate,
|
|
170
|
+
score: scoreReachableIPv4Candidate(candidate)
|
|
171
|
+
}))
|
|
172
|
+
.sort((left, right) => right.score - left.score || left.address.localeCompare(right.address))
|
|
173
|
+
.filter((candidate, index, all) => all.findIndex(item => item.address === candidate.address) === index);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function getReachableIPv4Candidates() {
|
|
177
|
+
return getReachableIPv4CandidateEntries().map(candidate => candidate.address);
|
|
101
178
|
}
|
|
102
179
|
|
|
103
180
|
function getReachableIPv4Address() {
|
|
@@ -409,7 +486,8 @@ export function createRemoteHub(options = {}) {
|
|
|
409
486
|
}
|
|
410
487
|
|
|
411
488
|
function getAgentEndpointRouteInfo() {
|
|
412
|
-
const
|
|
489
|
+
const candidateDetails = getReachableIPv4CandidateEntries();
|
|
490
|
+
const candidates = candidateDetails.map(candidate => candidate.address);
|
|
413
491
|
const endpoint = getAgentEndpoint();
|
|
414
492
|
const wildcardWithoutReachableAddress =
|
|
415
493
|
!publicEndpoint
|
|
@@ -424,7 +502,8 @@ export function createRemoteHub(options = {}) {
|
|
|
424
502
|
port: parsed?.port || 0,
|
|
425
503
|
accountRouteReady: reason === 'ok',
|
|
426
504
|
reason,
|
|
427
|
-
candidates
|
|
505
|
+
candidates,
|
|
506
|
+
candidateDetails
|
|
428
507
|
};
|
|
429
508
|
}
|
|
430
509
|
|
|
@@ -574,6 +653,7 @@ export function createRemoteHub(options = {}) {
|
|
|
574
653
|
agentEndpointAccountRouteReady: routeInfo.accountRouteReady,
|
|
575
654
|
agentEndpointRouteReason: routeInfo.reason,
|
|
576
655
|
agentEndpointCandidates: routeInfo.candidates,
|
|
656
|
+
agentEndpointCandidateDetails: routeInfo.candidateDetails,
|
|
577
657
|
pairToken: includeSecrets ? pairToken : undefined,
|
|
578
658
|
pairTokenPreview: maskToken(pairToken),
|
|
579
659
|
deviceCount: devices.size,
|
package/server.js
CHANGED
|
@@ -2732,6 +2732,7 @@ const remoteHub = createRemoteHub({
|
|
|
2732
2732
|
});
|
|
2733
2733
|
|
|
2734
2734
|
const REMOTE_AGENT_STDIO_TAIL_CHARS = 12000;
|
|
2735
|
+
const REMOTE_AGENT_EARLY_EXIT_MS = 1200;
|
|
2735
2736
|
const REMOTE_AGENT_DEFAULT_ENGINE = 'auto';
|
|
2736
2737
|
let remoteAgentState = createRemoteAgentIdleState();
|
|
2737
2738
|
|
|
@@ -2778,6 +2779,71 @@ function appendRemoteAgentOutput(stream, chunk) {
|
|
|
2778
2779
|
remoteAgentState.updatedAt = new Date().toISOString();
|
|
2779
2780
|
}
|
|
2780
2781
|
|
|
2782
|
+
function summarizeRemoteAgentOutput(value, maxLength = 700) {
|
|
2783
|
+
const text = String(value || '').replace(/\s+/g, ' ').trim();
|
|
2784
|
+
if (text.length <= maxLength) {
|
|
2785
|
+
return text;
|
|
2786
|
+
}
|
|
2787
|
+
|
|
2788
|
+
return `...${text.slice(text.length - maxLength + 3)}`;
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
function formatRemoteAgentFailureSummary(agent = remoteAgentState) {
|
|
2792
|
+
const details = [];
|
|
2793
|
+
if (agent.lastError) {
|
|
2794
|
+
details.push(agent.lastError);
|
|
2795
|
+
} else if (agent.signal) {
|
|
2796
|
+
details.push(`signal:${agent.signal}`);
|
|
2797
|
+
} else if (Number.isFinite(agent.exitCode)) {
|
|
2798
|
+
details.push(`exit:${agent.exitCode}`);
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2801
|
+
const stderr = summarizeRemoteAgentOutput(agent.stderrTail);
|
|
2802
|
+
if (stderr) {
|
|
2803
|
+
details.push(`stderr=${stderr}`);
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2806
|
+
const stdout = summarizeRemoteAgentOutput(agent.stdoutTail);
|
|
2807
|
+
if (stdout) {
|
|
2808
|
+
details.push(`stdout=${stdout}`);
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
return details.join(' | ') || 'remote-agent-failed';
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
function waitForRemoteAgentEarlyExit(child, settleMs = REMOTE_AGENT_EARLY_EXIT_MS) {
|
|
2815
|
+
return new Promise(resolve => {
|
|
2816
|
+
if (!child || child.exitCode !== null || child.killed) {
|
|
2817
|
+
resolve(true);
|
|
2818
|
+
return;
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2821
|
+
let settled = false;
|
|
2822
|
+
let timer = null;
|
|
2823
|
+
const cleanup = () => {
|
|
2824
|
+
if (timer) {
|
|
2825
|
+
clearTimeout(timer);
|
|
2826
|
+
}
|
|
2827
|
+
child.off('exit', onExit);
|
|
2828
|
+
child.off('error', onError);
|
|
2829
|
+
};
|
|
2830
|
+
const finish = value => {
|
|
2831
|
+
if (settled) {
|
|
2832
|
+
return;
|
|
2833
|
+
}
|
|
2834
|
+
settled = true;
|
|
2835
|
+
cleanup();
|
|
2836
|
+
resolve(value);
|
|
2837
|
+
};
|
|
2838
|
+
const onExit = () => finish(true);
|
|
2839
|
+
const onError = () => finish(true);
|
|
2840
|
+
|
|
2841
|
+
child.once('exit', onExit);
|
|
2842
|
+
child.once('error', onError);
|
|
2843
|
+
timer = setTimeout(() => finish(false), settleMs);
|
|
2844
|
+
});
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2781
2847
|
function normalizeRemoteAgentEngine(value) {
|
|
2782
2848
|
const engine = String(value || REMOTE_AGENT_DEFAULT_ENGINE).trim().toLowerCase();
|
|
2783
2849
|
if (engine === 'fast' || engine === 'csharp' || engine === 'c#') {
|
|
@@ -2888,10 +2954,12 @@ async function startRemoteAgentConnection(options = {}) {
|
|
|
2888
2954
|
const engine = normalizeRemoteAgentEngine(options.engine);
|
|
2889
2955
|
|
|
2890
2956
|
if (!manager) {
|
|
2957
|
+
logWarn('remote', 'managed RemoteAgent connect rejected: invalid manager endpoint');
|
|
2891
2958
|
return { ok: false, error: 'invalid-manager-endpoint', agent: serializeRemoteAgentState() };
|
|
2892
2959
|
}
|
|
2893
2960
|
|
|
2894
2961
|
if (!pairToken) {
|
|
2962
|
+
logWarn('remote', `managed RemoteAgent connect rejected: missing pair token ${formatKeyValue('manager', manager)}`);
|
|
2895
2963
|
return { ok: false, error: 'missing-pair-token', agent: serializeRemoteAgentState() };
|
|
2896
2964
|
}
|
|
2897
2965
|
|
|
@@ -2961,6 +3029,7 @@ async function startRemoteAgentConnection(options = {}) {
|
|
|
2961
3029
|
remoteAgentState.status = 'failed';
|
|
2962
3030
|
remoteAgentState.lastError = err?.message || String(err);
|
|
2963
3031
|
remoteAgentState.updatedAt = new Date().toISOString();
|
|
3032
|
+
logWarn('remote', `managed RemoteAgent failed: ${formatRemoteAgentFailureSummary()}`);
|
|
2964
3033
|
emitBridgeEvent('RemoteAgentFailed', serializeRemoteAgentState());
|
|
2965
3034
|
});
|
|
2966
3035
|
child.once('exit', (code, signal) => {
|
|
@@ -2972,6 +3041,11 @@ async function startRemoteAgentConnection(options = {}) {
|
|
|
2972
3041
|
if (code !== 0 && !remoteAgentState.lastError) {
|
|
2973
3042
|
remoteAgentState.lastError = signal ? `signal:${signal}` : `exit:${code}`;
|
|
2974
3043
|
}
|
|
3044
|
+
if (remoteAgentState.status === 'failed') {
|
|
3045
|
+
logWarn('remote', `managed RemoteAgent failed: ${formatRemoteAgentFailureSummary()}`);
|
|
3046
|
+
} else {
|
|
3047
|
+
logEvent('remote', `managed RemoteAgent exited ${formatKeyValue('code', code ?? 0)}`, 'remote');
|
|
3048
|
+
}
|
|
2975
3049
|
emitBridgeEvent(remoteAgentState.status === 'exited' ? 'RemoteAgentExited' : 'RemoteAgentFailed', serializeRemoteAgentState());
|
|
2976
3050
|
});
|
|
2977
3051
|
|
|
@@ -2981,6 +3055,13 @@ async function startRemoteAgentConnection(options = {}) {
|
|
|
2981
3055
|
'remote');
|
|
2982
3056
|
emitBridgeEvent('RemoteAgentStarted', serializeRemoteAgentState());
|
|
2983
3057
|
|
|
3058
|
+
const exitedEarly = await waitForRemoteAgentEarlyExit(child);
|
|
3059
|
+
if (exitedEarly) {
|
|
3060
|
+
const error = formatRemoteAgentFailureSummary();
|
|
3061
|
+
logWarn('remote', `managed RemoteAgent exited during startup ${formatKeyValue('manager', manager)} ${formatKeyValue('error', error)}`);
|
|
3062
|
+
return { ok: false, error, agent: serializeRemoteAgentState() };
|
|
3063
|
+
}
|
|
3064
|
+
|
|
2984
3065
|
return { ok: true, alreadyRunning: false, agent: serializeRemoteAgentState() };
|
|
2985
3066
|
}
|
|
2986
3067
|
|
|
@@ -7537,14 +7618,18 @@ app.get('/api/remote/agent/status', (req, res) => {
|
|
|
7537
7618
|
app.post('/api/remote/agent/connect', async (req, res) => {
|
|
7538
7619
|
res.setHeader('Cache-Control', 'no-store');
|
|
7539
7620
|
try {
|
|
7621
|
+
const requestedManager = normalizeRemoteManagerEndpoint(req.body?.manager || req.body?.managerEndpoint);
|
|
7540
7622
|
const result = await startRemoteAgentConnection({
|
|
7541
|
-
manager:
|
|
7623
|
+
manager: requestedManager,
|
|
7542
7624
|
pairToken: req.body?.pairToken || req.body?.pair,
|
|
7543
7625
|
leaseId: req.body?.leaseId,
|
|
7544
7626
|
nodeId: req.body?.nodeId,
|
|
7545
7627
|
engine: req.body?.engine,
|
|
7546
7628
|
source: req.body?.source || 'registry'
|
|
7547
7629
|
});
|
|
7630
|
+
if (!result.ok) {
|
|
7631
|
+
logWarn('remote', `managed RemoteAgent connect failed ${formatKeyValue('manager', requestedManager || 'invalid')} ${formatKeyValue('error', result.error || 'unknown')}`);
|
|
7632
|
+
}
|
|
7548
7633
|
res.status(result.ok ? 200 : 400).json(result);
|
|
7549
7634
|
} catch (err) {
|
|
7550
7635
|
logError('remote', 'managed RemoteAgent connect failed.', err);
|