@sprucelabs/spruce-conversation-plugin 56.0.38 → 56.1.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.
Files changed (27) hide show
  1. package/build/__tests__/behavioral/RegisteringConversationsOnBoot.test.d.ts +2 -0
  2. package/build/__tests__/behavioral/RegisteringConversationsOnBoot.test.js +42 -5
  3. package/build/__tests__/implementation/ConversationCoordinator.test.d.ts +2 -0
  4. package/build/__tests__/implementation/ConversationCoordinator.test.js +24 -2
  5. package/build/__tests__/implementation/TopicScriptPlayer.test.d.ts +1 -0
  6. package/build/__tests__/implementation/TopicScriptPlayer.test.js +26 -7
  7. package/build/conversations/ConversationCoordinator.d.ts +16 -3
  8. package/build/conversations/ConversationCoordinator.js +8 -4
  9. package/build/esm/__tests__/behavioral/RegisteringConversationsOnBoot.test.d.ts +2 -0
  10. package/build/esm/__tests__/behavioral/RegisteringConversationsOnBoot.test.js +47 -6
  11. package/build/esm/__tests__/implementation/ConversationCoordinator.test.d.ts +2 -0
  12. package/build/esm/__tests__/implementation/ConversationCoordinator.test.js +26 -2
  13. package/build/esm/__tests__/implementation/TopicScriptPlayer.test.d.ts +1 -0
  14. package/build/esm/__tests__/implementation/TopicScriptPlayer.test.js +30 -9
  15. package/build/esm/conversations/ConversationCoordinator.d.ts +16 -3
  16. package/build/esm/conversations/ConversationCoordinator.js +8 -4
  17. package/build/esm/plugins/conversation.plugin.js +1 -0
  18. package/build/esm/tests/ScriptTester.js +1 -0
  19. package/build/esm/topics/TopicScriptPlayer.d.ts +1 -0
  20. package/build/esm/topics/TopicScriptPlayer.js +17 -25
  21. package/build/esm/types/conversation.types.d.ts +3 -1
  22. package/build/plugins/conversation.plugin.js +1 -0
  23. package/build/tests/ScriptTester.js +1 -0
  24. package/build/topics/TopicScriptPlayer.d.ts +1 -0
  25. package/build/topics/TopicScriptPlayer.js +16 -24
  26. package/build/types/conversation.types.d.ts +3 -1
  27. package/package.json +5 -5
@@ -6,6 +6,8 @@ export default class RegisteringConversationsOnBootTest extends AbstractConversa
6
6
  protected static skillShutsDownWhenConvosFailToRegister(): Promise<void>;
7
7
  protected static canBootASecondTime(): Promise<void>;
8
8
  protected static skillCanBootASecondTime(): Promise<void>;
9
+ protected static coordinaterGetsSkillContext(): Promise<void>;
10
+ private static registerBootAndConnect;
9
11
  private static assertExpectedTopics;
10
12
  private static registerAndBoot;
11
13
  }
@@ -24,12 +24,12 @@ class RegisteringConversationsOnBootTest extends AbstractConversationTest_1.defa
24
24
  }
25
25
  static async noConvosToStart() {
26
26
  this.cwd = this.resolveTestPath('empty-skill');
27
- const topics = await this.registerAndBoot();
27
+ const { topics } = await this.registerAndBoot();
28
28
  test_utils_1.assert.isLength(topics, 0);
29
29
  }
30
30
  static async registersConvosOnBoot() {
31
31
  this.cwd = this.resolveTestPath('skill');
32
- const topics = await this.registerAndBoot();
32
+ const { topics } = await this.registerAndBoot();
33
33
  this.assertExpectedTopics(topics);
34
34
  }
35
35
  static async skillShutsDownWhenConvosFailToRegister() {
@@ -39,9 +39,9 @@ class RegisteringConversationsOnBootTest extends AbstractConversationTest_1.defa
39
39
  }
40
40
  static async canBootASecondTime() {
41
41
  this.cwd = this.resolveTestPath('skill');
42
- const topics = await this.registerAndBoot();
42
+ const { topics } = await this.registerAndBoot();
43
43
  this.assertExpectedTopics(topics);
44
- const topics2 = await this.registerAndBoot({
44
+ const { topics: topics2 } = await this.registerAndBoot({
45
45
  skillId: process.env.SKILL_ID,
46
46
  apiKey: process.env.SKILL_API_KEY,
47
47
  });
@@ -55,6 +55,40 @@ class RegisteringConversationsOnBootTest extends AbstractConversationTest_1.defa
55
55
  apiKey: process.env.SKILL_API_KEY,
56
56
  });
57
57
  }
58
+ static async coordinaterGetsSkillContext() {
59
+ this.cwd = this.resolveTestPath('skill');
60
+ const { skill, client } = await this.registerBootAndConnect();
61
+ const id = (0, test_utils_1.generateId)();
62
+ await client.emitAndFlattenResponses('did-message::v2020_12_25', {
63
+ payload: {
64
+ message: this.buildMessage({
65
+ body: (0, test_utils_1.generateId)(),
66
+ source: {
67
+ personId: id,
68
+ },
69
+ }),
70
+ },
71
+ });
72
+ const conversations = skill.getFeatureByCode('conversation');
73
+ //@ts-ignore
74
+ const coordinator = await conversations.coordinatorsBySource[id];
75
+ test_utils_1.assert.isTruthy(coordinator);
76
+ //@ts-ignore
77
+ skill.updateContext('hello', 'world');
78
+ test_utils_1.assert.isEqualDeep(coordinator.getContext(), { hello: 'world' });
79
+ //@ts-ignore
80
+ skill.updateContext('hello2', 'world2');
81
+ test_utils_1.assert.isEqualDeep(coordinator.getContext(), {
82
+ hello: 'world',
83
+ hello2: 'world2',
84
+ });
85
+ }
86
+ static async registerBootAndConnect() {
87
+ const { skill } = await this.registerAndBoot();
88
+ const events = skill.getFeatureByCode('event');
89
+ const client = await events.connectToApi();
90
+ return { skill, client };
91
+ }
58
92
  static assertExpectedTopics(topics) {
59
93
  test_utils_1.assert.isLength(topics, 5);
60
94
  test_utils_1.assert.doesInclude(topics, { key: 'bookAppointment' });
@@ -82,7 +116,7 @@ class RegisteringConversationsOnBootTest extends AbstractConversationTest_1.defa
82
116
  const results = await client.emit('get-conversation-topics::v2020_12_25');
83
117
  const payload = spruce_event_utils_1.eventResponseUtil.getFirstResponseOrThrow(results);
84
118
  topics = payload.topics;
85
- return topics;
119
+ return { topics, skill };
86
120
  }
