new(all): Alpha Word Debugger for 3.0.
This commit is contained in:
parent
24af00cff6
commit
b5da93b5cf
7 changed files with 231 additions and 124 deletions
|
@ -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'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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}) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
|
|
|
@ -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 });
|
||||
|
|
Loading…
Add table
Reference in a new issue