@statelyai/agent 2.0.0-next.0 → 2.0.0-next.1

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 (45) hide show
  1. package/.changeset/cyan-carpets-perform.md +5 -0
  2. package/.changeset/fast-donkeys-argue.md +5 -0
  3. package/.changeset/old-jobs-check.md +5 -0
  4. package/.changeset/pre.json +4 -1
  5. package/CHANGELOG.md +12 -0
  6. package/dist/index.d.mts +19 -25
  7. package/dist/index.d.ts +19 -25
  8. package/dist/index.js +43 -39
  9. package/dist/index.mjs +43 -39
  10. package/examples/chatbot-alt.ts +1 -1
  11. package/examples/chatbot.ts +1 -1
  12. package/examples/cot.ts +1 -1
  13. package/examples/customer-service-sim.ts +4 -4
  14. package/examples/email.ts +29 -33
  15. package/examples/example.ts +1 -1
  16. package/examples/goal.ts +1 -1
  17. package/examples/joke.ts +1 -1
  18. package/examples/jugs.ts +3 -3
  19. package/examples/multi.ts +1 -1
  20. package/examples/number.ts +1 -1
  21. package/examples/raffle.ts +1 -1
  22. package/examples/river-crossing.ts +3 -3
  23. package/examples/simple.ts +1 -1
  24. package/examples/summary.ts +1 -1
  25. package/examples/support.ts +1 -1
  26. package/examples/ticTacToe.ts +2 -2
  27. package/examples/todo.ts +1 -1
  28. package/examples/tutor.ts +2 -2
  29. package/examples/verify.ts +1 -1
  30. package/examples/weather.ts +1 -1
  31. package/examples/wiki.ts +1 -1
  32. package/examples/word.ts +1 -1
  33. package/package.json +5 -3
  34. package/src/agent.test.ts +150 -10
  35. package/src/agent.ts +20 -217
  36. package/src/decide.test.ts +124 -3
  37. package/src/decide.ts +24 -14
  38. package/src/middleware.ts +2 -14
  39. package/src/planners/shortestPath.test.ts +94 -0
  40. package/src/planners/shortestPath.ts +177 -0
  41. package/src/planners/{simplePlanner.ts → simple.ts} +6 -12
  42. package/src/types.ts +15 -8
  43. package/src/utils.ts +11 -0
  44. package/vitest.config.ts +9 -3
  45. package/src/planners/shortestPathPlanner.ts +0 -160
package/src/agent.test.ts CHANGED
@@ -7,7 +7,7 @@ import { dummyResponseValues, MockLanguageModelV1 } from './mockModel';
7
7
 