87
121
  }
88
122
  __decorate([
@@ -103,4 +137,7 @@ __decorate([
103
137
  __decorate([
104
138
  (0, test_utils_1.test)()
105
139
  ], RegisteringConversationsOnBootTest, "skillCanBootASecondTime", null);
140
+ __decorate([
141
+ (0, test_utils_1.test)()
142
+ ], RegisteringConversationsOnBootTest, "coordinaterGetsSkillContext", null);
106
143
  exports.default = RegisteringConversationsOnBootTest;
@@ -2,6 +2,7 @@ import AbstractConversationTest from '../../tests/AbstractConversationTest';
2
2
  export default class TopicCoordinatorTest extends AbstractConversationTest {
3
3
  private static coordinator;
4
4
  private static sentMessages;
5
+ private static skillContext;
5
6
  protected static beforeEach(): Promise<void>;
6
7
  protected static canCreateTopicCoordinator(): Promise<void>;
7
8
  protected static hasHandleMessage(): Promise<void>;
@@ -12,4 +13,5 @@ export default class TopicCoordinatorTest extends AbstractConversationTest {
12
13
  protected static playsScriptWhenTopicIsSelected(): Promise<void>;
13
14
  protected static canHandlePromptsInScript(): Promise<void>;
14
15
  protected static canPlaceMixedScriptsUntilTheEndAndStartsOver(): Promise<void>;
16
+ protected static passesThroughContextGetter(context: Record<string, any>): Promise<void>;
15
17
  }
@@ -17,13 +17,15 @@ class TopicCoordinatorTest extends AbstractConversationTest_1.default {
17
17
  static async beforeEach() {
18
18
  await super.beforeEach();
19
19
  this.sentMessages = [];
20
- this.coordinator = await ConversationCoordinator_1.ConversationCoordinator.Coordinator({
20
+ this.coordinator = (await ConversationCoordinator_1.ConversationCoordinator.Coordinator({
21
21
  lineDelay: 0,
22
22
  sendMessageHandler: async (message) => {
23
23
  this.sentMessages.push(message);
24
24
  },
25
25
  topicLookupPath: this.resolveTestPath('skill', 'build'),
26
- });
26
+ getContext: () => this.skillContext,
27
+ Class: SpyCoordinator,
28
+ }));
27
29
  }
28
30
  static async canCreateTopicCoordinator() {
29
31
  test_utils_1.assert.isTruthy(this.coordinator);
@@ -46,6 +48,7 @@ class TopicCoordinatorTest extends AbstractConversationTest_1.default {
46
48
  const coordinator = await ConversationCoordinator_1.ConversationCoordinator.Coordinator({
47
49
  topicLookupPath: this.cwd,
48
50
  sendMessageHandler: async () => { },
51
+ getContext: () => this.skillContext,
49
52
  });
50
53
  const results = await coordinator.handleMessage(this.buildMessage({ body: 'help me book!', source: { personId: '1234' } }));
51
54
  test_utils_1.assert.isArray(results.suggestedTopics);
@@ -110,8 +113,18 @@ class TopicCoordinatorTest extends AbstractConversationTest_1.default {
110
113
  test_utils_1.assert.isEqual((_h = this.sentMessages[0]) === null || _h === void 0 ? void 0 : _h.body, 'string 1');
111
114
  test_utils_1.assert.isEqual((_j = this.sentMessages[1]) === null || _j === void 0 ? void 0 : _j.body, 'prompt 1');
112
115
  }
116
+ static async passesThroughContextGetter(context) {
117
+ await this.coordinator.handleMessage(this.buildMessage({ body: 'answer 2', source: { personId: '1234' } }), 'bookAppointment');
118
+ this.skillContext = context;
119
+ const player = this.coordinator.getPlayer();
120
+ test_utils_1.assert.isTruthy(player);
121
+ //@ts-ignore
122
+ const actual = player.getContext();
123
+ test_utils_1.assert.isEqualDeep(actual, this.skillContext);
124
+ }
113
125
  }
114
126
  TopicCoordinatorTest.sentMessages = [];
127
+ TopicCoordinatorTest.skillContext = {};
115
128
  __decorate([
116
129
  (0, test_utils_1.test)()
117
130
  ], TopicCoordinatorTest, "canCreateTopicCoordinator", null);
@@ -139,4 +152,13 @@ __decorate([
139
152
  __decorate([
140
153
  (0, test_utils_1.test)()
141
154
  ], TopicCoordinatorTest, "canPlaceMixedScriptsUntilTheEndAndStartsOver", null);
155
+ __decorate([
156
+ (0, test_utils_1.test)('can passthrough context 1', { hello: 'world' }),
157
+ (0, test_utils_1.test)('can passthrough context 2', { go: 'team' })
158
+ ], TopicCoordinatorTest, "passesThroughContextGetter", null);
142
159
  exports.default = TopicCoordinatorTest;
160
+ class SpyCoordinator extends ConversationCoordinator_1.ConversationCoordinator {
161
+ getPlayer() {
162
+ return this.player;
163
+ }
164
+ }
@@ -16,6 +16,7 @@ export default class TopicScriptPlayerTest extends AbstractConversationTest {
16
16
  protected static retainsStateBetweenScriptLines(): Promise<void>;
17
17
  protected static retainsStateBetweenMessages(): Promise<void>;
18
18
  protected static alwaysEndsWithRedirectToDiscovery(): Promise<void>;
19
+ protected static skillContextAvailableOnScriptLine(context: Record<string, any>): Promise<void>;
19
20
  private static Player;
20
21
  private static sendMessage;
21
22
  }
@@ -18,7 +18,12 @@ class TopicScriptPlayerTest extends AbstractConversationTest_1.default {
18
18
  //@ts-ignore
19
19
  const err = test_utils_1.assert.doesThrow(() => new TopicScriptPlayer_1.TopicScriptPlayer());
20
20
  test_utils_2.errorAssert.assertError(err, 'MISSING_PARAMETERS', {
21
- parameters: ['script', 'sendMessageHandler', 'target.personId'],
21
+ parameters: [
22
+ 'script',
23
+ 'sendMessageHandler',
24
+ 'target.personId',
25
+ 'getContext',
26
+ ],
22
27
  });
23
28
  }
24
29
  static async respondsToMessageWithOnlyLineInScript() {
@@ -331,14 +336,24 @@ class TopicScriptPlayerTest extends AbstractConversationTest_1.default {
331
336
  test_utils_1.assert.isEqual(results === null || results === void 0 ? void 0 : results.transitionConversationTo, 'discovery');
332
337
  test_utils_1.assert.isEqual(stateCount, 4);
333
338
  }
339
+ static async skillContextAvailableOnScriptLine(context) {
340
+ let passedContext;
341
+ const player = this.Player({
342
+ getContext: () => context,
343
+ script: [
344
+ async (options) => {
345
+ passedContext = options.context;
346
+ },
347
+ ],
348
+ });
349
+ await this.sendMessage(player, {
350
+ body: (0, test_utils_1.generateId)(),
351
+ });
352
+ test_utils_1.assert.isEqualDeep(passedContext, context);
353
+ }
334
354
  static Player(options) {
335
355
  var _a, _b;
336
- return new TopicScriptPlayer_1.TopicScriptPlayer({
337
- target: (_a = options.target) !== null && _a !== void 0 ? _a : { personId: '12345' },
338
- script: options.script,
339
- lineDelay: 0,
340
- sendMessageHandler: (_b = options.sendMessageHandler) !== null && _b !== void 0 ? _b : async function () { },
341
- });
356
+ return new TopicScriptPlayer_1.TopicScriptPlayer(Object.assign({ target: (_a = options.target) !== null && _a !== void 0 ? _a : { personId: '12345' }, lineDelay: 0, sendMessageHandler: (_b = options.sendMessageHandler) !== null && _b !== void 0 ? _b : async function () { }, getContext: () => ({}) }, options));
342
357
  }
343
358
  static async sendMessage(player, message) {
344
359
  return player.handleMessage(this.buildMessage(Object.assign({ source: { personId: '1234' } }, message)));
@@ -410,4 +425,8 @@ __decorate([
410
425
  __decorate([
411
426
  (0, test_utils_1.test)()
412
427
  ], TopicScriptPlayerTest, "alwaysEndsWithRedirectToDiscovery", null);
428
+ __decorate([
429
+ (0, test_utils_1.test)('can passe context 1', { skill: 'test' }),
430
+ (0, test_utils_1.test)('can passe context 2', { hello: 'world' })
431
+ ], TopicScriptPlayerTest, "skillContextAvailableOnScriptLine", null);
413
432
  exports.default = TopicScriptPlayerTest;
@@ -1,20 +1,33 @@
1
1
  import { SkillEventContract } from '@sprucelabs/mercury-core-events';
2
2
  import { SchemaValues } from '@sprucelabs/schema';
3
- import { Message, SendMessageHandler } from '../types/conversation.types';
3
+ import { SkillContext } from '@sprucelabs/spruce-skill-utils';
4
+ import { TopicScriptPlayer } from '../topics/TopicScriptPlayer';
5
+ import { TopicSuggester } from '../topics/TopicSuggester';
6
+ import { LoadedTopicDefinition, Message, SendMessageHandler } from '../types/conversation.types';
4
7
  type MessageResponsePayloadSchema = SkillEventContract['eventSignatures']['did-message::v2020_12_25']['responsePayloadSchema'];
5
8
  type MessageResponsePayload = SchemaValues<MessageResponsePayloadSchema>;
9
+ type GetContext = () => SkillContext;
6
10
  export declare class ConversationCoordinator {
7
11
  private suggester;
8
12
  private sendMessageHandler;
9
13
  private topics;
10
- private player?;
14
+ protected player?: TopicScriptPlayer;
11
15
  private lastTopic?;
12
16
  private lineDelay?;
13
- private constructor();
17
+ private getContext;
18
+ protected constructor(options: {
19
+ suggester: TopicSuggester;
20
+ sendMessageHandler: SendMessageHandler;
21
+ topics: LoadedTopicDefinition[];
22
+ lineDelay?: number;
23
+ getContext: GetContext;
24
+ });
14
25
  static Coordinator(options: {
15
26
  topicLookupPath: string;
16
27
  sendMessageHandler: SendMessageHandler;
17
28
  lineDelay?: number;
29
+ getContext: GetContext;
30
+ Class?: new (...args: any[]) => ConversationCoordinator;
18
31
  }): Promise<ConversationCoordinator>;
19
32
  handleMessage(message: Message, topic?: string): Promise<MessageResponsePayload | void>;
20
33
  }
@@ -26,15 +26,18 @@ class ConversationCoordinator {
26
26
  this.sendMessageHandler = options.sendMessageHandler;
27
27
  this.topics = options.topics;
28
28
  this.lineDelay = options.lineDelay;
29
+ this.getContext = options.getContext;
29
30
  }
30
31
  static async Coordinator(options) {
31
- const topics = await TopicLoader_1.default.loadTopics(options.topicLookupPath);
32
+ const { Class, topicLookupPath, sendMessageHandler, lineDelay, getContext, } = options;
33
+ const topics = await TopicLoader_1.default.loadTopics(topicLookupPath);
32
34
  const suggester = await TopicSuggester_1.TopicSuggester.Suggester({ topics });
33
- return new ConversationCoordinator({
35
+ return new (Class !== null && Class !== void 0 ? Class : ConversationCoordinator)({
34
36
  suggester,
35
- sendMessageHandler: options.sendMessageHandler,
37
+ sendMessageHandler,
36
38
  topics,
37
- lineDelay: options.lineDelay,
39
+ lineDelay,
40
+ getContext,
38
41
  });
39
42
  }
40
43
  async handleMessage(message, topic) {
@@ -68,6 +71,7 @@ class ConversationCoordinator {
68
71
  sendMessageHandler: this.sendMessageHandler,
69
72
  script: matchedTopic.script,
70
73
  lineDelay: this.lineDelay,
74
+ getContext: this.getContext,
71
75
  });
72
76
  }
73
77
  const results = await this.player.handleMessage(message);
@@ -6,6 +6,8 @@ export default class RegisteringConversationsOnBootTest extends AbstractConversa
6
6
  protected static skillShutsDownWhenConvosFailToRegister(): Promise<void>;
7
7
  protected static canBootASecondTime(): Promise<void>;
8
8
  protected static skillCanBootASecondTime(): Promise<void>;
9
+ protected static coordinaterGetsSkillContext(): Promise<void>;
10
+ private static registerBootAndConnect;
9
11
  private static assertExpectedTopics;
10
12
  private static registerAndBoot;
11
13
  }
@@ -14,7 +14,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
14
14
  });
15
15
  };
16
16
  import { eventResponseUtil } from '@sprucelabs/spruce-event-utils';
17
- import { test, assert } from '@sprucelabs/test-utils';
17
+ import { test, assert, generateId } from '@sprucelabs/test-utils';
18
18
  import { errorAssert } from '@sprucelabs/test-utils';
19
19
  import plugin from '../../plugins/conversation.plugin.js';
20
20
  import AbstractConversationTest from '../../tests/AbstractConversationTest.js';
@@ -31,14 +31,14 @@ export default class RegisteringConversationsOnBootTest extends AbstractConversa
31
31
  static noConvosToStart() {
32
32
  return __awaiter(this, void 0, void 0, function* () {
33
33
  this.cwd = this.resolveTestPath('empty-skill');
34
- const topics = yield this.registerAndBoot();
34
+ const { topics } = yield this.registerAndBoot();
35
35
  assert.isLength(topics, 0);
36
36
  });
37
37
  }
38
38
  static registersConvosOnBoot() {
39
39
  return __awaiter(this, void 0, void 0, function* () {
40
40
  this.cwd = this.resolveTestPath('skill');
41
- const topics = yield this.registerAndBoot();
41
+ const { topics } = yield this.registerAndBoot();
42
42
  this.assertExpectedTopics(topics);
43
43
  });
44
44
  }
@@ -55,9 +55,9 @@ export default class RegisteringConversationsOnBootTest extends AbstractConversa
55
55
  static canBootASecondTime() {
56
56
  return __awaiter(this, void 0, void 0, function* () {
57
57
  this.cwd = this.resolveTestPath('skill');
58
- const topics = yield this.registerAndBoot();
58
+ const { topics } = yield this.registerAndBoot();
59
59
  this.assertExpectedTopics(topics);
60
- const topics2 = yield this.registerAndBoot({
60
+ const { topics: topics2 } = yield this.registerAndBoot({
61
61
  skillId: process.env.SKILL_ID,
62
62
  apiKey: process.env.SKILL_API_KEY,
63
63
  });
@@ -74,6 +74,44 @@ export default class RegisteringConversationsOnBootTest extends AbstractConversa
74
74
  });
75
75
  });
76
76
  }
77
+ static coordinaterGetsSkillContext() {
78
+ return __awaiter(this, void 0, void 0, function* () {
79
+ this.cwd = this.resolveTestPath('skill');
80
+ const { skill, client } = yield this.registerBootAndConnect();
81
+ const id = generateId();
82
+ yield client.emitAndFlattenResponses('did-message::v2020_12_25', {
83
+ payload: {
84
+ message: this.buildMessage({
85
+ body: generateId(),
86
+ source: {
87
+ personId: id,
88
+ },
89
+ }),
90
+ },
91
+ });
92
+ const conversations = skill.getFeatureByCode('conversation');
93
+ //@ts-ignore
94
+ const coordinator = yield conversations.coordinatorsBySource[id];
95
+ assert.isTruthy(coordinator);
96
+ //@ts-ignore
97
+ skill.updateContext('hello', 'world');
98
+ assert.isEqualDeep(coordinator.getContext(), { hello: 'world' });
99
+ //@ts-ignore
100
+ skill.updateContext('hello2', 'world2');
101
+ assert.isEqualDeep(coordinator.getContext(), {
102
+ hello: 'world',
103
+ hello2: 'world2',
104
+ });
105
+ });
106
+ }
107
+ static registerBootAndConnect() {
108
+ return __awaiter(this, void 0, void 0, function* () {
109
+ const { skill } = yield this.registerAndBoot();
110
+ const events = skill.getFeatureByCode('event');
111
+ const client = yield events.connectToApi();
112
+ return { skill, client };
113
+ });
114
+ }
77
115
  static assertExpectedTopics(topics) {
78
116
  assert.isLength(topics, 5);
79
117
  assert.doesInclude(topics, { key: 'bookAppointment' });
@@ -102,7 +140,7 @@ export default class RegisteringConversationsOnBootTest extends AbstractConversa
102
140
  const results = yield client.emit('get-conversation-topics::v2020_12_25');
103
141
  const payload = eventResponseUtil.getFirstResponseOrThrow(results);
104
142
  topics = payload.topics;
105
- return topics;
143
+ return { topics, skill };
106
144
  });
107
145
  }
108
146
  }
@@ -124,3 +162,6 @@ __decorate([
124
162
  __decorate([
125
163
  test()
126
164
  ], RegisteringConversationsOnBootTest, "skillCanBootASecondTime", null);
165
+ __decorate([
166
+ test()
167
+ ], RegisteringConversationsOnBootTest, "coordinaterGetsSkillContext", null);
@@ -2,6 +2,7 @@ import AbstractConversationTest from '../../tests/AbstractConversationTest';
2
2
  export default class TopicCoordinatorTest extends AbstractConversationTest {
3
3
  private static coordinator;
4
4
  private static sentMessages;
5
+ private static skillContext;
5
6
  protected static beforeEach(): Promise<void>;
6
7
  protected static canCreateTopicCoordinator(): Promise<void>;
7
8
  protected static hasHandleMessage(): Promise<void>;
@@ -12,4 +13,5 @@ export default class TopicCoordinatorTest extends AbstractConversationTest {
12
13
  protected static playsScriptWhenTopicIsSelected(): Promise<void>;
13
14
  protected static canHandlePromptsInScript(): Promise<void>;
14
15
  protected static canPlaceMixedScriptsUntilTheEndAndStartsOver(): Promise<void>;
16
+ protected static passesThroughContextGetter(context: Record<string, any>): Promise<void>;
15
17
  }
@@ -25,13 +25,15 @@ class TopicCoordinatorTest extends AbstractConversationTest {
25
25
  return __awaiter(this, void 0, void 0, function* () {
26
26
  yield _super.beforeEach.call(this);
27
27
  this.sentMessages = [];
28
- this.coordinator = yield ConversationCoordinator.Coordinator({
28
+ this.coordinator = (yield ConversationCoordinator.Coordinator({
29
29
  lineDelay: 0,
30
30
  sendMessageHandler: (message) => __awaiter(this, void 0, void 0, function* () {
31
31
  this.sentMessages.push(message);
32
32
  }),
33
33
  topicLookupPath: this.resolveTestPath('skill', 'build'),
34
- });
34
+ getContext: () => this.skillContext,
35
+ Class: SpyCoordinator,
36
+ }));
35
37
  });
36
38
  }
37
39
  static canCreateTopicCoordinator() {
@@ -64,6 +66,7 @@ class TopicCoordinatorTest extends AbstractConversationTest {
64
66
  const coordinator = yield ConversationCoordinator.Coordinator({
65
67
  topicLookupPath: this.cwd,
66
68
  sendMessageHandler: () => __awaiter(this, void 0, void 0, function* () { }),
69
+ getContext: () => this.skillContext,
67
70
  });
68
71
  const results = yield coordinator.handleMessage(this.buildMessage({ body: 'help me book!', source: { personId: '1234' } }));
69
72
  assert.isArray(results.suggestedTopics);
@@ -137,8 +140,20 @@ class TopicCoordinatorTest extends AbstractConversationTest {
137
140
  assert.isEqual((_j = this.sentMessages[1]) === null || _j === void 0 ? void 0 : _j.body, 'prompt 1');
138
141
  });
139
142
  }
143
+ static passesThroughContextGetter(context) {
144
+ return __awaiter(this, void 0, void 0, function* () {
145
+ yield this.coordinator.handleMessage(this.buildMessage({ body: 'answer 2', source: { personId: '1234' } }), 'bookAppointment');
146
+ this.skillContext = context;
147
+ const player = this.coordinator.getPlayer();
148
+ assert.isTruthy(player);
149
+ //@ts-ignore
150
+ const actual = player.getContext();
151
+ assert.isEqualDeep(actual, this.skillContext);
152
+ });
153
+ }
140
154
  }
141
155
  TopicCoordinatorTest.sentMessages = [];
156
+ TopicCoordinatorTest.skillContext = {};
142
157
  __decorate([
143
158
  test()
144
159
  ], TopicCoordinatorTest, "canCreateTopicCoordinator", null);
@@ -166,4 +181,13 @@ __decorate([
166
181
  __decorate([
167
182
  test()
168
183
  ], TopicCoordinatorTest, "canPlaceMixedScriptsUntilTheEndAndStartsOver", null);
184
+ __decorate([
185
+ test('can passthrough context 1', { hello: 'world' }),
186
+ test('can passthrough context 2', { go: 'team' })
187
+ ], TopicCoordinatorTest, "passesThroughContextGetter", null);
169
188
  export default TopicCoordinatorTest;
189
+ class SpyCoordinator extends ConversationCoordinator {
190
+ getPlayer() {
191
+ return this.player;
192
+ }
193
+ }
@@ -16,6 +16,7 @@ export default class TopicScriptPlayerTest extends AbstractConversationTest {
16
16
  protected static retainsStateBetweenScriptLines(): Promise<void>;
17
17
  protected static retainsStateBetweenMessages(): Promise<void>;
18
18
  protected static alwaysEndsWithRedirectToDiscovery(): Promise<void>;
19
+ protected static skillContextAvailableOnScriptLine(context: Record<string, any>): Promise<void>;
19
20
  private static Player;
20
21
  private static sendMessage;
21
22
  }
@@ -13,7 +13,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
13
13
  step((generator = generator.apply(thisArg, _arguments || [])).next());
14
14
  });
15
15
  };
16
- import { test, assert } from '@sprucelabs/test-utils';
16
+ import { test, assert, generateId } from '@sprucelabs/test-utils';
17
17
  import { errorAssert } from '@sprucelabs/test-utils';
18
18
  import AbstractConversationTest from '../../tests/AbstractConversationTest.js';
19
19
  import { TopicScriptPlayer } from '../../topics/TopicScriptPlayer.js';
@@ -23,7 +23,12 @@ export default class TopicScriptPlayerTest extends AbstractConversationTest {
23
23
  //@ts-ignore
24
24
  const err = assert.doesThrow(() => new TopicScriptPlayer());
25
25
  errorAssert.assertError(err, 'MISSING_PARAMETERS', {
26
- parameters: ['script', 'sendMessageHandler', 'target.personId'],
26
+ parameters: [
27
+ 'script',
28
+ 'sendMessageHandler',
29
+ 'target.personId',
30
+ 'getContext',
31
+ ],
27
32
  });
28
33
  });
29
34
  }
@@ -367,16 +372,28 @@ export default class TopicScriptPlayerTest extends AbstractConversationTest {
367
372
  assert.isEqual(stateCount, 4);
368
373
  });
369
374
  }
375
+ static skillContextAvailableOnScriptLine(context) {
376
+ return __awaiter(this, void 0, void 0, function* () {
377
+ let passedContext;
378
+ const player = this.Player({
379
+ getContext: () => context,
380
+ script: [
381
+ (options) => __awaiter(this, void 0, void 0, function* () {
382
+ passedContext = options.context;
383
+ }),
384
+ ],
385
+ });
386
+ yield this.sendMessage(player, {
387
+ body: generateId(),
388
+ });
389
+ assert.isEqualDeep(passedContext, context);
390
+ });
391
+ }
370
392
  static Player(options) {
371
393
  var _a, _b;
372
- return new TopicScriptPlayer({
373
- target: (_a = options.target) !== null && _a !== void 0 ? _a : { personId: '12345' },
374
- script: options.script,
375
- lineDelay: 0,
376
- sendMessageHandler: (_b = options.sendMessageHandler) !== null && _b !== void 0 ? _b : function () {
394
+ return new TopicScriptPlayer(Object.assign({ target: (_a = options.target) !== null && _a !== void 0 ? _a : { personId: '12345' }, lineDelay: 0, sendMessageHandler: (_b = options.sendMessageHandler) !== null && _b !== void 0 ? _b : function () {
377
395
  return __awaiter(this, void 0, void 0, function* () { });
378
- },
379
- });
396
+ }, getContext: () => ({}) }, options));
380
397
  }
381
398
  static sendMessage(player, message) {
382
399
  return __awaiter(this, void 0, void 0, function* () {
@@ -450,3 +467,7 @@ __decorate([
450
467
  __decorate([
451
468
  test()
452
469
  ], TopicScriptPlayerTest, "alwaysEndsWithRedirectToDiscovery", null);
470
+ __decorate([
471
+ test('can passe context 1', { skill: 'test' }),
472
+ test('can passe context 2', { hello: 'world' })
473
+ ], TopicScriptPlayerTest, "skillContextAvailableOnScriptLine", null);
@@ -1,20 +1,33 @@
1
1
  import { SkillEventContract } from '@sprucelabs/mercury-core-events';
2
2
  import { SchemaValues } from '@sprucelabs/schema';
3
- import { Message, SendMessageHandler } from '../types/conversation.types';
3
+ import { SkillContext } from '@sprucelabs/spruce-skill-utils';
4
+ import { TopicScriptPlayer } from '../topics/TopicScriptPlayer';
5
+ import { TopicSuggester } from '../topics/TopicSuggester';
6
+ import { LoadedTopicDefinition, Message, SendMessageHandler } from '../types/conversation.types';
4
7
  type MessageResponsePayloadSchema = SkillEventContract['eventSignatures']['did-message::v2020_12_25']['responsePayloadSchema'];
5
8
  type MessageResponsePayload = SchemaValues<MessageResponsePayloadSchema>;
9
+ type GetContext = () => SkillContext;
6
10
  export declare class ConversationCoordinator {
7
11
  private suggester;
8
12
  private sendMessageHandler;
9
13
  private topics;
10
- private player?;
14
+ protected player?: TopicScriptPlayer;
11
15
  private lastTopic?;
12
16
  private lineDelay?;
13
- private constructor();
17
+ private getContext;
18
+ protected constructor(options: {
19
+ suggester: TopicSuggester;
20
+ sendMessageHandler: SendMessageHandler;
21
+ topics: LoadedTopicDefinition[];
22
+ lineDelay?: number;
23
+ getContext: GetContext;
24
+ });
14
25
  static Coordinator(options: {
15
26
  topicLookupPath: string;
16
27
  sendMessageHandler: SendMessageHandler;
17
28
  lineDelay?: number;
29
+ getContext: GetContext;
30
+ Class?: new (...args: any[]) => ConversationCoordinator;
18
31
  }): Promise<ConversationCoordinator>;
19
32
  handleMessage(message: Message, topic?: string): Promise<MessageResponsePayload | void>;
20
33
  }
@@ -29,16 +29,19 @@ export class ConversationCoordinator {
29
29
  this.sendMessageHandler = options.sendMessageHandler;
30
30
  this.topics = options.topics;
31
31
  this.lineDelay = options.lineDelay;
32
+ this.getContext = options.getContext;
32
33
  }
33
34
  static Coordinator(options) {
34
35
  return __awaiter(this, void 0, void 0, function* () {
35
- const topics = yield TopicLoader.loadTopics(options.topicLookupPath);
36
+ const { Class, topicLookupPath, sendMessageHandler, lineDelay, getContext, } = options;
37
+ const topics = yield TopicLoader.loadTopics(topicLookupPath);
36
38
  const suggester = yield TopicSuggester.Suggester({ topics });
37
- return new ConversationCoordinator({
39
+ return new (Class !== null && Class !== void 0 ? Class : ConversationCoordinator)({
38
40
  suggester,
39
- sendMessageHandler: options.sendMessageHandler,
41
+ sendMessageHandler,
40
42
  topics,
41
- lineDelay: options.lineDelay,
43
+ lineDelay,
44
+ getContext,
42
45
  });
43
46
  });
44
47
  }
@@ -74,6 +77,7 @@ export class ConversationCoordinator {
74
77
  sendMessageHandler: this.sendMessageHandler,
75
78
  script: matchedTopic.script,
76
79
  lineDelay: this.lineDelay,
80
+ getContext: this.getContext,
77
81
  });
78
82
  }
79
83
  const results = yield this.player.handleMessage(message);
@@ -123,6 +123,7 @@ export class ConversationFeature {
123
123
  }
124
124
  this.coordinatorsBySource[sourceId] = ConversationCoordinator.Coordinator({
125
125
  topicLookupPath: this.skill.activeDir,
126
+ getContext: () => this.skill.getContext(),
126
127
  sendMessageHandler: (message) => __awaiter(this, void 0, void 0, function* () {
127
128
  try {
128
129
  const { target } = message, values = __rest(message, ["target"]);
@@ -46,6 +46,7 @@ export default class ScriptTester {
46
46
  sendMessageHandler: (message) => __awaiter(this, void 0, void 0, function* () {
47
47
  return this.writeHandler(message);
48
48
  }),
49
+ getContext: () => ({}),
49
50
  });
50
51
  let msg = firstMessage;
51
52
  if (!msg) {
@@ -10,6 +10,7 @@ export declare class TopicScriptPlayer {
10
10
  private scriptState;
11
11
  private runningLine;
12
12
  private scriptLineIndex;
13
+ private getContext;
13
14
  constructor(options: ScriptPlayerOptions);
14
15
  handleMessage(message: Message): Promise<DidMessageResponsePayload | null>;
15
16
  private play;
@@ -7,36 +7,27 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { SchemaError } from '@sprucelabs/schema';
10
+ import { assertOptions } from '@sprucelabs/schema';
11
11
  import MessageGraphicsInterface from '../interfaces/MessageGraphicsInterface.js';
12
12
  import randomUtil from '../utilities/random.utility.js';
13
13
  export class TopicScriptPlayer {
14
14
  constructor(options) {
15
- var _a, _b;
15
+ var _a;
16
16
  this.scriptState = {};
17
17
  this.scriptLineIndex = -1;
18
- const missing = [];
19
- if (!(options === null || options === void 0 ? void 0 : options.script)) {
20
- missing.push('script');
21
- }
22
- if (!(options === null || options === void 0 ? void 0 : options.sendMessageHandler)) {
23
- missing.push('sendMessageHandler');
24
- }
25
- if (!(options === null || options === void 0 ? void 0 : options.target.personId)) {
26
- missing.push('target.personId');
27
- }
28
- if (missing.length > 0) {
29
- throw new SchemaError({
30
- code: 'MISSING_PARAMETERS',
31
- parameters: missing,
32
- });
33
- }
34
- this.script = options.script;
35
- this.target = options.target;
36
- this.sendMessageHandler = options.sendMessageHandler;
37
- this.lineDelay = (_a = options.lineDelay) !== null && _a !== void 0 ? _a : 2000;
18
+ const { script, target, sendMessageHandler, lineDelay, getContext } = assertOptions(options, [
19
+ 'script',
20
+ 'sendMessageHandler',
21
+ 'target.personId',
22
+ 'getContext',
23
+ ]);
24
+ this.script = script;
25
+ this.target = target;
26
+ this.sendMessageHandler = sendMessageHandler;
27
+ this.lineDelay = lineDelay !== null && lineDelay !== void 0 ? lineDelay : 2000;
28
+ this.getContext = getContext;
38
29
  this.graphicsInterface =
39
- (_b = options.graphicsInterface) !== null && _b !== void 0 ? _b : new MessageGraphicsInterface({
30
+ (_a = options.graphicsInterface) !== null && _a !== void 0 ? _a : new MessageGraphicsInterface({
40
31
  sendMessageHandler: this.sendMessage.bind(this),
41
32
  });
42
33
  }
@@ -101,10 +92,10 @@ export class TopicScriptPlayer {
101
92
  return null;
102
93
  });
103
94
  }
104
- handleLineCallback(normalizedLine, message) {
95
+ handleLineCallback(line, message) {
105
96
  return __awaiter(this, void 0, void 0, function* () {
106
97
  this.runningLine = {
107
- promise: normalizedLine(this.buildCallbackOptions(message))
98
+ promise: line(this.buildCallbackOptions(message))
108
99
  .then((results) => {
109
100
  this.runningLine.isDone = true;
110
101
  return results;
@@ -143,6 +134,7 @@ export class TopicScriptPlayer {
143
134
  state: this.scriptState,
144
135
  rand: randomUtil.rand,
145
136
  message,
137
+ context: this.getContext(),
146
138
  };
147
139
  }
148
140
  pickRandomLine(line) {
@@ -3,7 +3,7 @@ import { SchemaValues } from '@sprucelabs/schema';
3
3
  import { FieldDefinitions } from '@sprucelabs/schema';
4
4
  import { SpruceSchemas } from '@sprucelabs/spruce-core-schemas';
5
5
  import { EventTarget } from '@sprucelabs/spruce-event-utils';
6
- import { GraphicsInterface, HealthCheckItem } from '@sprucelabs/spruce-skill-utils';
6
+ import { GraphicsInterface, HealthCheckItem, SkillContext } from '@sprucelabs/spruce-skill-utils';
7
7
  import MessageGraphicsInterface from '../interfaces/MessageGraphicsInterface';
8
8
  export type Message = SpruceSchemas.Spruce.v2020_07_22.Message;
9
9
  export type SendMessage = SpruceSchemas.Spruce.v2020_07_22.SendMessage;
@@ -15,6 +15,7 @@ export type ScriptLineCallbackOptions = {
15
15
  state: Record<string, any>;
16
16
  rand<T>(possibilities: T[]): T;
17
17
  message: Message;
18
+ context: SkillContext;
18
19
  };
19
20
  export type ScriptLine = string | ScriptLineCallback | string[];
20
21
  export interface ScriptLineCallback {
@@ -27,6 +28,7 @@ export interface ScriptPlayerOptions {
27
28
  target: EventTarget;
28
29
  lineDelay?: number;
29
30
  graphicsInterface?: MessageGraphicsInterface;
31
+ getContext: () => SkillContext;
30
32
  }
31
33
  export type ScriptPlayerSendMessage = {
32
34
  body: string;
@@ -109,6 +109,7 @@ class ConversationFeature {
109
109
  }
110
110
  this.coordinatorsBySource[sourceId] = ConversationCoordinator_1.ConversationCoordinator.Coordinator({
111
111
  topicLookupPath: this.skill.activeDir,
112
+ getContext: () => this.skill.getContext(),
112
113
  sendMessageHandler: async (message) => {
113
114
  try {
114
115
  const { target } = message, values = __rest(message, ["target"]);
@@ -42,6 +42,7 @@ class ScriptTester {
42
42
  sendMessageHandler: async (message) => {
43
43
  return this.writeHandler(message);
44
44
  },
45
+ getContext: () => ({}),
45
46
  });
46
47
  let msg = firstMessage;
47
48
  if (!msg) {
@@ -10,6 +10,7 @@ export declare class TopicScriptPlayer {
10
10
  private scriptState;
11
11
  private runningLine;
12
12
  private scriptLineIndex;
13
+ private getContext;
13
14
  constructor(options: ScriptPlayerOptions);
14
15
  handleMessage(message: Message): Promise<DidMessageResponsePayload | null>;
15
16
  private play;
@@ -9,31 +9,22 @@ const MessageGraphicsInterface_1 = __importDefault(require("../interfaces/Messag
9
9
  const random_utility_1 = __importDefault(require("../utilities/random.utility"));
10
10
  class TopicScriptPlayer {
11
11
  constructor(options) {
12
- var _a, _b;
12
+ var _a;
13
13
  this.scriptState = {};
14
14
  this.scriptLineIndex = -1;
15
- const missing = [];
16
- if (!(options === null || options === void 0 ? void 0 : options.script)) {
17
- missing.push('script');
18
- }
19
- if (!(options === null || options === void 0 ? void 0 : options.sendMessageHandler)) {
20
- missing.push('sendMessageHandler');
21
- }
22
- if (!(options === null || options === void 0 ? void 0 : options.target.personId)) {
23
- missing.push('target.personId');
24
- }
25
- if (missing.length > 0) {
26
- throw new schema_1.SchemaError({
27
- code: 'MISSING_PARAMETERS',
28
- parameters: missing,
29
- });
30
- }
31
- this.script = options.script;
32
- this.target = options.target;
33
- this.sendMessageHandler = options.sendMessageHandler;
34
- this.lineDelay = (_a = options.lineDelay) !== null && _a !== void 0 ? _a : 2000;
15
+ const { script, target, sendMessageHandler, lineDelay, getContext } = (0, schema_1.assertOptions)(options, [
16
+ 'script',
17
+ 'sendMessageHandler',
18
+ 'target.personId',
19
+ 'getContext',
20
+ ]);
21
+ this.script = script;
22
+ this.target = target;
23
+ this.sendMessageHandler = sendMessageHandler;
24
+ this.lineDelay = lineDelay !== null && lineDelay !== void 0 ? lineDelay : 2000;
25
+ this.getContext = getContext;
35
26
  this.graphicsInterface =
36
- (_b = options.graphicsInterface) !== null && _b !== void 0 ? _b : new MessageGraphicsInterface_1.default({
27
+ (_a = options.graphicsInterface) !== null && _a !== void 0 ? _a : new MessageGraphicsInterface_1.default({
37
28
  sendMessageHandler: this.sendMessage.bind(this),
38
29
  });
39
30
  }
@@ -92,9 +83,9 @@ class TopicScriptPlayer {
92
83
  }
93
84
  return null;
94
85
  }
95
- async handleLineCallback(normalizedLine, message) {
86
+ async handleLineCallback(line, message) {
96
87
  this.runningLine = {
97
- promise: normalizedLine(this.buildCallbackOptions(message))
88
+ promise: line(this.buildCallbackOptions(message))
98
89
  .then((results) => {
99
90
  this.runningLine.isDone = true;
100
91
  return results;
@@ -130,6 +121,7 @@ class TopicScriptPlayer {
130
121
  state: this.scriptState,
131
122
  rand: random_utility_1.default.rand,
132
123
  message,
124
+ context: this.getContext(),
133
125
  };
134
126
  }
135
127
  pickRandomLine(line) {
@@ -3,7 +3,7 @@ import { SchemaValues } from '@sprucelabs/schema';
3
3
  import { FieldDefinitions } from '@sprucelabs/schema';
4
4
  import { SpruceSchemas } from '@sprucelabs/spruce-core-schemas';
5
5
  import { EventTarget } from '@sprucelabs/spruce-event-utils';
6
- import { GraphicsInterface, HealthCheckItem } from '@sprucelabs/spruce-skill-utils';
6
+ import { GraphicsInterface, HealthCheckItem, SkillContext } from '@sprucelabs/spruce-skill-utils';
7
7
  import MessageGraphicsInterface from '../interfaces/MessageGraphicsInterface';
8
8
  export type Message = SpruceSchemas.Spruce.v2020_07_22.Message;
9
9
  export type SendMessage = SpruceSchemas.Spruce.v2020_07_22.SendMessage;
@@ -15,6 +15,7 @@ export type ScriptLineCallbackOptions = {
15
15
  state: Record<string, any>;
16
16
  rand<T>(possibilities: T[]): T;
17
17
  message: Message;
18
+ context: SkillContext;
18
19
  };
19
20
  export type ScriptLine = string | ScriptLineCallback | string[];
20
21
  export interface ScriptLineCallback {
@@ -27,6 +28,7 @@ export interface ScriptPlayerOptions {
27
28
  target: EventTarget;
28
29
  lineDelay?: number;
29
30
  graphicsInterface?: MessageGraphicsInterface;
31
+ getContext: () => SkillContext;
30
32
  }
31
33
  export type ScriptPlayerSendMessage = {
32
34
  body: string;
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "@types/inquirer"
13
13
  ]
14
14
  },
15
- "version": "56.0.38",
15
+ "version": "56.1.0",
16
16
  "files": [
17
17
  "build/**/*",
18
18
  "!build/__tests__",
@@ -68,11 +68,11 @@
68
68
  "@sprucelabs/mercury-core-events": "^18.0.32",
69
69
  "@sprucelabs/schema": "^28.5.153",
70
70
  "@sprucelabs/spruce-core-schemas": "^37.2.17",
71
- "@sprucelabs/spruce-event-plugin": "^56.0.38",
71
+ "@sprucelabs/spruce-event-plugin": "^56.1.0",
72
72
  "@sprucelabs/spruce-event-utils": "^34.0.29",
73
- "@sprucelabs/spruce-skill-booter": "^56.0.38",
73
+ "@sprucelabs/spruce-skill-booter": "^56.1.0",
74
74
  "@sprucelabs/spruce-skill-utils": "^28.1.109",
75
- "@sprucelabs/spruce-test-fixtures": "^56.0.38",
75
+ "@sprucelabs/spruce-test-fixtures": "^56.1.0",
76
76
  "fuzzyset": "^1.0.7",
77
77
  "inquirer": "^8.2.4",
78
78
  "node-nlp": "^4.26.1",
@@ -118,5 +118,5 @@
118
118
  "^#spruce/(.*)$": "<rootDir>/build/.spruce/$1"
119
119
  }
120
120
  },
121
- "gitHead": "5fea873ddeee33e93b0218520bbd062697027bbf"
121
+ "gitHead": "24c52c19b2a6a25f3822497bbf038934cf243938"
122
122
  }