new(all): Alpha Word Debugger for 3.0.

This commit is contained in:
rodrigorodriguez 2022-11-13 22:56:09 -03:00
parent 24af00cff6
commit b5da93b5cf
7 changed files with 231 additions and 124 deletions

View file

@ -32,7 +32,7 @@
'use strict';
import { GBLog, GBMinInstance } from 'botlib';
import { GBError, GBLog, GBMinInstance } from 'botlib';
import { GBServer } from '../../../src/app';
import { GBAdminService } from '../../admin.gbapp/services/GBAdminService';
import { GuaribasUser } from '../../security.gbapp/models';
@ -52,10 +52,9 @@ const url = require('url');
* Web Automation services of conversation to be called by BASIC.
*/
export class DebuggerService {
/**
* Reference to minimal bot instance.
*/
* Reference to minimal bot instance.
*/
public min: GBMinInstance;
/**
@ -91,6 +90,96 @@ export class DebuggerService {
conversationsMap = {};
watermarkMap = {};
static systemVariables = [
'AggregateError',
'Array',
'ArrayBuffer',
'Atomics',
'BigInt',
'BigInt64Array',
'BigUint64Array',
'Boolean',
'DataView',
'Date',
'Error',
'EvalError',
'FinalizationRegistry',
'Float32Array',
'Float64Array',
'Function',
'Headers',
'Infinity',
'Int16Array',
'Int32Array',
'Int8Array',
'Intl',
'JSON',
'Map',
'Math',
'NaN',
'Number',
'Object',
'Promise',
'Proxy',
'RangeError',
'ReferenceError',
'Reflect',
'RegExp',
'Request',
'Response',
'Set',
'SharedArrayBuffer',
'String',
'Symbol',
'SyntaxError',
'TypeError',
'URIError',
'Uint16Array',
'Uint32Array',
'Uint8Array',
'Uint8ClampedArray',
'VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL',
'WeakMap',
'WeakRef',
'WeakSet',
'WebAssembly',
'__defineGetter__',
'__defineSetter__',
'__lookupGetter__',
'__lookupSetter__',
'__proto__',
'clearImmediate',
'clearInterval',
'clearTimeout',
'console',
'constructor',
'decodeURI',
'decodeURIComponent',
'dss',
'encodeURI',
'encodeURIComponent',
'escape',
'eval',
'fetch',
'global',
'globalThis',
'hasOwnProperty',
'isFinite',
'isNaN',
'isPrototypeOf',
'parseFloat',
'parseInt',
'process',
'propertyIsEnumerable',
'setImmediate',
'setInterval',
'setTimeout',
'toLocaleString',
'toString',
'undefined',
'unescape',
'valueOf'
];
/**
* When creating this keyword facade,a bot instance is
@ -101,110 +190,111 @@ export class DebuggerService {
this.user = user;
this.dk = dk;
this.debugWeb = this.min.core.getParam<boolean>(
this.min.instance,
'Debug Web Automation',
false
);
this.debugWeb = this.min.core.getParam<boolean>(this.min.instance, 'Debug Web Automation', false);
const botId = min.botId;
GBServer.globals.debuggers[botId] = {};
GBServer.globals.debuggers[botId].state = 1;
GBServer.globals.debuggers[botId].state = 0;
GBServer.globals.debuggers[botId].breaks = [];
}
private client;
public async breakpoint({ botId, botApiKey, line }) {
GBLog.info(`GBDEBUG: Enabled breakpoint for ${botId} on ${line}.`);
GBServer.globals.debuggers[botId].breaks.push(Number.parseInt(line));
}
public async removeBreakPoint({ botId, botApiKey, line }) {
public async resume({ botId, botApiKey, force }) {
const client = GBServer.globals.debuggers[botId].client;
}
public async continueRun({ botId, botApiKey, force }) {
const client = GBServer.globals.debuggers[botId].client;
client.Debugger.resume();
await client.Debugger.resume();
}
public async stop({ botId, botApiKey, force }) {
GBServer.globals.debuggers[botId].state = 0;
const client = GBServer.globals.debuggers[botId].client;
client.close();
await client.close();
}
public async stepOver({ botId, botApiKey }) {
const client = GBServer.globals.debuggers[botId].client;
client.stepOver();
public async step({ botId, botApiKey }) {
if (GBServer.globals.debuggers[botId].state === 2) {
const client = GBServer.globals.debuggers[botId].client;
await client.stepOver();
} else {
throw new GBError(new Error('Invalid call to stepOver and state not being debug(2).'));
}
}
public async getExecutionContext({ botId, botApiKey, force }) {
const client = GBServer.globals.debuggers[botId].client;
public async context({ botId, botApiKey, force }) {
const conversationId = this.conversationsMap[botId];
const response = await client.Conversations.Conversations_GetActivities({
conversationId: conversationId,
watermark: this.watermarkMap[botId]
});
this.watermarkMap[botId] = response.obj.watermark;
let activities = response.obj.activites;
let messages = [];
if (activities && activities.length) {
activities = activities.filter(m => m.from.id === botId && m.type === 'message');
if (activities.length) {
activities.forEach(activity => {
messages.push({ text: activity.text });
GBLog.info(`GBDEBUG: SND TO WORD ${activity.text}`);
});
if (this.client) {
const response = await this.client.Conversations.Conversations_GetActivities({
conversationId: conversationId,
watermark: this.watermarkMap[botId]
});
this.watermarkMap[botId] = response.obj.watermark;
let activities = response.obj.activites;
if (activities && activities.length) {
activities = activities.filter(m => m.from.id === botId && m.type === 'message');
if (activities.length) {
activities.forEach(activity => {
messages.push({ text: activity.text });
GBLog.info(`Debugger sending text to API: ${activity.text}`);
});
}
}
}
return { state: GBServer.globals.debuggers[botId].state, messages, scope: GBServer.globals.debuggers[botId].scope };
let messagesText = messages.join('\n');
return {
state: GBServer.globals.debuggers[botId].state,
messagesText,
scope: GBServer.globals.debuggers[botId].scope
};
}
public async run({ botId, botApiKey, scriptName }) {
GBLog.info(`BASIC: Running ${botId} in DEBUG mode.`);
public async debug({ botId, botApiKey, scriptName }) {
if (GBServer.globals.debuggers[botId].state === 1) {
throw new Error(`Cannot DEBUG an already running process. ${botId}`);
} else if (GBServer.globals.debuggers[botId].state === 2) {
GBLog.info(`BASIC: Releasing execution ${botId} in DEBUG mode.`);
return await this.continueRun({ botId, botApiKey, force: false });
} else {
GBLog.info(`BASIC: Running ${botId} in DEBUG mode.`);
GBServer.globals.debuggers[botId].state = 1;
GBServer.globals.debuggers[botId].state = 1;
let min: GBMinInstance = GBServer.globals.minInstances.filter(p => p.instance.botId === botId)[0];
this.client = await new Swagger({
spec: JSON.parse(fs.readFileSync('directline-3.0.json', 'utf8')),
usePromise: true
});
this.client.clientAuthorizations.add(
'AuthorizationBotConnector',
new Swagger.ApiKeyAuthorization('Authorization', `Bearer ${min.instance.webchatKey}`, 'header')
);
const response = await this.client.Conversations.Conversations_StartConversation();
const conversationId = response.obj.conversationId;
this.conversationsMap[botId] = conversationId;
GBServer.globals.debugConversationId = conversationId;
let min: GBMinInstance = GBServer.globals.minInstances.filter(
p => p.instance.botId === botId
)[0];
this.client = await new Swagger({
spec: JSON.parse(fs.readFileSync('directline-3.0.json', 'utf8')), usePromise: true
});
this.client.clientAuthorizations.add(
'AuthorizationBotConnector',
new Swagger.ApiKeyAuthorization('Authorization', `Bearer ${min.instance.webchatKey}`, 'header')
);
const response = await this.client.Conversations.Conversations_StartConversation();
const conversationId = response.obj.conversationId;
this.conversationsMap[botId] = conversationId;
GBServer.globals.debugConversationId = conversationId;
this.client.Conversations.Conversations_PostActivity({
conversationId: conversationId,
activity: {
textFormat: 'plain',
text: `/call ${scriptName}`,
type: 'message',
from: {
id: 'test',
name: 'test'
this.client.Conversations.Conversations_PostActivity({
conversationId: conversationId,
activity: {
textFormat: 'plain',
text: `/calldbg ${scriptName}`,
type: 'message',
from: {
id: 'test',
name: 'test'
}
}
}
});
});
}
}
}
}

View file

@ -765,7 +765,9 @@ export class GBVMService extends GBService {
/**
* Executes the converted JavaScript from BASIC code inside execution context.
*/
public static async callVM(text: string, min: GBMinInstance, step, GBDialogdeployer: GBDeployer) {
public static async callVM(text: string, min: GBMinInstance, step, GBDialogdeployer: GBDeployer, debug: boolean) {
const debuggerPort = 9222;
// Creates a class DialogKeywords which is the *this* pointer
// in BASIC.
@ -829,7 +831,8 @@ export class GBVMService extends GBService {
const { run } = createVm2Pool({
min: 0,
max: 0,
debuggerPort: 9222,
debug: debug,
debuggerPort: debuggerPort,
botId: botId,
cpu: 100,
memory: 50000,

View file

@ -148,7 +148,7 @@ export class ScheduleServices extends GBService {
let min: GBMinInstance = GBServer.globals.minInstances.filter(
p => p.instance.instanceId === item.instanceId
)[0];
await GBVMService.callVM(script, min, null, null);
await GBVMService.callVM(script, min, null, null, false);
};
(async () => {
await finalData();

View file

@ -99,7 +99,7 @@ export class SystemKeywords {
const step = null;
const deployer = null;
return await GBVMService.callVM(text, min, step, deployer);
return await GBVMService.callVM(text, min, step, deployer, false);
}
public async append({args}) {

View file

@ -6,6 +6,7 @@ const net = require('net');
import { GBLog } from 'botlib';
import { CollectionUtil } from 'pragmatismo-io-framework';
import { GBServer } from '../../../../src/app';
import { DebuggerService } from '../DebuggerService';
const finalStream = require('final-stream');
const waitUntil = condition => {
@ -53,7 +54,7 @@ const createVm2Pool = ({ min, max, ...limits }) => {
limits.cpu,
'--',
'node',
`--inspect=${limits.debuggerPort}`,
`${limits.debug ? '--inspect=' + limits.debuggerPort : ''}`,
`--experimental-fetch`,
`--max-old-space-size=${limits.memory}`,
limits.script,
@ -79,60 +80,71 @@ const createVm2Pool = ({ min, max, ...limits }) => {
});
let socket = null;
await waitUntil(() => childProcess.socket);
socket = net.createConnection(childProcess.socket);
socket.write(JSON.stringify({ code, scope }) + '\n');
// Only attach if called by /debugger/run.
// Only attach if called by debugger/run.
if (GBServer.globals.debuggers[limits.botId]) {
const debug = async () => {
return new Promise((resolve, reject) => {
CDP(async client => {
const { Debugger, Runtime } = client;
try {
GBServer.globals.debuggers[limits.botId].client = client;
await client.Runtime.runIfWaitingForDebugger();
await client.Debugger.enable();
async function mainScript({ Debugger }) {
return new Promise((fulfill, reject) => {
Debugger.scriptParsed(params => {
const { scriptId, url } = params;
fulfill(scriptId);
});
});
}
await CollectionUtil.asyncForEach(GBServer.globals.debuggers[limits.botId].breaks, async brk => {
const scriptId = await mainScript(client);
const { breakpointId } = await client.Debugger.setBreakpoint({
location: {
scriptId,
lineNumber: brk
}
});
GBLog.info(`BASIC break defined ${breakpointId} for ${limits.botId}`);
});
await client.Debugger.paused(({ callFrames, reason, hitBreakpoints }) => {
await client.Debugger.paused(async ({ callFrames, reason, hitBreakpoints }) => {
const frame = callFrames[0];
if (hitBreakpoints.length > 1) {
GBLog.info(`.gbdialog break at line ${frame.location.lineNumber + 1}`); // (zero-based)
const scope = `${frame.scopeChain[0].name} ${frame.scopeChain[0].object}`;
// Build variable list ignoring system variables of script.
GBServer.globals.debuggers[limits.botId].scope = scope;
const scopeObjectId = frame.scopeChain[2].object.objectId;
const variables = await Runtime.getProperties({ objectId: scopeObjectId });
let variablesText = '';
if (variables && variables.result) {
await CollectionUtil.asyncForEach(variables.result, async v => {
if (!DebuggerService.systemVariables.filter(x => x === v.name)[0]) {
if (v.value.value) {
variablesText = `${variablesText} \n ${v.name}: ${v.value.value}`;
}
}
});
}
GBLog.info(`BASIC: Breakpoint variables: ${variablesText}`); // (zero-based)
// Processes breakpoint hits.
if (hitBreakpoints.length >= 1) {
GBLog.info(`BASIC: Break at line ${frame.location.lineNumber + 1}`); // (zero-based)
GBServer.globals.debuggers[limits.botId].scope = variablesText;
GBServer.globals.debuggers[limits.botId].state = 2;
} else if (reason === '') {
GBLog.info(`.gbdialog ${reason} at line ${frame.location.lineNumber + 1}`); // (zero-based)
} else {
GBLog.info(`BASIC: Configuring breakpoints if any for ${limits.botId}`);
// Waits for debugger and setup breakpoints.
await CollectionUtil.asyncForEach(GBServer.globals.debuggers[limits.botId].breaks, async brk => {
try {
const { breakpointId } = await client.Debugger.setBreakpoint({
location: {
scriptId: frame.location.scriptId,
lineNumber: brk
}
});
GBLog.info(`BASIC break defined ${breakpointId} for ${limits.botId}`);
} catch (error) {
GBLog.info(`BASIC error defining defining ${brk} for ${limits.botId}. ${error}`);
}
});
await client.Debugger.resume();
}
});
await client.Runtime.runIfWaitingForDebugger();
await client.Debugger.enable();
await client.Runtime.enable();
resolve(1);
} catch (err) {
GBLog.error(err);

View file

@ -945,7 +945,7 @@ export class GBMinService {
if (startDialog && !user.welcomed) {
user.welcomed = true;
GBLog.info(`Auto start (teams) dialog is now being called: ${startDialog} for ${min.instance.botId}...`);
await GBVMService.callVM(startDialog.toLowerCase(), min, step, this.deployer);
await GBVMService.callVM(startDialog.toLowerCase(), min, step, this.deployer, false);
}
}
}
@ -990,7 +990,7 @@ export class GBMinService {
min["conversationWelcomed"][step.context.activity.conversation.id] = true;
GBLog.info(`Auto start (web 1) dialog is now being called: ${startDialog} for ${min.instance.instanceId}...`);
await GBVMService.callVM(startDialog.toLowerCase(), min, step, this.deployer);
await GBVMService.callVM(startDialog.toLowerCase(), min, step, this.deployer, false);
}
}
@ -1004,7 +1004,7 @@ export class GBMinService {
min["conversationWelcomed"][step.context.activity.conversation.id] = true;
await min.userProfile.set(step.context, user);
GBLog.info(`Auto start (whatsapp) dialog is now being called: ${startDialog} for ${min.instance.instanceId}...`);
await GBVMService.callVM(startDialog.toLowerCase(), min, step, this.deployer);
await GBVMService.callVM(startDialog.toLowerCase(), min, step, this.deployer, false);
}
}
}
@ -1071,7 +1071,7 @@ export class GBMinService {
if (startDialog && !min["conversationWelcomed"][step.context.activity.conversation.id]) {
user.welcomed = true;
GBLog.info(`Auto start (web 2) dialog is now being called: ${startDialog} for ${min.instance.instanceId}...`);
await GBVMService.callVM(startDialog.toLowerCase(), min, step, this.deployer);
await GBVMService.callVM(startDialog.toLowerCase(), min, step, this.deployer, false);
}
} else if (context.activity.name === 'updateToken') {
const token = context.activity.data;
@ -1139,7 +1139,7 @@ export class GBMinService {
const isVMCall = Object.keys(min.scriptMap).find(key => min.scriptMap[key] === context.activity.text) !== undefined;
if (isVMCall) {
await GBVMService.callVM(context.activity.text, min, step, this.deployer);
await GBVMService.callVM(context.activity.text, min, step, this.deployer, false);
} else if (context.activity.text.charAt(0) === '/') {
const text = context.activity.text;
@ -1158,9 +1158,11 @@ export class GBMinService {
await min.userProfile.set(step.context, user);
} else if (cmdOrDialogName === '/call') {
await GBVMService.callVM(args, min, step, this.deployer);
await GBVMService.callVM(args, min, step, this.deployer, false);
} else if (cmdOrDialogName === '/callsch') {
await GBVMService.callVM(args, min, null, null);
await GBVMService.callVM(args, min, null, null, false);
} else if (cmdOrDialogName === '/calldbg') {
await GBVMService.callVM(args, min, step, this.deployer, true);
} else {
await step.beginDialog(cmdOrDialogName, { args: args });
}

View file

@ -179,7 +179,7 @@ export class AskDialog extends IGBDialog {
const startDialog =
min.core.getParam(min.instance, 'Start Dialog', null);
if (startDialog) {
await GBVMService.callVM(startDialog.toLowerCase().trim(), min, step, this.deployer);
await GBVMService.callVM(startDialog.toLowerCase().trim(), min, step, this.deployer, false);
}
return step.endDialog();
@ -304,7 +304,7 @@ export class AskDialog extends IGBDialog {
const mainName = GBVMService.getMethodNameFromVBSFilename(text);
return await GBVMService.callVM(mainName, min, step, this.deployer);
return await GBVMService.callVM(mainName, min, step, this.deployer, false);
} else {
await service.sendAnswer(min, AskDialog.getChannel(step), step, answer);
return await step.replaceDialog('/ask', { isReturning: true });