@scout9/app 1.0.0-alpha.0.4.8 → 1.0.0-alpha.0.5.0
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/dist/{dev-332ccb5a.cjs → dev-5c6f5d76.cjs} +323 -153
- package/dist/{index-2d62ac36.cjs → index-45f6ee5e.cjs} +16 -10
- package/dist/index.cjs +5 -4
- package/dist/{macros-e4105c56.cjs → macros-2f21c706.cjs} +8 -1
- package/dist/{multipart-parser-948d98ce.cjs → multipart-parser-bac8efc8.cjs} +4 -4
- package/dist/schemas.cjs +3 -1
- package/dist/{spirits-5c9243a1.cjs → spirits-2ab4d673.cjs} +14 -2
- package/dist/spirits.cjs +1 -1
- package/dist/testing-tools.cjs +3 -3
- package/package.json +3 -3
- package/src/core/config/commands.js +41 -0
- package/src/core/config/index.js +4 -1
- package/src/core/templates/app.js +466 -76
- package/src/exports.js +1 -0
- package/src/public.d.ts +5 -2
- package/src/runtime/macros/builder.js +6 -3
- package/src/runtime/schemas/conversation.js +1 -0
- package/src/runtime/schemas/workflow.js +7 -0
- package/src/testing-tools/dev.js +371 -360
- package/src/testing-tools/spirits.js +2 -2
- package/types/index.d.ts +625 -618
- package/types/index.d.ts.map +4 -1
|
@@ -2,21 +2,21 @@ import polka from 'polka';
|
|
|
2
2
|
import sirv from 'sirv';
|
|
3
3
|
import compression from 'compression';
|
|
4
4
|
import bodyParser from 'body-parser';
|
|
5
|
-
import colors from 'kleur';
|
|
6
5
|
import { config as dotenv } from 'dotenv';
|
|
7
6
|
import { Configuration, Scout9Api } from '@scout9/admin';
|
|
8
|
-
import { EventResponse } from '@scout9/app';
|
|
7
|
+
import { EventResponse, ProgressLogger } from '@scout9/app';
|
|
9
8
|
import { WorkflowEventSchema, WorkflowResponseSchema } from '@scout9/app/schemas';
|
|
9
|
+
import { Spirits } from '@scout9/app/spirits';
|
|
10
10
|
import path, { resolve } from 'node:path';
|
|
11
11
|
import fs from 'node:fs';
|
|
12
12
|
import https from 'node:https';
|
|
13
13
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
14
|
-
import projectApp from './src/app.js';
|
|
15
|
-
import config from './config.js';
|
|
16
14
|
import { readdir } from 'fs/promises';
|
|
17
15
|
import { ZodError } from 'zod';
|
|
18
16
|
import { fromError } from 'zod-validation-error';
|
|
19
|
-
|
|
17
|
+
import { bgBlack, blue, bold, cyan, green, grey, magenta, red, white } from 'kleur/colors';
|
|
18
|
+
import projectApp from './src/app.js';
|
|
19
|
+
import config from './config.js';
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -80,7 +80,7 @@ const simplifyZodError = (error, tag = undefined) => {
|
|
|
80
80
|
validationError.message = validationError.message.replace('Validation error', tag);
|
|
81
81
|
}
|
|
82
82
|
return validationError;
|
|
83
|
-
}
|
|
83
|
+
};
|
|
84
84
|
|
|
85
85
|
const handleError = (e, res = undefined, tag = undefined, body = undefined) => {
|
|
86
86
|
let name = e?.name || 'Runtime Error';
|
|
@@ -105,7 +105,7 @@ const handleError = (e, res = undefined, tag = undefined, body = undefined) => {
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
if (body) {
|
|
108
|
-
console.log(
|
|
108
|
+
console.log(grey(JSON.stringify(body, null, dev ? 2 : undefined)));
|
|
109
109
|
}
|
|
110
110
|
if (tag && typeof tag === 'string') {
|
|
111
111
|
message = `${tag}: ${message}`;
|
|
@@ -113,12 +113,12 @@ const handleError = (e, res = undefined, tag = undefined, body = undefined) => {
|
|
|
113
113
|
if (typeof e?.constructor?.name === 'string') {
|
|
114
114
|
message = `(${e?.constructor?.name}) ${message}`;
|
|
115
115
|
}
|
|
116
|
-
console.log(
|
|
116
|
+
console.log(red(`${bold(`${code} Error`)}: ${message}`));
|
|
117
117
|
if ('stack' in e) {
|
|
118
|
-
console.log('STACK:',
|
|
118
|
+
console.log('STACK:', grey(e.stack));
|
|
119
119
|
}
|
|
120
120
|
if (body) {
|
|
121
|
-
console.log('INPUT:',
|
|
121
|
+
console.log('INPUT:', grey(JSON.stringify(body, null, dev ? 2 : undefined)));
|
|
122
122
|
}
|
|
123
123
|
if (res) {
|
|
124
124
|
res.writeHead(code, {'Content-Type': 'application/json'});
|
|
@@ -130,7 +130,16 @@ const handleError = (e, res = undefined, tag = undefined, body = undefined) => {
|
|
|
130
130
|
}
|
|
131
131
|
};
|
|
132
132
|
|
|
133
|
-
const handleZodError = ({
|
|
133
|
+
const handleZodError = ({
|
|
134
|
+
error,
|
|
135
|
+
res = undefined,
|
|
136
|
+
code = 500,
|
|
137
|
+
status,
|
|
138
|
+
name,
|
|
139
|
+
bodyLabel = 'Provided Input',
|
|
140
|
+
body = undefined,
|
|
141
|
+
action = ''
|
|
142
|
+
}) => {
|
|
134
143
|
res?.writeHead?.(code, {'Content-Type': 'application/json'});
|
|
135
144
|
if (error instanceof ZodError) {
|
|
136
145
|
const formattedError = simplifyZodError(error);
|
|
@@ -139,12 +148,12 @@ const handleZodError = ({error, res = undefined, code = 500, status, name, bodyL
|
|
|
139
148
|
error: formattedError.message,
|
|
140
149
|
errors: [formattedError.message]
|
|
141
150
|
}));
|
|
142
|
-
console.log(
|
|
151
|
+
console.log(red(`${bold(`${name}`)}:`));
|
|
143
152
|
if (body) {
|
|
144
|
-
console.log(
|
|
145
|
-
console.log(
|
|
153
|
+
console.log(grey(`${bodyLabel}:`));
|
|
154
|
+
console.log(grey(JSON.stringify(body, null, dev ? 2 : undefined)));
|
|
146
155
|
}
|
|
147
|
-
console.log(
|
|
156
|
+
console.log(red(`${action}${formattedError}`));
|
|
148
157
|
} else {
|
|
149
158
|
console.error(error);
|
|
150
159
|
error.message = `${name}: ` + error.message;
|
|
@@ -253,7 +262,7 @@ app.post(dev ? '/dev/workflow' : '/', async (req, res) => {
|
|
|
253
262
|
} else {
|
|
254
263
|
return response;
|
|
255
264
|
}
|
|
256
|
-
})
|
|
265
|
+
});
|
|
257
266
|
} catch (error) {
|
|
258
267
|
if (error instanceof ZodError) {
|
|
259
268
|
handleZodError({
|
|
@@ -278,8 +287,8 @@ app.post(dev ? '/dev/workflow' : '/', async (req, res) => {
|
|
|
278
287
|
try {
|
|
279
288
|
const formattedResponse = WorkflowResponseSchema.parse(response);
|
|
280
289
|
if (dev) {
|
|
281
|
-
console.log(
|
|
282
|
-
console.log(
|
|
290
|
+
console.log(green(`Workflow Sending Response:`));
|
|
291
|
+
console.log(grey(JSON.stringify(formattedResponse, null, 2)));
|
|
283
292
|
}
|
|
284
293
|
res.writeHead(200, {'Content-Type': 'application/json'});
|
|
285
294
|
res.end(JSON.stringify(formattedResponse));
|
|
@@ -402,7 +411,7 @@ async function runEntityApi(req, res) {
|
|
|
402
411
|
res.writeHead(response.status || 200, {'Content-Type': 'application/json'});
|
|
403
412
|
res.end(JSON.stringify(data));
|
|
404
413
|
console.log(`${req.method} EntityApi.${lastSegment}:`);
|
|
405
|
-
console.log(
|
|
414
|
+
console.log(grey(JSON.stringify(data)));
|
|
406
415
|
} else {
|
|
407
416
|
throw new Error(`Invalid response: not an EventResponse`);
|
|
408
417
|
}
|
|
@@ -429,12 +438,13 @@ async function getFilesRecursive(dir) {
|
|
|
429
438
|
return results;
|
|
430
439
|
}
|
|
431
440
|
|
|
441
|
+
|
|
442
|
+
const commandsDir = resolve(__dirname, `./src/commands`);
|
|
443
|
+
|
|
432
444
|
async function runCommandApi(req, res) {
|
|
433
445
|
let file;
|
|
434
446
|
const {body, url} = req;
|
|
435
447
|
const params = url.split('/').slice(2).filter(Boolean);
|
|
436
|
-
const commandsDir = resolve(__dirname, `./src/commands`);
|
|
437
|
-
|
|
438
448
|
try {
|
|
439
449
|
const files = await getFilesRecursive(commandsDir).then(files => files.map(file => file.replace(commandsDir, '.'))
|
|
440
450
|
.filter(file => params.every(p => file.includes(p))));
|
|
@@ -481,7 +491,14 @@ async function runCommandApi(req, res) {
|
|
|
481
491
|
let result;
|
|
482
492
|
|
|
483
493
|
try {
|
|
484
|
-
result = await mod.default(body)
|
|
494
|
+
result = await mod.default(body)
|
|
495
|
+
.then((response) => {
|
|
496
|
+
if ('toJSON' in response) {
|
|
497
|
+
return response.toJSON();
|
|
498
|
+
} else {
|
|
499
|
+
return response;
|
|
500
|
+
}
|
|
501
|
+
});
|
|
485
502
|
} catch (e) {
|
|
486
503
|
console.error('Failed to run command', e);
|
|
487
504
|
res.writeHead(500, {'Content-Type': 'application/json'});
|
|
@@ -523,6 +540,7 @@ app.post('/entity/:entity/*', runEntityApi);
|
|
|
523
540
|
app.delete('/entity/:entity/*', runEntityApi);
|
|
524
541
|
|
|
525
542
|
// For local development: parse a message
|
|
543
|
+
let devProgram;
|
|
526
544
|
if (dev) {
|
|
527
545
|
|
|
528
546
|
app.get('/dev/config', async (req, res, next) => {
|
|
@@ -543,14 +561,14 @@ if (dev) {
|
|
|
543
561
|
if (!cache.isTested()) {
|
|
544
562
|
const testableEntities = config.entities.filter(e => e?.definitions?.length > 0 || e?.training?.length > 0);
|
|
545
563
|
if (dev && testableEntities.length > 0) {
|
|
546
|
-
console.log(`${
|
|
564
|
+
console.log(`${grey(`${cyan('>')} Testing ${bold(white(testableEntities.length))} Entities...`)}`);
|
|
547
565
|
const _res = await scout9.parse({
|
|
548
566
|
message: 'Dummy message to parse',
|
|
549
567
|
language: 'en',
|
|
550
568
|
entities: testableEntities
|
|
551
569
|
});
|
|
552
570
|
cache.setTested();
|
|
553
|
-
console.log(`\t${
|
|
571
|
+
console.log(`\t${green(`+ ${testableEntities.length} Entities passed`)}`);
|
|
554
572
|
}
|
|
555
573
|
}
|
|
556
574
|
} catch (e) {
|
|
@@ -558,25 +576,30 @@ if (dev) {
|
|
|
558
576
|
}
|
|
559
577
|
});
|
|
560
578
|
|
|
579
|
+
const devParse = async (message, language = 'en') => {
|
|
580
|
+
if (typeof message !== 'string') {
|
|
581
|
+
throw new Error('Invalid message - expected to be a string');
|
|
582
|
+
}
|
|
583
|
+
console.log(`${grey(`${cyan('>')} Parsing "${bold(white(message))}`)}"`);
|
|
584
|
+
const payload = await scout9.parse({
|
|
585
|
+
message,
|
|
586
|
+
language,
|
|
587
|
+
entities: config.entities
|
|
588
|
+
}).then((_res => _res.data));
|
|
589
|
+
let fields = '';
|
|
590
|
+
for (const [key, value] of Object.entries(payload.context)) {
|
|
591
|
+
fields += `\n\t\t${bold(white(key))}: ${grey(JSON.stringify(value))}`;
|
|
592
|
+
}
|
|
593
|
+
console.log(`\tParsed in ${payload.ms}ms:${grey(`${fields}`)}:`);
|
|
594
|
+
console.log(grey(JSON.stringify(payload)));
|
|
595
|
+
return payload;
|
|
596
|
+
};
|
|
597
|
+
|
|
561
598
|
app.post('/dev/parse', async (req, res, next) => {
|
|
562
599
|
try {
|
|
563
600
|
// req.body: {message: string}
|
|
564
|
-
const {message, language} = req.body;
|
|
565
|
-
|
|
566
|
-
throw new Error('Invalid message - expected to be a string');
|
|
567
|
-
}
|
|
568
|
-
console.log(`${colors.grey(`${colors.cyan('>')} Parsing "${colors.bold(colors.white(message))}`)}"`);
|
|
569
|
-
const payload = await scout9.parse({
|
|
570
|
-
message,
|
|
571
|
-
language: 'en',
|
|
572
|
-
entities: config.entities
|
|
573
|
-
}).then((_res => _res.data));
|
|
574
|
-
let fields = '';
|
|
575
|
-
for (const [key, value] of Object.entries(payload.context)) {
|
|
576
|
-
fields += `\n\t\t${colors.bold(colors.white(key))}: ${colors.grey(JSON.stringify(value))}`;
|
|
577
|
-
}
|
|
578
|
-
console.log(`\tParsed in ${payload.ms}ms:${colors.grey(`${fields}`)}:`);
|
|
579
|
-
console.log(colors.grey(JSON.stringify(payload)));
|
|
601
|
+
const {message, language = 'en'} = req.body;
|
|
602
|
+
const payload = await devParse(message, language);
|
|
580
603
|
res.writeHead(200, {'Content-Type': 'application/json'});
|
|
581
604
|
res.end(JSON.stringify(payload));
|
|
582
605
|
} catch (e) {
|
|
@@ -584,13 +607,18 @@ if (dev) {
|
|
|
584
607
|
}
|
|
585
608
|
});
|
|
586
609
|
|
|
610
|
+
const devForward = async (convo) => {
|
|
611
|
+
console.log(`${grey(`${cyan('>')} Forwarding...`)}`);
|
|
612
|
+
const payload = await scout9.forward({convo}).then((_res => _res.data));
|
|
613
|
+
console.log(`\tForwarded in ${payload?.ms}ms`);
|
|
614
|
+
return payload;
|
|
615
|
+
};
|
|
616
|
+
|
|
587
617
|
app.post('/dev/forward', async (req, res, next) => {
|
|
588
618
|
try {
|
|
589
619
|
// req.body: {message: string}
|
|
590
620
|
const {convo} = req.body;
|
|
591
|
-
|
|
592
|
-
const payload = await scout9.forward({convo}).then((_res => _res.data));
|
|
593
|
-
console.log(`\tForwarded in ${payload?.ms}ms`);
|
|
621
|
+
const payload = await devForward(convo);
|
|
594
622
|
res.writeHead(200, {'Content-Type': 'application/json'});
|
|
595
623
|
res.end(JSON.stringify(payload));
|
|
596
624
|
} catch (e) {
|
|
@@ -598,40 +626,399 @@ if (dev) {
|
|
|
598
626
|
}
|
|
599
627
|
});
|
|
600
628
|
|
|
629
|
+
const devGenerate = async (messages, personaId) => {
|
|
630
|
+
if (typeof messages !== 'object' || !Array.isArray(messages)) {
|
|
631
|
+
throw new Error('Invalid messages array - expected to be an array of objects');
|
|
632
|
+
}
|
|
633
|
+
if (typeof personaId !== 'string') {
|
|
634
|
+
throw new Error('Invalid persona - expected to be a string');
|
|
635
|
+
}
|
|
636
|
+
const persona = (config.persona || config.agents).find(p => p.id === personaId);
|
|
637
|
+
if (!persona) {
|
|
638
|
+
throw new Error(`Could not find persona with id: ${personaId}, ensure your project is sync'd by running "scout9 sync"`);
|
|
639
|
+
}
|
|
640
|
+
console.log(`${grey(`${cyan('>')} Determining ${bold(white(persona.firstName))}'s`)} response`);
|
|
641
|
+
const payload = await scout9.generate({
|
|
642
|
+
messages,
|
|
643
|
+
persona,
|
|
644
|
+
llm: config.llm,
|
|
645
|
+
pmt: config.pmt
|
|
646
|
+
}).then((_res => _res.data));
|
|
647
|
+
console.log(`\t${grey(`Response: ${green('"')}${bold(white(payload.message))}`)}${green(
|
|
648
|
+
'"')} (elapsed ${payload.ms}ms)`);
|
|
649
|
+
|
|
650
|
+
return payload;
|
|
651
|
+
};
|
|
652
|
+
|
|
601
653
|
app.post('/dev/generate', async (req, res, next) => {
|
|
602
654
|
try {
|
|
603
655
|
// req.body: {conversation: {}, messages: []}
|
|
604
656
|
const {messages, persona: personaId} = req.body;
|
|
605
|
-
|
|
606
|
-
throw new Error('Invalid messages array - expected to be an array of objects');
|
|
607
|
-
}
|
|
608
|
-
if (typeof personaId !== 'string') {
|
|
609
|
-
throw new Error('Invalid persona - expected to be a string');
|
|
610
|
-
}
|
|
611
|
-
const persona = (config.persona || config.agents).find(p => p.id === personaId);
|
|
612
|
-
if (!persona) {
|
|
613
|
-
throw new Error(`Could not find persona with id: ${personaId}, ensure your project is sync'd by running "scout9 sync"`);
|
|
614
|
-
}
|
|
615
|
-
console.log(`${colors.grey(`${colors.cyan('>')} Generating ${colors.bold(colors.white(persona.firstName))}'s`)} ${colors.bold(
|
|
616
|
-
colors.red(colors.bgBlack('auto-reply')))}`);
|
|
617
|
-
const payload = await scout9.generate({
|
|
618
|
-
messages,
|
|
619
|
-
persona,
|
|
620
|
-
llm: config.llm,
|
|
621
|
-
pmt: config.pmt
|
|
622
|
-
}).then((_res => _res.data));
|
|
623
|
-
console.log(`\t${colors.grey(`Response: ${colors.green('"')}${colors.bold(colors.white(payload.message))}`)}${colors.green(
|
|
624
|
-
'"')} (elapsed ${payload.ms}ms)`);
|
|
657
|
+
const payload = await devGenerate(messages, personaId);
|
|
625
658
|
res.writeHead(200, {'Content-Type': 'application/json'});
|
|
626
659
|
res.end(JSON.stringify(payload));
|
|
627
660
|
} catch (e) {
|
|
628
661
|
handleError(e, res);
|
|
629
662
|
}
|
|
630
663
|
});
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
// NOTE: This does not sync with localhost app, that uses its own state
|
|
667
|
+
devProgram = async () => {
|
|
668
|
+
// Start program where use can test via command console
|
|
669
|
+
const {createInterface} = await import('node:readline');
|
|
670
|
+
const rl = createInterface({
|
|
671
|
+
input: process.stdin,
|
|
672
|
+
output: process.stdout
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
const persona = (config.persona || config.agents)?.[0];
|
|
676
|
+
if (!persona) {
|
|
677
|
+
throw new Error(`A persona is required before processing`);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* @returns {Omit<WorkflowEvent, 'message'> & {command?: CommandConfiguration}}
|
|
682
|
+
*/
|
|
683
|
+
const createState = () => ({
|
|
684
|
+
messages: [],
|
|
685
|
+
conversation: {
|
|
686
|
+
$id: 'dev_console_input',
|
|
687
|
+
$agent: persona.id,
|
|
688
|
+
$customer: 'temp',
|
|
689
|
+
environment: 'web'
|
|
690
|
+
},
|
|
691
|
+
context: {},
|
|
692
|
+
agent: persona,
|
|
693
|
+
customer: {
|
|
694
|
+
firstName: 'test',
|
|
695
|
+
name: 'test'
|
|
696
|
+
},
|
|
697
|
+
intent: {current: null, flow: [], initial: null},
|
|
698
|
+
stagnationCount: 0
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
/** @type {Omit<WorkflowEvent, 'message'> & {command?: CommandConfiguration}} */
|
|
702
|
+
let state = createState();
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
async function processCustomerMessage(message, callback) {
|
|
706
|
+
const messagePayload = {
|
|
707
|
+
id: `user_test_${Date.now()}`,
|
|
708
|
+
role: 'customer',
|
|
709
|
+
content: message,
|
|
710
|
+
time: new Date().toISOString()
|
|
711
|
+
};
|
|
712
|
+
const logger = new ProgressLogger('Processing...');
|
|
713
|
+
if (state.conversation.locked) {
|
|
714
|
+
logger.error(`Conversation locked - ${state.conversation.lockedReason ?? 'Unknown reason'}`);
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
const addMessage = (payload) => {
|
|
718
|
+
state.messages.push(payload);
|
|
719
|
+
switch (payload.role) {
|
|
720
|
+
case 'system':
|
|
721
|
+
logger.write(magenta('system: ' + payload.content));
|
|
722
|
+
break;
|
|
723
|
+
case 'user':
|
|
724
|
+
case 'customer':
|
|
725
|
+
logger.write(green(`> ${state.agent.firstName ? state.agent.firstName + ': ' : ''}` + payload.content));
|
|
726
|
+
break;
|
|
727
|
+
case 'agent':
|
|
728
|
+
case 'assistant':
|
|
729
|
+
logger.write(blue(`> ${state.customer.name ? state.customer.name + ': ' : ''}` + payload.content));
|
|
730
|
+
break;
|
|
731
|
+
default:
|
|
732
|
+
logger.write(red(`UNKNOWN (${payload.role}) + ${payload.content}`));
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
const updateMessage = (payload) => {
|
|
737
|
+
const index = state.messages.findIndex(m => m.id === payload.id);
|
|
738
|
+
if (index < 0) {
|
|
739
|
+
throw new Error(`Cannot find message ${payload.id}`);
|
|
740
|
+
}
|
|
741
|
+
state.messages[index] = payload;
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
const removeMessage = (payload) => {
|
|
745
|
+
if (typeof payload !== 'string') {
|
|
746
|
+
throw new Error(`Invalid payload`);
|
|
747
|
+
}
|
|
748
|
+
const index = state.messages.findIndex(m => m.id === payload.id);
|
|
749
|
+
if (index < 0) {
|
|
750
|
+
throw new Error(`Cannot find message ${payload.id}`);
|
|
751
|
+
}
|
|
752
|
+
state.messages.splice(index, 1);
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
const updateConversation = (payload) => {
|
|
756
|
+
Object.assign(state.conversation, payload);
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
const updateContext = (payload) => {
|
|
760
|
+
Object.assign(state.context, payload);
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
addMessage(messagePayload);
|
|
764
|
+
const result = await Spirits.customer({
|
|
765
|
+
customer: state.customer,
|
|
766
|
+
config,
|
|
767
|
+
parser: async (_msg, _lng) => {
|
|
768
|
+
logger.log(`Parsing...`);
|
|
769
|
+
return devParse(_msg, _lng);
|
|
770
|
+
},
|
|
771
|
+
workflow: async (workflowEvent) => {
|
|
772
|
+
// Set the global variables for the workflows/commands to run Scout9 Macros
|
|
773
|
+
globalThis.SCOUT9 = {
|
|
774
|
+
...workflowEvent,
|
|
775
|
+
$convo: state.conversation.$id ?? state.conversation.id
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
logger.log(`Gathering ${state.command ? 'Command ' + state.command.entity + ' ' : ''}instructions...`);
|
|
779
|
+
if (state.command) {
|
|
780
|
+
const commandFilePath = resolve(commandsDir, state.command.path);
|
|
781
|
+
let mod;
|
|
782
|
+
try {
|
|
783
|
+
mod = await import(commandFilePath);
|
|
784
|
+
} catch (e) {
|
|
785
|
+
logger.error(`Unable to resolve command ${state.command.entity} at ${commandFilePath}`);
|
|
786
|
+
throw new Error('Failed to gather command instructions');
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
if (!mod || !mod.default) {
|
|
790
|
+
logger.error(`Unable to run command ${state.command.entity} at ${commandFilePath} - must return a default function that returns a WorkflowEvent payload`);
|
|
791
|
+
throw new Error('Failed to run command instructions');
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
try {
|
|
795
|
+
|
|
796
|
+
return mod.default(workflowEvent)
|
|
797
|
+
.then((response) => {
|
|
798
|
+
if ('toJSON' in response) {
|
|
799
|
+
return response.toJSON();
|
|
800
|
+
} else {
|
|
801
|
+
return response;
|
|
802
|
+
}
|
|
803
|
+
})
|
|
804
|
+
.then(WorkflowResponseSchema.parse);
|
|
805
|
+
} catch (e) {
|
|
806
|
+
logger.error(`Failed to run command - ${e.message}`);
|
|
807
|
+
throw e;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
} else {
|
|
811
|
+
return projectApp(workflowEvent)
|
|
812
|
+
.then((response) => {
|
|
813
|
+
if ('toJSON' in response) {
|
|
814
|
+
return response.toJSON();
|
|
815
|
+
} else {
|
|
816
|
+
return response;
|
|
817
|
+
}
|
|
818
|
+
})
|
|
819
|
+
.then(WorkflowResponseSchema.parse);
|
|
820
|
+
}
|
|
821
|
+
},
|
|
822
|
+
generator: async (request) => {
|
|
823
|
+
logger.log(`Determining response...`);
|
|
824
|
+
const personaId = typeof request.persona === 'string' ? request.persona : request.persona.id;
|
|
825
|
+
return devGenerate(request.messages, personaId);
|
|
826
|
+
},
|
|
827
|
+
idGenerator: (prefix) => `${prefix}_test_${Date.now()}`,
|
|
828
|
+
progress: (
|
|
829
|
+
message,
|
|
830
|
+
level,
|
|
831
|
+
type,
|
|
832
|
+
payload
|
|
833
|
+
) => {
|
|
834
|
+
callback(message, level);
|
|
835
|
+
if (type) {
|
|
836
|
+
switch (type) {
|
|
837
|
+
case 'ADD_MESSAGE':
|
|
838
|
+
addMessage(payload);
|
|
839
|
+
break;
|
|
840
|
+
case 'UPDATE_MESSAGE':
|
|
841
|
+
updateMessage(payload);
|
|
842
|
+
break;
|
|
843
|
+
case 'REMOVE_MESSAGE':
|
|
844
|
+
removeMessage(payload);
|
|
845
|
+
break;
|
|
846
|
+
case 'UPDATE_CONVERSATION':
|
|
847
|
+
updateConversation(payload);
|
|
848
|
+
break;
|
|
849
|
+
case 'UPDATE_CONTEXT':
|
|
850
|
+
updateContext(payload);
|
|
851
|
+
break;
|
|
852
|
+
case 'SET_PROCESSING':
|
|
853
|
+
break;
|
|
854
|
+
default:
|
|
855
|
+
throw new Error(`Unknown progress type: ${type}`);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
},
|
|
859
|
+
message: messagePayload,
|
|
860
|
+
context: state.context,
|
|
861
|
+
messages: state.messages,
|
|
862
|
+
conversation: state.conversation
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
// If a forward happens (due to a lock or other reason)
|
|
866
|
+
if (!!result.conversation.forward) {
|
|
867
|
+
if (!state.conversation.locked) {
|
|
868
|
+
// Only forward if conversation is not already locked
|
|
869
|
+
await devForward(state.conversation.$id);
|
|
870
|
+
}
|
|
871
|
+
updateConversation({locked: true});
|
|
872
|
+
logger.error(`Conversation locked`);
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// Process changes as a success
|
|
877
|
+
|
|
878
|
+
// Update conversation (assuming it's changed)
|
|
879
|
+
if (result.conversation.after) {
|
|
880
|
+
updateConversation(result.conversation.after);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// Update conversation context (assuming it's changed)
|
|
884
|
+
if (result.context) {
|
|
885
|
+
updateContext(result.context.after);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
if (!result.messages.after.find(m => m.id === result.message.after.id)) {
|
|
889
|
+
console.error(`Message not found in result.messages.after`, result.message.after.id);
|
|
890
|
+
result.messages.after.push(result.message.after);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// Sync messages state update/add/delete
|
|
894
|
+
for (const message of result.messages.after) {
|
|
895
|
+
// Did this exist?
|
|
896
|
+
const existed = !!result.messages.before.find(m => m.id === message.id);
|
|
897
|
+
if (existed) {
|
|
898
|
+
updateMessage(message);
|
|
899
|
+
} else {
|
|
900
|
+
addMessage(message);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
for (const message of result.messages.before) {
|
|
904
|
+
const exists = !!result.messages.after.find(m => m.id === message.id);
|
|
905
|
+
if (!exists) {
|
|
906
|
+
removeMessage(message.id);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
logger.done();
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* @param {CommandConfiguration} command
|
|
915
|
+
* @param {string} message
|
|
916
|
+
* @param callback
|
|
917
|
+
* @returns {Promise<void>}
|
|
918
|
+
*/
|
|
919
|
+
async function processCommand(command, message, callback) {
|
|
920
|
+
console.log(magenta(`> command <${command.entity}>`));
|
|
921
|
+
state = createState();
|
|
922
|
+
state.command = command;
|
|
923
|
+
return processCustomerMessage(`Assist me in this ${command.entity} flow`, callback);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
async function devProgramProcessInput(message, callback) {
|
|
927
|
+
// Check if internal command
|
|
928
|
+
switch (message.toLowerCase().trim()) {
|
|
929
|
+
case 'context':
|
|
930
|
+
console.log(white('> Current Conversation Context:'));
|
|
931
|
+
console.log(grey(JSON.stringify(state.context)));
|
|
932
|
+
return;
|
|
933
|
+
case 'conversation':
|
|
934
|
+
case 'convo':
|
|
935
|
+
console.log(white('> Current Conversation State:'));
|
|
936
|
+
console.log(grey(JSON.stringify(state.conversation)));
|
|
937
|
+
return;
|
|
938
|
+
case 'messages':
|
|
939
|
+
state.messages.forEach((msg) => {
|
|
940
|
+
switch (msg.role) {
|
|
941
|
+
case 'system':
|
|
942
|
+
console.log(magenta('\t - ' + msg.content));
|
|
943
|
+
break;
|
|
944
|
+
case 'user':
|
|
945
|
+
case 'customer':
|
|
946
|
+
console.log(green('> ' + msg.content));
|
|
947
|
+
break;
|
|
948
|
+
case 'agent':
|
|
949
|
+
case 'assistant':
|
|
950
|
+
console.log(blue('> ' + msg.content));
|
|
951
|
+
break;
|
|
952
|
+
default:
|
|
953
|
+
console.log(red(`UNKNOWN (${msg.role}) + ${msg.content}`));
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// Check if it's a command
|
|
960
|
+
const target = message.toLowerCase().trim();
|
|
961
|
+
const command = config.commands.find(command => {
|
|
962
|
+
return command.entity === target;
|
|
963
|
+
});
|
|
964
|
+
// Run the command
|
|
965
|
+
if (command) {
|
|
966
|
+
return processCommand(command, message, callback);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// Otherwise default to processing customer message
|
|
970
|
+
return processCustomerMessage(message, callback);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// Function to ask for input, perform the task, and then ask again
|
|
974
|
+
function promptUser() {
|
|
975
|
+
rl.question('> ', async (input) => {
|
|
976
|
+
if (input.toLowerCase() === 'exit') {
|
|
977
|
+
rl.close();
|
|
978
|
+
} else {
|
|
979
|
+
if (input) {
|
|
980
|
+
await devProgramProcessInput(input, () => {
|
|
981
|
+
//
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
promptUser();
|
|
985
|
+
}
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
|
|
990
|
+
|
|
991
|
+
console.log(grey(`\nThe following ${bold('commands')} are available...`));
|
|
992
|
+
[['context', 'logs the state context inserted into the conversation'], ['conversation', 'logs conversation details'], ['messages', 'logs all message history']].forEach(([command, description]) => {
|
|
993
|
+
console.log(`\t - ${magenta(command)} ${grey(description)}`);
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
if (config.commands.length) {
|
|
997
|
+
console.log(grey(`\nThe following ${bold('custom commands')} are available...`));
|
|
998
|
+
}
|
|
999
|
+
config.commands.forEach((command) => {
|
|
1000
|
+
console.log(magenta(`\t - ${command.entity}`));
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
// Start the first prompt
|
|
1004
|
+
|
|
1005
|
+
console.log(white(`\nType and hit enter to test your PMT responses...\n`));
|
|
1006
|
+
promptUser();
|
|
1007
|
+
|
|
1008
|
+
// Handle Ctrl+C (SIGINT) signal to exit gracefully
|
|
1009
|
+
rl.on('SIGINT', () => {
|
|
1010
|
+
rl.close();
|
|
1011
|
+
process.exit(0);
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
|
|
1015
|
+
};
|
|
1016
|
+
|
|
1017
|
+
|
|
631
1018
|
}
|
|
632
1019
|
|
|
633
1020
|
|
|
634
|
-
app.listen(process.env.PORT || 8080, err => {
|
|
1021
|
+
app.listen(process.env.PORT || 8080, async (err) => {
|
|
635
1022
|
if (err) throw err;
|
|
636
1023
|
|
|
637
1024
|
const art_scout9 = `
|
|
@@ -647,6 +1034,8 @@ app.listen(process.env.PORT || 8080, err => {
|
|
|
647
1034
|
\\|_________|
|
|
648
1035
|
`;
|
|
649
1036
|
const art_pmt = `
|
|
1037
|
+
|
|
1038
|
+
|
|
650
1039
|
_______ __ __ ________
|
|
651
1040
|
| \ | \ / \| \
|
|
652
1041
|
| $$$$$$$\| $$\ / $$ \$$$$$$$$
|
|
@@ -657,34 +1046,35 @@ app.listen(process.env.PORT || 8080, err => {
|
|
|
657
1046
|
| $$ | $$ \$ | $$ | $$
|
|
658
1047
|
\$$ \$$ \$$ \$$
|
|
659
1048
|
|
|
660
|
-
|
|
661
|
-
|
|
662
1049
|
`;
|
|
663
1050
|
const protocol = process.env.PROTOCOL || 'http';
|
|
664
1051
|
const host = process.env.HOST || 'localhost';
|
|
665
1052
|
const port = process.env.PORT || 8080;
|
|
666
1053
|
const fullUrl = `${protocol}://${host}:${port}`;
|
|
667
1054
|
if (dev) {
|
|
668
|
-
console.log(
|
|
669
|
-
console.log(
|
|
670
|
-
console.log(`${
|
|
671
|
-
colors.red(colors.bgBlack('auto-reply')))} ${colors.grey('dev environment on')} ${fullUrl}`);
|
|
1055
|
+
console.log(bold(green(art_scout9)));
|
|
1056
|
+
console.log(bold(cyan(art_pmt)));
|
|
1057
|
+
console.log(`${grey(`${cyan('>')} Running ${bold(white('Scout9'))}`)} ${grey('dev environment on')} ${fullUrl}`);
|
|
672
1058
|
} else {
|
|
673
|
-
console.log(`Running Scout9
|
|
1059
|
+
console.log(`Running Scout9 app on ${fullUrl}`);
|
|
674
1060
|
}
|
|
675
1061
|
// Run checks
|
|
676
1062
|
if (!fs.existsSync(configFilePath)) {
|
|
677
|
-
console.log(
|
|
1063
|
+
console.log(red('Missing .env file, your PMT application may not work without it.'));
|
|
678
1064
|
}
|
|
679
1065
|
|
|
680
1066
|
if (dev && !process.env.SCOUT9_API_KEY) {
|
|
681
|
-
console.log(
|
|
682
|
-
'Missing SCOUT9_API_KEY environment variable, your
|
|
1067
|
+
console.log(red(
|
|
1068
|
+
'Missing SCOUT9_API_KEY environment variable, your PMT application may not work without it.'));
|
|
683
1069
|
}
|
|
684
1070
|
|
|
685
1071
|
if (process.env.SCOUT9_API_KEY === '<insert-scout9-api-key>') {
|
|
686
|
-
console.log(`${
|
|
687
|
-
'You can find your API key in the Scout9 dashboard.')} ${
|
|
1072
|
+
console.log(`${red('SCOUT9_API_KEY has not been set in your .env file.')} ${grey(
|
|
1073
|
+
'You can find your API key in the Scout9 dashboard.')} ${bold(cyan('https://scout9.com'))}`);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
if (dev) {
|
|
1077
|
+
devProgram();
|
|
688
1078
|
}
|
|
689
1079
|
|
|
690
1080
|
});
|