fix (templates): llm-server almost OK.

This commit is contained in:
Rodrigo Rodriguez 2024-09-15 14:41:56 -03:00
parent 93ff7a418e
commit 25882854f8
6 changed files with 260 additions and 240 deletions

View file

@ -11,7 +11,7 @@ import {GBUtil} from './dist/src/util.js'
// Displays version of Node JS being used at runtime and others attributes. // Displays version of Node JS being used at runtime and others attributes.
console.log(`\nLoading virtual machine source code files...`); console.log(`\nLoading General Bots VM...`);
var __dirname = process.env.PWD || process.cwd(); var __dirname = process.env.PWD || process.cwd();
try { try {
@ -24,7 +24,7 @@ try {
var processDist = async () => { var processDist = async () => {
if (!await GBUtil.exists('dist')) { if (!await GBUtil.exists('dist')) {
console.log(`\n`); console.log(`\n`);
console.log(`Generall Bots: Compiling...`); console.log(`General Bots: Compiling...`);
exec(path.join(__dirname, 'node_modules/.bin/tsc'), async (err, stdout, stderr) => { exec(path.join(__dirname, 'node_modules/.bin/tsc'), async (err, stdout, stderr) => {
if (err) { if (err) {
console.error(err); console.error(err);
@ -41,7 +41,7 @@ try {
if (!await GBUtil.exists('node_modules')) { if (!await GBUtil.exists('node_modules')) {
console.log(`\n`); console.log(`\n`);
console.log(`Generall Bots: Installing modules for the first time, please wait...`); console.log(`General Bots: Installing modules for the first time, please wait...`);
exec('npm install', async (err, stdout, stderr) => { exec('npm install', async (err, stdout, stderr) => {
if (err) { if (err) {
console.error(err); console.error(err);

View file

@ -121,7 +121,7 @@ export class GBVMService extends GBService {
const wordFile = filename; const wordFile = filename;
const vbsFile = isWord ? filename.substr(0, filename.indexOf('docx')) + 'vbs' : filename; const vbsFile = isWord ? filename.substr(0, filename.indexOf('docx')) + 'vbs' : filename;
const fullVbsFile = urlJoin(folder, vbsFile); const fullVbsFile = urlJoin(folder, vbsFile);
const docxStat =await fs.stat(urlJoin(folder, wordFile)); const docxStat = await fs.stat(urlJoin(folder, wordFile));
const interval = 3000; // If compiled is older 30 seconds, then recompile. const interval = 3000; // If compiled is older 30 seconds, then recompile.
let writeVBS = true; let writeVBS = true;
@ -141,7 +141,7 @@ export class GBVMService extends GBService {
// .post(subscription); // .post(subscription);
if (await GBUtil.exists(fullVbsFile)) { if (await GBUtil.exists(fullVbsFile)) {
const vbsStat =await fs.stat(fullVbsFile); const vbsStat = await fs.stat(fullVbsFile);
if (docxStat['mtimeMs'] < vbsStat['mtimeMs'] + interval) { if (docxStat['mtimeMs'] < vbsStat['mtimeMs'] + interval) {
writeVBS = false; writeVBS = false;
} }
@ -155,7 +155,7 @@ export class GBVMService extends GBService {
// Write VBS file without pragma keywords. // Write VBS file without pragma keywords.
await fs.writeFile(urlJoin(folder, vbsFile), text); await fs.writeFile(urlJoin(folder, vbsFile), text);
} }
// Process node_modules install. // Process node_modules install.
@ -173,11 +173,11 @@ export class GBVMService extends GBService {
}); });
} }
const compiledAt =await fs.stat(fullFilename); const compiledAt = await fs.stat(fullFilename);
const jsfile = urlJoin(folder, `${filename}.js`); const jsfile = urlJoin(folder, `${filename}.js`);
if (await GBUtil.exists(jsfile)) { if (await GBUtil.exists(jsfile)) {
const jsStat =await fs.stat(jsfile); const jsStat = await fs.stat(jsfile);
const interval = 1000; // If compiled is older 1 seconds, then recompile. const interval = 1000; // If compiled is older 1 seconds, then recompile.
if (compiledAt.isFile() && compiledAt['mtimeMs'] > jsStat['mtimeMs'] + interval) { if (compiledAt.isFile() && compiledAt['mtimeMs'] > jsStat['mtimeMs'] + interval) {
await this.translateBASIC(mainName, fullFilename, min); await this.translateBASIC(mainName, fullFilename, min);
@ -196,7 +196,7 @@ export class GBVMService extends GBService {
} }
private async processNodeModules(folder: string, min: GBMinInstance) { private async processNodeModules(folder: string, min: GBMinInstance) {
const node_modules = urlJoin(process.env.PWD, folder, 'node_modules'); const node_modules = urlJoin(process.env.PWD, folder, 'node_modules');
if (!await GBUtil.exists(node_modules)) { if (!(await GBUtil.exists(node_modules))) {
const packageJson = ` const packageJson = `
{ {
"name": "${min.botId}.gbdialog", "name": "${min.botId}.gbdialog",
@ -215,7 +215,7 @@ export class GBVMService extends GBService {
"async-retry": "1.3.3" "async-retry": "1.3.3"
} }
}`; }`;
await fs.writeFile(urlJoin(folder, 'package.json'), packageJson); await fs.writeFile(urlJoin(folder, 'package.json'), packageJson);
GBLogEx.info(min, `Installing node_modules...`); GBLogEx.info(min, `Installing node_modules...`);
const npmPath = urlJoin(process.env.PWD, 'node_modules', '.bin', 'npm'); const npmPath = urlJoin(process.env.PWD, 'node_modules', '.bin', 'npm');
@ -498,10 +498,10 @@ export class GBVMService extends GBService {
// Generates function JSON metadata to be used later. // Generates function JSON metadata to be used later.
const jsonFile = `${filename}.json`; const jsonFile = `${filename}.json`;
await fs.writeFile(jsonFile, JSON.stringify(metadata)); await fs.writeFile(jsonFile, JSON.stringify(metadata));
const mapFile = `${filename}.map`; const mapFile = `${filename}.map`;
await fs.writeFile(mapFile, JSON.stringify(map)); await fs.writeFile(mapFile, JSON.stringify(map));
// Execute off-line code tasks // Execute off-line code tasks
@ -511,207 +511,11 @@ await fs.writeFile(mapFile, JSON.stringify(map));
const jsfile: string = `${filename}.js`; const jsfile: string = `${filename}.js`;
code = ` code = (await fs.readFile('./vm-inject.js')).toString();
module.exports = (async () => {
// Imports npm packages for this .gbdialog conversational application.
require('isomorphic-fetch');
const YAML = require('yaml');
const http = require('node:http');
const retry = require('async-retry');
const createRpcClient = require("@push-rpc/core").createRpcClient;
const createHttpClient = require("@push-rpc/http").createHttpClient;
// Unmarshalls Local variables from server VM.
const pid = this.pid;
let id = this.id;
let username = this.username;
let mobile = this.mobile;
let from = this.from;
const channel = this.channel;
const ENTER = this.ENTER;
const headers = this.headers;
let httpUsername = this.httpUsername;
let httpPs = this.httpPs;
let today = this.today;
let now = this.now;
let date = new Date();
let page = null;
const files = [];
let col = 1;
let index = 1;
const mid = (arr, start, length) => {
if (length === undefined) {
return arr.slice(start);
}
return arr.slice(start, start + length);
}
// Makes objects in BASIC insensitive.
const caseInsensitive = (listOrRow) => {
if (!listOrRow) {
return listOrRow;
};
const lowercase = (oldKey) => typeof oldKey === 'string' ? oldKey.toLowerCase() : oldKey;
const createCaseInsensitiveProxy = (obj) => {
const propertiesMap = new Map(Object.keys(obj).map(propKey => [lowercase(propKey), obj[propKey]]));
const caseInsensitiveGetHandler = {
get: (target, property) => propertiesMap.get(lowercase(property))
};
return new Proxy(obj, caseInsensitiveGetHandler);
};
if (listOrRow.length) {
return listOrRow.map(row => createCaseInsensitiveProxy(row));
} else {
return createCaseInsensitiveProxy(listOrRow);
}
};
// Transfers auto variables into global object.
for (const key of Object.keys(this.variables)) {
global[key] = this.variables[key];
console.log('Defining global variable: ' + key);
}
// Defines local utility BASIC functions.
const ubound = (gbarray) => {
let length = 0;
if (gbarray){
length = gbarray.length;
if (length > 0){
if(gbarray[0].gbarray){
return length - 1;
}
}
}
return length;
}
const isarray = (gbarray) => {return Array.isArray(gbarray) };
// Proxies remote functions as BASIC functions.
const weekday = (v) => { return (async () => { return await dk.getWeekFromDate({v}) })(); };
const hour = (v) => { return (async () => { return await dk.getHourFromDate({v}) })(); };
const base64 = (v) => { return (async () => { return await dk.getCoded({v}) })(); };
const tolist = (v) => { return (async () => { return await dk.getToLst({v}) })(); };
const uuid = () => {
var dt = new Date().getTime();
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
var r = (dt + Math.random()*16)%16 | 0;
dt = Math.floor(dt/16);
return (c=='x' ? r :(r&0x3|0x8)).toString(16);
});
return uuid;
};
const random = () => { return Number.parseInt((Math.random() * 8) % 8 * 100000000)};
// Setups interprocess communication from .gbdialog run-time to the BotServer API.
const optsRPC = {callTimeout: this.callTimeout, messageParser: data => {return JSON.parse(data)}};
let url;
const agent = http.Agent({ keepAlive: true });
url = 'http://localhost:${GBVMService.API_PORT}/${min.botId}/dk';
const dk = (await createRpcClient(() => createHttpClient(url, {agent: agent}), optsRPC)).remote;
url = 'http://localhost:${GBVMService.API_PORT}/${min.botId}/sys';
const sys = (await createRpcClient(() => createHttpClient(url, {agent: agent}), optsRPC)).remote;
url = 'http://localhost:${GBVMService.API_PORT}/${min.botId}/wa';
const wa = (await createRpcClient(() => createHttpClient(url, {agent: agent}), optsRPC)).remote;
url = 'http://localhost:${GBVMService.API_PORT}/${min.botId}/img';
const img = (await createRpcClient(() => createHttpClient(url, {agent: agent}), optsRPC)).remote;
const timeout = (ms)=> {
return new Promise(resolve => setTimeout(resolve, ms));
}
const ensureTokens = async (firstTime) => {
const REFRESH_THRESHOLD_MS = 10 * 60 * 1000; // 10 minutes in milliseconds
const tokens = this.tokens ? this.tokens.split(',') : [];
for (let i = 0; i < tokens.length; i++) {
const tokenName = tokens[i];
// Auto update Bearer authentication for the first token.
const expiresOn = new Date(global[tokenName + '_expiresOn']);
const expiration = expiresOn.getTime() - REFRESH_THRESHOLD_MS;
// Expires token 10min. before or if it the first time, load it.
if (expiration < Date.now() || firstTime) {
console.log('Expired. Refreshing token...' + expiration);
try {
const result = await sys.getCustomToken({pid: this.pid, tokenName: tokenName});
global[tokenName] = result.token;
global[tokenName + '_expiresOn'] = result.expiresOn;
console.log('DONE:' + new Date(global[tokenName + '_expiresOn']));
} catch (error) {
console.error('Failed to refresh token for ' + tokenName + ':', error);
continue;
}
}
if (i == 0) {
headers['Authorization'] = 'Bearer ' + global[tokenName];
}
}
};
const sleep = async (ms) => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
const TOYAML = (json) => {
const doc = new YAML.Document();
doc.contents = json;
return doc.toString();
}
// Line of Business logic.
let __reportMerge = {adds: 0, updates: 0, skipped: 0};
let __report = () => {
return __reportMerge.title + ' adds: ' + __reportMerge.adds + ', updates: ' + __reportMerge.updates + ' and skipped: ' + __reportMerge.skipped + '.';
};
let REPORT = 'No report yet';
try{
await ensureTokens(true);
${code}
}
catch(e){
console.log(e);
reject ({message: e.message, name: e.name});
}
finally{
// Closes handles if any.
await wa.closeHandles({pid: pid});
await sys.closeHandles({pid: pid});
resolve(true);
}
})();
`;
code = ji.default(code, ' '); code = ji.default(code, ' ');
await fs.writeFile(jsfile, code); await fs.writeFile(jsfile, code);
GBLogEx.info(min, `Code reloaded: ${path.basename(filename)}.`); GBLogEx.info(min, `Code reloaded: ${path.basename(filename)}.`);
} }
@ -723,7 +527,7 @@ await fs.writeFile(jsfile, code);
// Creates an empty object that will receive Sequelize fields. // Creates an empty object that will receive Sequelize fields.
const tablesFile = `${task.file}.tables.json`; const tablesFile = `${task.file}.tables.json`;
await fs.writeFile(tablesFile, JSON.stringify(task.tables)); await fs.writeFile(tablesFile, JSON.stringify(task.tables));
} }
} }
} }
@ -741,7 +545,6 @@ await fs.writeFile(jsfile, code);
lines.forEach(line => { lines.forEach(line => {
if (line.trim()) { if (line.trim()) {
console.log(line);
const keyword = /^\s*SET SCHEDULE (.*)/gi; const keyword = /^\s*SET SCHEDULE (.*)/gi;
let result: any = keyword.exec(line); let result: any = keyword.exec(line);
if (result) { if (result) {
@ -807,22 +610,21 @@ await fs.writeFile(jsfile, code);
const propertiesExp = propertiesText[i]; const propertiesExp = propertiesText[i];
const t = getType(propertiesExp[2]); const t = getType(propertiesExp[2]);
let element; let element;
const description = propertiesExp[4]?.trim(); const description = propertiesExp[4]?.trim();
if (t === 'enum') { if (t === 'enum') {
const list = propertiesExp[2] as any; const list = propertiesExp[2] as any;
element = z.enum(list.split(',')); element = z.enum(list.split(','));
} else if (t === 'string') { } else if (t === 'string') {
element = z.string({description:description}); element = z.string({ description: description });
} else if (t === 'object') { } else if (t === 'object') {
element = z.string({description:description}); // Assuming 'object' is represented as a string here element = z.string({ description: description }); // Assuming 'object' is represented as a string here
} else if (t === 'number') { } else if (t === 'number') {
element = z.number({description:description}); element = z.number({ description: description });
} else { } else {
GBLog.warn(`Element type invalid specified on .docx: ${propertiesExp[0]}`); GBLog.warn(`Element type invalid specified on .docx: ${propertiesExp[0]}`);
} }
element['type'] = t; element['type'] = t;
properties[propertiesExp[1].trim()] = element; properties[propertiesExp[1].trim()] = element;
} }
@ -835,7 +637,7 @@ await fs.writeFile(jsfile, code);
schema: zodToJsonSchema(z.object(properties)) schema: zodToJsonSchema(z.object(properties))
}, },
arguments: propertiesText.reduce((acc, prop) => { arguments: propertiesText.reduce((acc, prop) => {
acc[prop[1].trim()] = prop[3]?.trim(); // Assuming value is in the 3rd index acc[prop[1].trim()] = prop[3]?.trim(); // Assuming value is in the 3rd index
return acc; return acc;
}, {}) }, {})
}; };
@ -1177,10 +979,9 @@ await fs.writeFile(jsfile, code);
time: 60 * 60 * 24 * 14, time: 60 * 60 * 24 * 14,
cwd: gbdialogPath, cwd: gbdialogPath,
script: runnerPath script: runnerPath
}); });
result = await run(code, Object.assign( sandbox, { filename: scriptFilePath})); result = await run(code, Object.assign(sandbox, { filename: scriptFilePath }));
} }
} catch (error) { } catch (error) {
throw new Error(`BASIC RUNTIME ERR: ${error.message ? error.message : error}\n Stack:${error.stack}`); throw new Error(`BASIC RUNTIME ERR: ${error.message ? error.message : error}\n Stack:${error.stack}`);

View file

@ -574,8 +574,8 @@ export class GBConversationalService {
GBLog.verbose(`Translated text(playMarkdown): ${text}.`); GBLog.verbose(`Translated text(playMarkdown): ${text}.`);
} }
var renderer = new marked.marked.Renderer(); var renderer = new marked.Renderer();
renderer.oldImage = renderer.image; renderer['oldImage'] = renderer.image;
renderer.image = function (href, title, text) { renderer.image = function (href, title, text) {
var videos = ['webm', 'mp4', 'mov']; var videos = ['webm', 'mp4', 'mov'];
var filetype = href.split('.').pop(); var filetype = href.split('.').pop();
@ -592,7 +592,7 @@ export class GBConversationalService {
'</video>'; '</video>';
return out; return out;
} else { } else {
return renderer.oldImage(href, title, text); return renderer['oldImage'](href, title, text);
} }
}; };
@ -601,13 +601,8 @@ export class GBConversationalService {
marked.setOptions({ marked.setOptions({
renderer: renderer, renderer: renderer,
gfm: true, gfm: true,
tables: true,
breaks: false, breaks: false,
pedantic: false, pedantic: false,
sanitize: false,
smartLists: true,
smartypants: false,
xhtml: false
}); });
// MSFT Translator breaks markdown, so we need to manually fix it: // MSFT Translator breaks markdown, so we need to manually fix it:
@ -619,10 +614,10 @@ export class GBConversationalService {
if (mobile) { if (mobile) {
await this.sendMarkdownToMobile(min, step, mobile, text); await this.sendMarkdownToMobile(min, step, mobile, text);
} else if (GBConfigService.get('DISABLE_WEB') !== 'true') { } else if (GBConfigService.get('DISABLE_WEB') !== 'true') {
const html = marked(text); const html = await marked.marked(text);
await this.sendHTMLToWeb(min, step, html, answer); await this.sendHTMLToWeb(min, step, html, answer);
} else { } else {
const html = marked(text); const html = await marked.marked(text);
await min.conversationalService.sendText(min, step, html); await min.conversationalService.sendText(min, step, html);
} }
} }

View file

@ -1230,6 +1230,7 @@ export class GBMinService {
GBLog.error('Calling processActivity due to Signing Key could not be retrieved error.'); GBLog.error('Calling processActivity due to Signing Key could not be retrieved error.');
await adapter['processActivity'](req, res, handler); await adapter['processActivity'](req, res, handler);
} else { } else {
throw error; throw error;
} }
} }

View file

@ -8,7 +8,7 @@ BEGIN SYSTEM PROMPT
You must act as a chatbot that will assist a store attendant by following these rules: You must act as a chatbot that will assist a store attendant by following these rules:
Whenever the attendant places an order, it must include the table and the customer's name. Example: A 400ml Pineapple Caipirinha for Rafael at table 10. Whenever the attendant places an order, it must include the table and the customer's name. Example: A 400ml Pineapple Caipirinha for Rafael at table 10.
Orders are based on the products and sides from this product menu: Orders are based on the products and sides from this product menu:
${JSON.stringify(products)}. ${TOYAML(products)}.
For each order placed, return a JSON containing the product name, the table, and a list of sides with their respective ids. For each order placed, return a JSON containing the product name, the table, and a list of sides with their respective ids.
Keep orderedItems with only one item and keep sideItems only with the sides that were specified. Keep orderedItems with only one item and keep sideItems only with the sides that were specified.

223
vm-inject.js Normal file
View file

@ -0,0 +1,223 @@
module.exports = (async () => {
// Imports npm packages for this .gbdialog conversational application.
require('isomorphic-fetch');
const YAML = require('yaml');
const http = require('node:http');
const retry = require('async-retry');
const createRpcClient = require('@push-rpc/core').createRpcClient;
const createHttpClient = require('@push-rpc/http').createHttpClient;
// Unmarshalls Local variables from server VM.
const pid = this.pid;
let id = this.id;
let username = this.username;
let mobile = this.mobile;
let from = this.from;
const channel = this.channel;
const ENTER = this.ENTER;
const headers = this.headers;
let httpUsername = this.httpUsername;
let httpPs = this.httpPs;
let today = this.today;
let now = this.now;
let date = new Date();
let page = null;
const files = [];
let col = 1;
let index = 1;
const mid = (arr, start, length) => {
if (length === undefined) {
return arr.slice(start);
}
return arr.slice(start, start + length);
};
// Makes objects in BASIC insensitive.
const caseInsensitive = listOrRow => {
if (!listOrRow) {
return listOrRow;
}
const lowercase = oldKey => (typeof oldKey === 'string' ? oldKey.toLowerCase() : oldKey);
const createCaseInsensitiveProxy = obj => {
const propertiesMap = new Map(Object.keys(obj).map(propKey => [lowercase(propKey), obj[propKey]]));
const caseInsensitiveGetHandler = {
get: (target, property) => propertiesMap.get(lowercase(property))
};
return new Proxy(obj, caseInsensitiveGetHandler);
};
if (listOrRow.length) {
return listOrRow.map(row => createCaseInsensitiveProxy(row));
} else {
return createCaseInsensitiveProxy(listOrRow);
}
};
// Transfers auto variables into global object.
for (const key of Object.keys(this.variables)) {
global[key] = this.variables[key];
console.log('Defining global variable: ' + key);
}
// Defines local utility BASIC functions.
const ubound = gbarray => {
let length = 0;
if (gbarray) {
length = gbarray.length;
if (length > 0) {
if (gbarray[0].gbarray) {
return length - 1;
}
}
}
return length;
};
const isarray = gbarray => {
return Array.isArray(gbarray);
};
// Proxies remote functions as BASIC functions.
const weekday = v => {
return (async () => {
return await dk.getWeekFromDate({ v });
})();
};
const hour = v => {
return (async () => {
return await dk.getHourFromDate({ v });
})();
};
const base64 = v => {
return (async () => {
return await dk.getCoded({ v });
})();
};
const tolist = v => {
return (async () => {
return await dk.getToLst({ v });
})();
};
const uuid = () => {
var dt = new Date().getTime();
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
var r = (dt + Math.random() * 16) % 16 | 0;
dt = Math.floor(dt / 16);
return (c == 'x' ? r : (r & 0x3) | 0x8).toString(16);
});
return uuid;
};
const random = () => {
return Number.parseInt(((Math.random() * 8) % 8) * 100000000);
};
// Setups interprocess communication from .gbdialog run-time to the BotServer API.
const optsRPC = {
callTimeout: this.callTimeout,
messageParser: data => {
return JSON.parse(data);
}
};
let url;
const agent = http.Agent({ keepAlive: true });
url = 'http://localhost:${GBVMService.API_PORT}/${min.botId}/dk';
const dk = (await createRpcClient(() => createHttpClient(url, { agent: agent }), optsRPC)).remote;
url = 'http://localhost:${GBVMService.API_PORT}/${min.botId}/sys';
const sys = (await createRpcClient(() => createHttpClient(url, { agent: agent }), optsRPC)).remote;
url = 'http://localhost:${GBVMService.API_PORT}/${min.botId}/wa';
const wa = (await createRpcClient(() => createHttpClient(url, { agent: agent }), optsRPC)).remote;
url = 'http://localhost:${GBVMService.API_PORT}/${min.botId}/img';
const img = (await createRpcClient(() => createHttpClient(url, { agent: agent }), optsRPC)).remote;
const timeout = ms => {
return new Promise(resolve => setTimeout(resolve, ms));
};
const ensureTokens = async firstTime => {
const REFRESH_THRESHOLD_MS = 10 * 60 * 1000; // 10 minutes in milliseconds
const tokens = this.tokens ? this.tokens.split(',') : [];
for (let i = 0; i < tokens.length; i++) {
const tokenName = tokens[i];
// Auto update Bearer authentication for the first token.
const expiresOn = new Date(global[tokenName + '_expiresOn']);
const expiration = expiresOn.getTime() - REFRESH_THRESHOLD_MS;
// Expires token 10min. before or if it the first time, load it.
if (expiration < Date.now() || firstTime) {
console.log('Expired. Refreshing token...' + expiration);
try {
const result = await sys.getCustomToken({ pid: this.pid, tokenName: tokenName });
global[tokenName] = result.token;
global[tokenName + '_expiresOn'] = result.expiresOn;
console.log('DONE:' + new Date(global[tokenName + '_expiresOn']));
} catch (error) {
console.error('Failed to refresh token for ' + tokenName + ':', error);
continue;
}
}
if (i == 0) {
headers['Authorization'] = 'Bearer ' + global[tokenName];
}
}
};
const sleep = async ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
const TOYAML = json => {
const doc = new YAML.Document();
doc.contents = json;
return doc.toString();
};
// Line of Business logic.
let __reportMerge = { adds: 0, updates: 0, skipped: 0 };
let __report = () => {
return (
__reportMerge.title +
' adds: ' +
__reportMerge.adds +
', updates: ' +
__reportMerge.updates +
' and skipped: ' +
__reportMerge.skipped +
'.'
);
};
let REPORT = 'No report yet';
try {
await ensureTokens(true);
${code}
} catch (e) {
console.log(e);
reject({ message: e.message, name: e.name });
} finally {
// Closes handles if any.
await wa.closeHandles({ pid: pid });
await sys.closeHandles({ pid: pid });
resolve(true);
}
})();