8
8
  test('an agent has the expected interface', () => {
9
9
  const agent = createAgent({
10
- name: 'test',
10
+ id: 'test',
11
11
  events: {},
12
12
  model: new MockLanguageModelV1(),
13
13
  });
@@ -31,7 +31,7 @@ test('agent.addMessage() adds to message history', () => {
31
31
  const model = new MockLanguageModelV1();
32
32
 
33
33
  const agent = createAgent({
34
- name: 'test',
34
+ id: 'test',
35
35
  events: {},
36
36
  model,
37
37
  });
@@ -65,7 +65,7 @@ test('agent.addMessage() adds to message history', () => {
65
65
 
66
66
  test('agent.addFeedback() adds to feedback', () => {
67
67
  const agent = createAgent({
68
- name: 'test',
68
+ id: 'test',
69
69
  events: {},
70
70
  model: {} as any,
71
71
  });
@@ -106,7 +106,7 @@ test('agent.addFeedback() adds to feedback', () => {
106
106
 
107
107
  test('agent.addObservation() adds to observations', () => {
108
108
  const agent = createAgent({
109
- name: 'test',
109
+ id: 'test',
110
110
  events: {},
111
111
  model: {} as any,
112
112
  });
@@ -132,7 +132,7 @@ test('agent.addObservation() adds to observations', () => {
132
132
 
133
133
  test('agent.addObservation() adds to observations (initial state)', () => {
134
134
  const agent = createAgent({
135
- name: 'test',
135
+ id: 'test',
136
136
  events: {},
137
137
  model: {} as any,
138
138
  });
@@ -154,7 +154,7 @@ test('agent.addObservation() adds to observations (initial state)', () => {
154
154
 
155
155
  test('agent.addObservation() adds to observations with machine hash', () => {
156
156
  const agent = createAgent({
157
- name: 'test',
157
+ id: 'test',
158
158
  events: {},
159
159
  model: {} as any,
160
160
  });
@@ -194,7 +194,7 @@ test('agent.addObservation() adds to observations with machine hash', () => {
194
194
 
195
195
  test('agent.addFeedback() adds to feedback (with observation)', () => {
196
196
  const agent = createAgent({
197
- name: 'test',
197
+ id: 'test',
198
198
  events: {},
199
199
  model: {} as any,
200
200
  });
@@ -251,7 +251,7 @@ test('agent.interact() observes machine actors (no 2nd arg)', () => {
251
251
  });
252
252
 
253
253
  const agent = createAgent({
254
- name: 'test',
254
+ id: 'test',
255
255
  events: {},
256
256
  model: {} as any,
257
257
  });
@@ -289,7 +289,7 @@ test('agent.interact() observes machine actors (no 2nd arg)', () => {
289
289
  test('You can listen for feedback events', () => {
290
290
  const fn = vi.fn();
291
291
  const agent = createAgent({
292
- name: 'test',
292
+ id: 'test',
293
293
  events: {},
294
294
  model: {} as any,
295
295
  });
@@ -332,7 +332,7 @@ test('You can listen for plan events', async () => {
332
332
  });
333
333
 
334
334
  const agent = createAgent({
335
- name: 'test',
335
+ id: 'test',
336
336
  model,
337
337
  events: {
338
338
  WIN: z.object({}),
@@ -393,3 +393,143 @@ test('agent.types provides context and event types', () => {
393
393
  // @ts-expect-error
394
394
  agent.types.context satisfies { score: string };
395
395
  });
396
+
397
+ test('It allows unrecognized events', () => {
398
+ const agent = createAgent({
399
+ model: {} as any,
400
+ events: {},
401
+ context: {},
402
+ });
403
+
404
+ expect(() => {
405
+ agent.send({
406
+ // @ts-expect-error
407
+ type: 'unrecognized',
408
+ });
409
+ }).not.toThrow();
410
+ });
411
+
412
+ test('You can listen for message events', () => {
413
+ const fn = vi.fn();
414
+ const agent = createAgent({
415
+ id: 'test',
416
+ events: {},
417
+ model: {} as any,
418
+ });
419
+
420
+ agent.onMessage(fn);
421
+
422
+ const message = {
423
+ role: 'user' as const,
424
+ content: [{ type: 'text' as const, text: 'test message' }],
425
+ };
426
+
427
+ agent.addMessage(message);
428
+
429
+ expect(fn).toHaveBeenCalledWith(
430
+ expect.objectContaining({
431
+ role: 'user',
432
+ content: [{ type: 'text', text: 'test message' }],
433
+ episodeId: expect.any(String),
434
+ timestamp: expect.any(Number),
435
+ })
436
+ );
437
+ });
438
+
439
+ test('agent.getPlans() returns plans from context', () => {
440
+ const agent = createAgent({
441
+ id: 'test',
442
+ events: {},
443
+ model: {} as any,
444
+ planner: async (agent) => {
445
+ return {
446
+ episodeId: agent.episodeId,
447
+ planner: 'test-planner',
448
+ goal: '',
449
+ goalState: undefined,
450
+ paths: [
451
+ {
452
+ state: undefined,
453
+ steps: [],
454
+ },
455
+ ],
456
+ nextEvent: undefined,
457
+ timestamp: Date.now(),
458
+ };
459
+ },
460
+ });
461
+
462
+ const plans = agent.getPlans();
463
+
464
+ expect(plans).toBeDefined();
465
+ expect(Array.isArray(plans)).toBe(true);
466
+ });
467
+
468
+ test('Event listeners can be unsubscribed', () => {
469
+ const fn = vi.fn();
470
+ const agent = createAgent({
471
+ id: 'test',
472
+ events: {},
473
+ model: {} as any,
474
+ });
475
+
476
+ const subscription = agent.on('message', fn);
477
+
478
+ agent.addMessage({
479
+ role: 'user',
480
+ content: [{ type: 'text', text: 'first message' }],
481
+ });
482
+
483
+ expect(fn).toHaveBeenCalledTimes(1);
484
+
485
+ subscription.unsubscribe();
486
+
487
+ agent.addMessage({
488
+ role: 'user',
489
+ content: [{ type: 'text', text: 'second message' }],
490
+ });
491
+
492
+ expect(fn).toHaveBeenCalledTimes(1); // Still only called once
493
+ });
494
+
495
+ test('agent.observe() adds observations from actor snapshots', () => {
496
+ const machine = createMachine({
497
+ initial: 'idle',
498
+ states: {
499
+ idle: {
500
+ on: { START: 'running' },
501
+ },
502
+ running: {},
503
+ },
504
+ });
505
+
506
+ const agent = createAgent({
507
+ id: 'test',
508
+ events: {},
509
+ model: {} as any,
510
+ });
511
+
512
+ const actor = createActor(machine);
513
+ const subscription = agent.observe(actor);
514
+
515
+ actor.start();
516
+ actor.send({ type: 'START' });
517
+
518
+ expect(agent.getObservations()).toContainEqual(
519
+ expect.objectContaining({
520
+ state: expect.objectContaining({ value: 'idle' }),
521
+ machineHash: expect.any(String),
522
+ })
523
+ );
524
+
525
+ expect(agent.getObservations()).toContainEqual(
526
+ expect.objectContaining({
527
+ prevState: expect.objectContaining({ value: 'idle' }),
528
+ event: { type: 'START' },
529
+ state: expect.objectContaining({ value: 'running' }),
530
+ machineHash: expect.any(String),
531
+ })
532
+ );
533
+
534
+ subscription.unsubscribe();
535
+ });
package/src/agent.ts CHANGED
@@ -27,10 +27,11 @@ import {
27
27
  Compute,
28
28
  AgentDecisionInput,
29
29
  AgentDecideOptions,
30
+ AnyAgent,
30
31
  } from './types';
31
- import { simplePlanner } from './planners/simplePlanner';
32
+ import { simplePlanner } from './planners/simple';
32
33
  import { agentDecide } from './decide';
33
- import { getMachineHash, isActorRef, randomId } from './utils';
34
+ import { getMachineHash, isActorRef, isMachineActor, randomId } from './utils';
34
35
  import {
35
36
  experimental_wrapLanguageModel,
36
37
  LanguageModel,
@@ -77,8 +78,11 @@ export const agentLogic: AgentLogic<AnyEventObject> = fromTransition(
77
78
  });
78
79
  break;
79
80
  }
80
- default:
81
+ default: {
82
+ // unrecognized
83
+ console.warn('Unrecognized event', event);
81
84
  break;
85
+ }
82
86
  }
83
87
  return state;
84
88
  },
@@ -98,22 +102,18 @@ export function createAgent<
98
102
  TContext = ContextFromZodContextMapping<TContextSchema>
99
103
  >({
100
104
  id,
101
- name,
102
- description,
105
+ description: description,
103
106
  model,
104
107
  events,
105
108
  context,
106
109
  planner = simplePlanner as AgentPlanner<Agent<TContextSchema, TEventSchemas>>,
107
- stringify = JSON.stringify,
108
- getMemory,
109
110
  logic = agentLogic as AgentLogic<TEvents>,
110
- ...generateTextOptions
111
111
  }: {
112
112
  /**
113
113
  * The unique identifier for the agent.
114
114
  *
115
115
  * This should be the same across all sessions of a specific agent, as it can be
116
- * used to retrieve memory for this agent.
116
+ * used to retrieve memory for previous episodes of this agent.
117
117
  *
118
118
  * @example
119
119
  * ```ts
@@ -124,10 +124,6 @@ export function createAgent<
124
124
  * ```
125
125
  */
126
126
  id?: string;
127
- /**
128
- * The name of the agent
129
- */
130
- name?: string;
131
127
  /**
132
128
  * A description of the role of the agent
133
129
  */
@@ -150,210 +146,17 @@ export function createAgent<
150
146
  * Agent logic
151
147
  */
152
148
  logic?: AgentLogic<TEvents>;
149
+ model: LanguageModel;
153
150
  } & GenerateTextOptions): Agent<TContextSchema, TEventSchemas> {
154
151
  return new Agent({
155
152
  id,
156
153
  context,
157
154
  events,
158
- name,
159
155
  description,
160
156
  planner,
161
157
  model,
162
158
  logic,
163
159
  }) as any;
164
- // const agent = createActor(logic) as unknown as Agent<TContext, TEvents>;
165
- // agent.events = events;
166
- // agent.model = model;
167
- // agent.name = name;
168
- // agent.description = description;
169
- // agent.defaultOptions = { ...generateTextOptions, model };
170
- // agent.memory = getMemory ? getMemory(agent) : undefined;
171
-
172
- // agent.onMessage = (callback) => {
173
- // agent.on('message', (ev) => callback(ev.message));
174
- // };
175
-
176
- // agent.decide = (opts) => {
177
- // return agentDecide(agent, opts);
178
- // };
179
-
180
- // agent.addMessage = (messageInput) => {
181
- // const message = {
182
- // ...messageInput,
183
- // id: messageInput.id ?? randomId(),
184
- // timestamp: messageInput.timestamp ?? Date.now(),
185
- // sessionId: agent.sessionId,
186
- // } satisfies AgentMessage;
187
- // agent.send({
188
- // type: 'agent.message',
189
- // message,
190
- // });
191
-
192
- // return message;
193
- // };
194
- // agent.getMessages = () => agent.getSnapshot().context.messages;
195
-
196
- // agent.addFeedback = (feedbackInput) => {
197
- // const feedback = {
198
- // ...feedbackInput,
199
- // attributes: { ...feedbackInput.attributes },
200
- // reward: feedbackInput.reward ?? 0,
201
- // timestamp: feedbackInput.timestamp ?? Date.now(),
202
- // sessionId: agent.sessionId,
203
- // } satisfies AgentFeedback;
204
- // agent.send({
205
- // type: 'agent.feedback',
206
- // feedback,
207
- // });
208
- // return feedback;
209
- // };
210
- // agent.getFeedback = () => agent.getSnapshot().context.feedback;
211
-
212
- // agent.addObservation = (observationInput) => {
213
- // const { prevState, event, state } = observationInput;
214
- // const observedState = { context: state.context, value: state.value };
215
- // const observedPrevState = prevState
216
- // ? {
217
- // context: prevState.context,
218
- // value: prevState.value,
219
- // }
220
- // : undefined;
221
- // const observation = {
222
- // prevState: observedPrevState,
223
- // event,
224
- // state: observedState,
225
- // id: observationInput.id ?? randomId(),
226
- // sessionId: agent.sessionId,
227
- // timestamp: observationInput.timestamp ?? Date.now(),
228
- // machineHash: observationInput.machine
229
- // ? getMachineHash(observationInput.machine)
230
- // : undefined,
231
- // } satisfies AgentObservation<any>;
232
-
233
- // agent.send({
234
- // type: 'agent.observe',
235
- // observation,
236
- // });
237
-
238
- // return observation;
239
- // };
240
- // agent.getObservations = () => agent.getSnapshot().context.observations;
241
-
242
- // agent.addPlan = (plan) => {
243
- // agent.send({
244
- // type: 'agent.plan',
245
- // plan,
246
- // });
247
- // };
248
- // agent.getPlans = () => agent.getSnapshot().context.plans;
249
-
250
- // agent.interact = ((actorRef, getInput) => {
251
- // let prevState: ObservedState | undefined = undefined;
252
- // let subscribed = true;
253
-
254
- // async function handleObservation(observationInput: AgentObservationInput) {
255
- // const observation = agent.addObservation(observationInput);
256
-
257
- // const input = getInput?.(observation);
258
-
259
- // if (input) {
260
- // await agentDecide(agent, {
261
- // machine: actorRef.src as AnyStateMachine,
262
- // state: observation.state,
263
- // execute: async (event) => {
264
- // actorRef.send(event);
265
- // },
266
- // ...input,
267
- // });
268
- // }
269
-
270
- // prevState = observationInput.state;
271
- // }
272
-
273
- // // Inspect system, but only observe specified actor
274
- // const sub = actorRef.system.inspect({
275
- // next: async (inspEvent) => {
276
- // if (
277
- // !subscribed ||
278
- // inspEvent.actorRef !== actorRef ||
279
- // inspEvent.type !== '@xstate.snapshot'
280
- // ) {
281
- // return;
282
- // }
283
-
284
- // const observationInput = {
285
- // event: inspEvent.event,
286
- // prevState,
287
- // state: inspEvent.snapshot as any,
288
- // machine: (actorRef as any).src,
289
- // } satisfies AgentObservationInput;
290
-
291
- // await handleObservation(observationInput);
292
- // },
293
- // });
294
-
295
- // // If actor already started, interact with current state
296
- // if ((actorRef as any)._processingStatus === 1) {
297
- // handleObservation({
298
- // prevState: undefined,
299
- // event: { type: '' }, // TODO: unknown events?
300
- // state: actorRef.getSnapshot(),
301
- // machine: (actorRef as any).src,
302
- // });
303
- // }
304
-
305
- // return {
306
- // unsubscribe: () => {
307
- // sub.unsubscribe();
308
- // subscribed = false;
309
- // },
310
- // };
311
- // }) as typeof agent.interact;
312
-
313
- // agent.observe = (actorRef) => {
314
- // let prevState: ObservedState = actorRef.getSnapshot();
315
-
316
- // const sub = actorRef.system.inspect({
317
- // next: async (inspEvent) => {
318
- // if (
319
- // inspEvent.actorRef !== actorRef ||
320
- // inspEvent.type !== '@xstate.snapshot'
321
- // ) {
322
- // return;
323
- // }
324
-
325
- // const observationInput = {
326
- // event: inspEvent.event,
327
- // prevState,
328
- // state: inspEvent.snapshot as any,
329
- // machine: (actorRef as any).src,
330
- // } satisfies AgentObservationInput;
331
-
332
- // prevState = observationInput.state;
333
-
334
- // agent.addObservation(observationInput);
335
- // },
336
- // });
337
-
338
- // return sub;
339
- // };
340
-
341
- // agent.types = {} as any;
342
-
343
- // agent.wrap = (modelToWrap) =>
344
- // experimental_wrapLanguageModel({
345
- // model: modelToWrap,
346
- // middleware: createAgentMiddleware(agent),
347
- // });
348
-
349
- // agent.model = experimental_wrapLanguageModel({
350
- // model,
351
- // middleware: createAgentMiddleware(agent),
352
- // });
353
-
354
- // agent.start();
355
-
356
- // return agent;
357
160
  }
358
161
 
359
162
  export class Agent<
@@ -381,7 +184,7 @@ export class Agent<
381
184
  };
382
185
  public model: LanguageModel;
383
186
  public memory: AgentLongTermMemory | undefined;
384
- public defaultOptions: any; // todo
187
+ public defaultOptions: AgentDecideOptions<AnyAgent> | undefined; // todo
385
188
 
386
189
  constructor({
387
190
  logic = agentLogic as AgentLogic<TEvents>,
@@ -566,7 +369,8 @@ export class Agent<
566
369
  observation: AgentObservation<TActor>
567
370
  ) => AgentDecisionInput | undefined
568
371
  ): Subscription {
569
- const actorRefCheck = isActorRef(actorRef);
372
+ const actorRefCheck = isActorRef(actorRef) && actorRef.src;
373
+ const machine = isMachineActor(actorRef) ? actorRef.src : undefined;
570
374
 
571
375
  let prevState: ObservedState | undefined = undefined;
572
376
  let subscribed = true;
@@ -579,16 +383,15 @@ export class Agent<
579
383
  const input = getInput?.(observation);
580
384
 
581
385
  if (input) {
582
- await agentDecide(agent, {
583
- machine: actorRefCheck
584
- ? (actorRef.src as AnyStateMachine)
585
- : undefined,
386
+ const res = await agentDecide(agent, {
387
+ machine,
586
388
  state: observation.state,
587
- execute: async (event) => {
588
- actorRef.send(event);
589
- },
590
389
  ...input,
591
390
  });
391
+
392
+ if (res?.nextEvent) {
393
+ actorRef.send(res.nextEvent);
394
+ }
592
395
  }
593
396
 
594
397
  prevState = observationInput.state;
@@ -682,7 +485,7 @@ export class Agent<
682
485
  * - The `machine` (e.g. a state machine) that specifies what can happen next
683
486
  * - Additional `context`
684
487
  */
685
- public decide(opts: AgentDecideOptions) {
488
+ public decide(opts: AgentDecideOptions<this>) {
686
489
  return agentDecide(this, opts);
687
490
  }
688
491
  }
@@ -28,7 +28,7 @@ test('fromDecision() makes a decision', async () => {
28
28
  doGenerate,
29
29
  });
30
30
  const agent = createAgent({
31
- name: 'test',
31
+ id: 'test',
32
32
  model,
33
33
  events: {
34
34
  doFirst: z.object({}),
@@ -73,7 +73,7 @@ test('interacts with an actor', async () => {
73
73
  doGenerate,
74
74
  });
75
75
  const agent = createAgent({
76
- name: 'test',
76
+ id: 'test',
77
77
  model,
78
78
  events: {
79
79
  doFirst: z.object({}),
@@ -116,7 +116,7 @@ test('interacts with an actor (late interaction)', async () => {
116
116
  doGenerate,
117
117
  });
118
118
  const agent = createAgent({
119
- name: 'test',
119
+ id: 'test',
120
120
  model,
121
121
  events: {
122
122
  doFirst: z.object({}),
@@ -153,3 +153,124 @@ test('interacts with an actor (late interaction)', async () => {
153
153
 
154
154
  expect(actor.getSnapshot().value).toBe('third');
155
155
  });
156
+
157
+ test('agent.decide() makes a decision based on goal and state (simple planner)', async () => {
158
+ const model = new MockLanguageModelV1({
159
+ doGenerate,
160
+ });
161
+
162
+ const agent = createAgent({
163
+ id: 'test',
164
+ model,
165
+ events: {
166
+ MOVE: z.object({}),
167
+ },
168
+ });
169
+
170
+ const plan = await agent.decide({
171
+ goal: 'Make the best move',
172
+ state: {
173
+ value: 'playing',
174
+ context: {
175
+ board: [0, 0, 0],
176
+ },
177
+ },
178
+ machine: createMachine({
179
+ initial: 'playing',
180
+ states: {
181
+ playing: {
182
+ on: {
183
+ MOVE: 'next',
184
+ },
185
+ },
186
+ next: {},
187
+ },
188
+ }),
189
+ });
190
+
191
+ expect(plan).toBeDefined();
192
+ expect(plan!.nextEvent).toEqual(
193
+ expect.objectContaining({
194
+ type: 'MOVE',
195
+ })
196
+ );
197
+ });
198
+
199
+ test.each([
200
+ [undefined, true],
201
+ [undefined, false],
202
+ [3, true],
203
+ [3, false],
204
+ ])(
205
+ 'agent.decide() retries if a decision is not made (%i attempts, succeed: %s)',
206
+ async (maxAttempts, succeed) => {
207
+ let attempts = 0;
208
+ const doGenerateWithRetry = async (params: LanguageModelV1CallOptions) => {
209
+ const keys =
210
+ params.mode.type === 'regular'
211
+ ? params.mode.tools?.map((t) => t.name)
212
+ : [];
213
+
214
+ console.log('try', attempts, 'max', maxAttempts);
215
+
216
+ const toolCalls =
217
+ succeed && attempts++ === (maxAttempts ?? 2) - 1
218
+ ? [
219
+ {
220
+ toolCallType: 'function',
221
+ toolCallId: 'call-1',
222
+ toolName: keys![0],
223
+ args: `{ "type": "${keys?.[0]}" }`,
224
+ },
225
+ ]
226
+ : [];
227
+
228
+ return {
229
+ ...dummyResponseValues,
230
+ finishReason: 'tool-calls',
231
+ toolCalls,
232
+ } as any;
233
+ };
234
+ const model = new MockLanguageModelV1({
235
+ doGenerate: doGenerateWithRetry,
236
+ });
237
+
238
+ const agent = createAgent({
239
+ id: 'test',
240
+ model,
241
+ events: {
242
+ MOVE: z.object({}),
243
+ },
244
+ });
245
+
246
+ const plan = await agent.decide({
247
+ goal: 'Make the best move',
248
+ state: {
249
+ value: 'playing',
250
+ },
251
+ machine: createMachine({
252
+ initial: 'playing',
253
+ states: {
254
+ playing: {
255
+ on: {
256
+ MOVE: 'win',
257
+ },
258
+ },
259
+ win: {},
260
+ },
261
+ }),
262
+ maxAttempts,
263
+ });
264
+
265
+ if (!succeed) {
266
+ expect(plan).toBeUndefined();
267
+ } else {
268
+ expect(plan).toBeDefined();
269
+ expect(plan!.nextEvent).toEqual(
270
+ expect.objectContaining({
271
+ type: 'MOVE',
272
+ })
273
+ );
274
+ }
275
+ }
276
+ );