@sublang/playbook 7.0.0 → 8.0.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 (61) hide show
  1. package/README.md +17 -4
  2. package/docs/cli.md +74 -29
  3. package/docs/configuration.md +209 -112
  4. package/docs/embedding.md +71 -25
  5. package/package.json +4 -3
  6. package/reference/sdlc/captain.playbook/captain.playbook.js +3 -3
  7. package/reference/sdlc/captain.playbook/captain.playbook.ts +3 -3
  8. package/reference/sdlc/code.md +1 -1
  9. package/reference/sdlc/code.playbook/bin/interactive-session.js +816 -0
  10. package/reference/sdlc/code.playbook/bin/launch-config.js +1078 -116
  11. package/reference/sdlc/code.playbook/bin/playbook.js +489 -34
  12. package/reference/sdlc/code.playbook/bin/run.js +283 -298
  13. package/reference/sdlc/code.playbook/bin/session-store.js +818 -26
  14. package/reference/sdlc/code.playbook/code.fsm.d.ts +5 -5
  15. package/reference/sdlc/code.playbook/code.fsm.introspect.js +2 -2
  16. package/reference/sdlc/code.playbook/code.fsm.introspect.ts +2 -2
  17. package/reference/sdlc/code.playbook/code.fsm.js +7 -11
  18. package/reference/sdlc/code.playbook/code.fsm.ts +9 -17
  19. package/reference/sdlc/code.playbook/code.gears.md +1 -1
  20. package/reference/sdlc/code.playbook/code.playbook.d.ts +2 -1
  21. package/reference/sdlc/code.playbook/code.playbook.js +12 -13
  22. package/reference/sdlc/code.playbook/code.playbook.ts +22 -15
  23. package/reference/sdlc/code.playbook/code.registry.d.ts +5 -13
  24. package/reference/sdlc/code.playbook/code.registry.js +3 -10
  25. package/reference/sdlc/code.playbook/code.registry.ts +7 -32
  26. package/reference/sdlc/code.playbook/playbook-captain.d.ts +39 -14
  27. package/reference/sdlc/code.playbook/playbook-captain.js +970 -289
  28. package/reference/sdlc/code.playbook/playbook-captain.ts +1403 -396
  29. package/reference/sdlc/code.playbook/playbook.config.template.yaml +41 -49
  30. package/reference/sdlc/decide.md +4 -4
  31. package/reference/sdlc/decide.playbook/decide.fsm.d.ts +9 -9
  32. package/reference/sdlc/decide.playbook/decide.fsm.js +21 -14
  33. package/reference/sdlc/decide.playbook/decide.fsm.ts +27 -23
  34. package/reference/sdlc/decide.playbook/decide.gears.md +3 -5
  35. package/reference/sdlc/decide.playbook/decide.playbook.d.ts +9 -13
  36. package/reference/sdlc/decide.playbook/decide.playbook.js +171 -134
  37. package/reference/sdlc/decide.playbook/decide.playbook.ts +238 -162
  38. package/reference/sdlc/decide.playbook/decide.registry.d.ts +5 -13
  39. package/reference/sdlc/decide.playbook/decide.registry.js +3 -9
  40. package/reference/sdlc/decide.playbook/decide.registry.ts +7 -31
  41. package/reference/sdlc/review.md +4 -5
  42. package/reference/sdlc/review.playbook/review.fsm.d.ts +9 -11
  43. package/reference/sdlc/review.playbook/review.fsm.js +30 -24
  44. package/reference/sdlc/review.playbook/review.fsm.ts +39 -35
  45. package/reference/sdlc/review.playbook/review.gears.md +6 -5
  46. package/reference/sdlc/review.playbook/review.playbook.d.ts +2 -1
  47. package/reference/sdlc/review.playbook/review.playbook.js +16 -21
  48. package/reference/sdlc/review.playbook/review.playbook.ts +26 -26
  49. package/reference/sdlc/review.playbook/review.registry.d.ts +5 -13
  50. package/reference/sdlc/review.playbook/review.registry.js +3 -16
  51. package/reference/sdlc/review.playbook/review.registry.ts +7 -38
  52. package/slc/gears2fsm.md +27 -23
  53. package/slc/link.md +113 -93
  54. package/slc/text2gears.md +19 -18
  55. package/src/runtime.d.ts +20 -16
  56. package/src/runtime.ts +19 -23
  57. package/src/xstate-playbook-runtime.d.ts +21 -17
  58. package/src/xstate-playbook-runtime.js +241 -149
  59. package/src/xstate-playbook-runtime.ts +331 -178
  60. package/src/xstate-runtime.js +63 -24
  61. package/src/xstate-runtime.ts +96 -28
@@ -268,6 +268,25 @@ function capturedSessionStore(descriptors) {
268
268
  restore: method('restore'),
269
269
  });
270
270
  }
271
+ function capturedRoleBindings(descriptors) {
272
+ const captured = snapshotJsonValue(capturedDataValue(descriptors, 'roleBindings', 'playbook session roleBindings'), 'playbook session roleBindings');
273
+ if (!isRecord(captured)) {
274
+ throw new TypeError('playbook session roleBindings must be an object');
275
+ }
276
+ const bindings = {};
277
+ for (const [roleId, value] of Object.entries(captured)) {
278
+ requireNonEmptyString(roleId, 'playbook session roleBindings role id');
279
+ if (!isRecord(value)) {
280
+ throw new TypeError(`playbook session roleBindings.${roleId} must be an object`);
281
+ }
282
+ rejectUnknownKeys(value, ['playerId', 'promptIdentity'], `playbook session roleBindings.${roleId}`);
283
+ defineEnumerableDataProperty(bindings, roleId, Object.freeze({
284
+ playerId: requireNonEmptyString(value.playerId, `playbook session roleBindings.${roleId}.playerId`),
285
+ promptIdentity: requireNonEmptyString(value.promptIdentity, `playbook session roleBindings.${roleId}.promptIdentity`),
286
+ }));
287
+ }
288
+ return Object.freeze(bindings);
289
+ }
271
290
  /** Validate session causality and detach its immutable identity from the host. */
272
291
  export function snapshotPlaybookSession(session) {
273
292
  if (!isRecord(session)) {
@@ -287,6 +306,7 @@ export function snapshotPlaybookSession(session) {
287
306
  const hasParentSessionId = Object.prototype.hasOwnProperty.call(sessionDescriptors, 'parentSessionId');
288
307
  const hasParentCallId = Object.prototype.hasOwnProperty.call(sessionDescriptors, 'parentCallId');
289
308
  const hasPlayerSessions = Object.prototype.hasOwnProperty.call(sessionDescriptors, 'playerSessions');
309
+ const hasRoleBindings = Object.prototype.hasOwnProperty.call(sessionDescriptors, 'roleBindings');
290
310
  let parentSessionId;
291
311
  let parentCallId;
292
312
  if (depth === 0) {
@@ -320,6 +340,9 @@ export function snapshotPlaybookSession(session) {
320
340
  const playerSessions = hasPlayerSessions
321
341
  ? capturedSessionStore(sessionDescriptors)
322
342
  : undefined;
343
+ const roleBindings = hasRoleBindings
344
+ ? capturedRoleBindings(sessionDescriptors)
345
+ : undefined;
323
346
  return Object.freeze({
324
347
  sessionId,
325
348
  playbookId,
@@ -327,6 +350,7 @@ export function snapshotPlaybookSession(session) {
327
350
  ...(parentSessionId === undefined ? {} : { parentSessionId }),
328
351
  ...(parentCallId === undefined ? {} : { parentCallId }),
329
352
  depth,
353
+ ...(roleBindings === undefined ? {} : { roleBindings }),
330
354
  ...(playerSessions === undefined ? {} : { playerSessions }),
331
355
  ports,
332
356
  });
@@ -548,9 +572,10 @@ function snapshotSuspendedCall(value, path = 'runtime snapshot suspendedCall') {
548
572
  }
549
573
  return Object.freeze(call);
550
574
  }
551
- // DR-014 §1 / DR-031 §5: validate and detach a host-supplied runtime
552
- // snapshot before restore touches any state. A suspended schema-2 call is
553
- // rejected unless the restore path explicitly promises to seed and claim it.
575
+ // DR-014 §1 / DR-031 §5 / DR-032: validate and detach a host-supplied
576
+ // schema-3 runtime snapshot before restore touches any state. A suspended
577
+ // call is rejected unless the restore path explicitly promises to seed and
578
+ // claim it; older schemas are rejected rather than guessing role identity.
554
579
  export function assertPlaybookRuntimeSnapshot(value, expectedPlaybookId, options = {}) {
555
580
  const snapshot = snapshotJsonValue(value, 'runtime snapshot');
556
581
  if (!isRecord(snapshot)) {
@@ -566,25 +591,21 @@ export function assertPlaybookRuntimeSnapshot(value, expectedPlaybookId, options
566
591
  throw new TypeError('runtime snapshot validation options.allowSuspendedCall must be boolean');
567
592
  }
568
593
  const allowSuspendedCall = capturedOptions.allowSuspendedCall ?? false;
569
- if (snapshot.schemaVersion !== 1 && snapshot.schemaVersion !== 2) {
570
- throw new TypeError(`runtime snapshot schemaVersion ${String(snapshot.schemaVersion)} is not supported (expected 1 or 2)`);
594
+ if (snapshot.schemaVersion !== 3) {
595
+ throw new TypeError(`runtime snapshot schemaVersion ${String(snapshot.schemaVersion)} is not supported (expected 3)`);
571
596
  }
572
- const schemaVersion = snapshot.schemaVersion;
573
597
  rejectUnknownKeys(snapshot, [
574
598
  'schemaVersion',
575
599
  'playbookId',
576
600
  'machine',
577
- 'playerResumeTokens',
601
+ 'roleResumeTokens',
578
602
  'sequences',
579
603
  'state',
580
604
  'pendingBossQuestions',
581
605
  'suspendedCall',
582
606
  ], 'runtime snapshot');
583
- if (schemaVersion === 1 && own(snapshot, 'suspendedCall')) {
584
- throw new TypeError('runtime snapshot schemaVersion 1 must not carry suspendedCall');
585
- }
586
607
  let suspendedCall;
587
- if (schemaVersion === 2 && own(snapshot, 'suspendedCall')) {
608
+ if (own(snapshot, 'suspendedCall')) {
588
609
  suspendedCall = snapshotSuspendedCall(snapshot.suspendedCall);
589
610
  if (!allowSuspendedCall) {
590
611
  throw new TypeError('runtime snapshot suspendedCall requires a restore path that explicitly allows it');
@@ -598,12 +619,12 @@ export function assertPlaybookRuntimeSnapshot(value, expectedPlaybookId, options
598
619
  throw new TypeError('runtime snapshot machine must be an object');
599
620
  }
600
621
  const machine = snapshot.machine;
601
- if (!isRecord(snapshot.playerResumeTokens)) {
602
- throw new TypeError('runtime snapshot playerResumeTokens must be an object');
622
+ if (!isRecord(snapshot.roleResumeTokens)) {
623
+ throw new TypeError('runtime snapshot roleResumeTokens must be an object');
603
624
  }
604
- const playerResumeTokens = {};
605
- for (const [playerId, token] of Object.entries(snapshot.playerResumeTokens)) {
606
- defineEnumerableDataProperty(playerResumeTokens, playerId, requireNonEmptyString(token, `runtime snapshot playerResumeTokens.${playerId}`));
625
+ const roleResumeTokens = {};
626
+ for (const [roleId, token] of Object.entries(snapshot.roleResumeTokens)) {
627
+ defineEnumerableDataProperty(roleResumeTokens, requireNonEmptyString(roleId, 'runtime snapshot roleResumeTokens role id'), requireNonEmptyString(token, `runtime snapshot roleResumeTokens.${roleId}`));
607
628
  }
608
629
  if (!isRecord(snapshot.sequences)) {
609
630
  throw new TypeError('runtime snapshot sequences must be an object');
@@ -627,7 +648,7 @@ export function assertPlaybookRuntimeSnapshot(value, expectedPlaybookId, options
627
648
  validateState(snapshot.state, 'runtime snapshot state');
628
649
  const state = snapshot.state;
629
650
  if (state.tags.includes(SUSPENDED_TAG) && suspendedCall === undefined) {
630
- throw new TypeError(`runtime snapshot state tagged ${SUSPENDED_TAG} requires schemaVersion 2 suspendedCall`);
651
+ throw new TypeError(`runtime snapshot state tagged ${SUSPENDED_TAG} requires suspendedCall`);
631
652
  }
632
653
  if (suspendedCall) {
633
654
  if (sequences.playbookCall === 0) {
@@ -654,10 +675,28 @@ export function assertPlaybookRuntimeSnapshot(value, expectedPlaybookId, options
654
675
  const path = `runtime snapshot pendingBossQuestions[${index}]`;
655
676
  if (!isRecord(entry))
656
677
  throw new TypeError(`${path} must be an object`);
657
- rejectUnknownKeys(entry, ['questionId', 'player', 'question', 'sourceItem'], path);
678
+ rejectUnknownKeys(entry, ['questionId', 'asker', 'question', 'sourceItem'], path);
679
+ if (!isRecord(entry.asker)) {
680
+ throw new TypeError(`${path}.asker must be an object`);
681
+ }
682
+ let asker;
683
+ if (entry.asker.kind === 'captain') {
684
+ rejectUnknownKeys(entry.asker, ['kind'], `${path}.asker`);
685
+ asker = Object.freeze({ kind: 'captain' });
686
+ }
687
+ else if (entry.asker.kind === 'role') {
688
+ rejectUnknownKeys(entry.asker, ['kind', 'roleId'], `${path}.asker`);
689
+ asker = Object.freeze({
690
+ kind: 'role',
691
+ roleId: requireNonEmptyString(entry.asker.roleId, `${path}.asker.roleId`),
692
+ });
693
+ }
694
+ else {
695
+ throw new TypeError(`${path}.asker.kind must be "captain" or "role"`);
696
+ }
658
697
  const question = {
659
698
  questionId: requireNonEmptyString(entry.questionId, `${path}.questionId`),
660
- player: requireNonEmptyString(entry.player, `${path}.player`),
699
+ asker,
661
700
  question: requireNonEmptyString(entry.question, `${path}.question`),
662
701
  ...(entry.sourceItem === undefined
663
702
  ? {}
@@ -670,16 +709,13 @@ export function assertPlaybookRuntimeSnapshot(value, expectedPlaybookId, options
670
709
  const fields = {
671
710
  playbookId,
672
711
  machine,
673
- playerResumeTokens: Object.freeze(playerResumeTokens),
712
+ roleResumeTokens: Object.freeze(roleResumeTokens),
674
713
  sequences: Object.freeze(sequences),
675
714
  state,
676
715
  pendingBossQuestions: Object.freeze(pendingBossQuestions),
677
716
  };
678
- if (schemaVersion === 1) {
679
- return Object.freeze({ schemaVersion: 1, ...fields });
680
- }
681
717
  return Object.freeze({
682
- schemaVersion: 2,
718
+ schemaVersion: 3,
683
719
  ...fields,
684
720
  ...(suspendedCall === undefined ? {} : { suspendedCall }),
685
721
  });
@@ -773,6 +809,9 @@ export function validatePlayerResult(value, path = 'player result') {
773
809
  rejectUnknownKeys(result, ['status', 'resumeToken', 'finalText', 'error'], path);
774
810
  validateRunStatus(result.status, `${path}.status`);
775
811
  validateOptionalString(result, 'resumeToken', path);
812
+ if (result.resumeToken !== undefined) {
813
+ requireNonEmptyString(result.resumeToken, `${path}.resumeToken`);
814
+ }
776
815
  validateOptionalString(result, 'finalText', path);
777
816
  validateOptionalString(result, 'error', path);
778
817
  return result;
@@ -386,6 +386,50 @@ function capturedSessionStore(
386
386
  });
387
387
  }
388
388
 
389
+ function capturedRoleBindings(
390
+ descriptors: PropertyDescriptorMap,
391
+ ): NonNullable<PlaybookSession['roleBindings']> {
392
+ const captured = snapshotJsonValue(
393
+ capturedDataValue(
394
+ descriptors,
395
+ 'roleBindings',
396
+ 'playbook session roleBindings',
397
+ ),
398
+ 'playbook session roleBindings',
399
+ );
400
+ if (!isRecord(captured)) {
401
+ throw new TypeError('playbook session roleBindings must be an object');
402
+ }
403
+ const bindings: Record<
404
+ string,
405
+ { readonly playerId: string; readonly promptIdentity: string }
406
+ > = {};
407
+ for (const [roleId, value] of Object.entries(captured)) {
408
+ requireNonEmptyString(roleId, 'playbook session roleBindings role id');
409
+ if (!isRecord(value)) {
410
+ throw new TypeError(
411
+ `playbook session roleBindings.${roleId} must be an object`,
412
+ );
413
+ }
414
+ rejectUnknownKeys(
415
+ value,
416
+ ['playerId', 'promptIdentity'],
417
+ `playbook session roleBindings.${roleId}`,
418
+ );
419
+ defineEnumerableDataProperty(bindings, roleId, Object.freeze({
420
+ playerId: requireNonEmptyString(
421
+ value.playerId,
422
+ `playbook session roleBindings.${roleId}.playerId`,
423
+ ),
424
+ promptIdentity: requireNonEmptyString(
425
+ value.promptIdentity,
426
+ `playbook session roleBindings.${roleId}.promptIdentity`,
427
+ ),
428
+ }));
429
+ }
430
+ return Object.freeze(bindings);
431
+ }
432
+
389
433
  /** Validate session causality and detach its immutable identity from the host. */
390
434
  export function snapshotPlaybookSession(
391
435
  session: PlaybookSession,
@@ -453,6 +497,10 @@ export function snapshotPlaybookSession(
453
497
  sessionDescriptors,
454
498
  'playerSessions',
455
499
  );
500
+ const hasRoleBindings = Object.prototype.hasOwnProperty.call(
501
+ sessionDescriptors,
502
+ 'roleBindings',
503
+ );
456
504
  let parentSessionId: string | undefined;
457
505
  let parentCallId: string | undefined;
458
506
  if (depth === 0) {
@@ -501,6 +549,9 @@ export function snapshotPlaybookSession(
501
549
  const playerSessions = hasPlayerSessions
502
550
  ? capturedSessionStore(sessionDescriptors)
503
551
  : undefined;
552
+ const roleBindings = hasRoleBindings
553
+ ? capturedRoleBindings(sessionDescriptors)
554
+ : undefined;
504
555
  return Object.freeze({
505
556
  sessionId,
506
557
  playbookId,
@@ -508,6 +559,7 @@ export function snapshotPlaybookSession(
508
559
  ...(parentSessionId === undefined ? {} : { parentSessionId }),
509
560
  ...(parentCallId === undefined ? {} : { parentCallId }),
510
561
  depth,
562
+ ...(roleBindings === undefined ? {} : { roleBindings }),
511
563
  ...(playerSessions === undefined ? {} : { playerSessions }),
512
564
  ports,
513
565
  });
@@ -805,9 +857,10 @@ function snapshotSuspendedCall(
805
857
  return Object.freeze(call);
806
858
  }
807
859
 
808
- // DR-014 §1 / DR-031 §5: validate and detach a host-supplied runtime
809
- // snapshot before restore touches any state. A suspended schema-2 call is
810
- // rejected unless the restore path explicitly promises to seed and claim it.
860
+ // DR-014 §1 / DR-031 §5 / DR-032: validate and detach a host-supplied
861
+ // schema-3 runtime snapshot before restore touches any state. A suspended
862
+ // call is rejected unless the restore path explicitly promises to seed and
863
+ // claim it; older schemas are rejected rather than guessing role identity.
811
864
  export function assertPlaybookRuntimeSnapshot(
812
865
  value: unknown,
813
866
  expectedPlaybookId: string,
@@ -838,19 +891,18 @@ export function assertPlaybookRuntimeSnapshot(
838
891
  );
839
892
  }
840
893
  const allowSuspendedCall = capturedOptions.allowSuspendedCall ?? false;
841
- if (snapshot.schemaVersion !== 1 && snapshot.schemaVersion !== 2) {
894
+ if (snapshot.schemaVersion !== 3) {
842
895
  throw new TypeError(
843
- `runtime snapshot schemaVersion ${String(snapshot.schemaVersion)} is not supported (expected 1 or 2)`,
896
+ `runtime snapshot schemaVersion ${String(snapshot.schemaVersion)} is not supported (expected 3)`,
844
897
  );
845
898
  }
846
- const schemaVersion = snapshot.schemaVersion;
847
899
  rejectUnknownKeys(
848
900
  snapshot,
849
901
  [
850
902
  'schemaVersion',
851
903
  'playbookId',
852
904
  'machine',
853
- 'playerResumeTokens',
905
+ 'roleResumeTokens',
854
906
  'sequences',
855
907
  'state',
856
908
  'pendingBossQuestions',
@@ -858,13 +910,8 @@ export function assertPlaybookRuntimeSnapshot(
858
910
  ],
859
911
  'runtime snapshot',
860
912
  );
861
- if (schemaVersion === 1 && own(snapshot, 'suspendedCall')) {
862
- throw new TypeError(
863
- 'runtime snapshot schemaVersion 1 must not carry suspendedCall',
864
- );
865
- }
866
913
  let suspendedCall: PlaybookSuspendedCall | undefined;
867
- if (schemaVersion === 2 && own(snapshot, 'suspendedCall')) {
914
+ if (own(snapshot, 'suspendedCall')) {
868
915
  suspendedCall = snapshotSuspendedCall(snapshot.suspendedCall);
869
916
  if (!allowSuspendedCall) {
870
917
  throw new TypeError(
@@ -885,19 +932,19 @@ export function assertPlaybookRuntimeSnapshot(
885
932
  throw new TypeError('runtime snapshot machine must be an object');
886
933
  }
887
934
  const machine = snapshot.machine;
888
- if (!isRecord(snapshot.playerResumeTokens)) {
935
+ if (!isRecord(snapshot.roleResumeTokens)) {
889
936
  throw new TypeError(
890
- 'runtime snapshot playerResumeTokens must be an object',
937
+ 'runtime snapshot roleResumeTokens must be an object',
891
938
  );
892
939
  }
893
- const playerResumeTokens: Record<string, string> = {};
894
- for (const [playerId, token] of Object.entries(snapshot.playerResumeTokens)) {
940
+ const roleResumeTokens: Record<string, string> = {};
941
+ for (const [roleId, token] of Object.entries(snapshot.roleResumeTokens)) {
895
942
  defineEnumerableDataProperty(
896
- playerResumeTokens,
897
- playerId,
943
+ roleResumeTokens,
944
+ requireNonEmptyString(roleId, 'runtime snapshot roleResumeTokens role id'),
898
945
  requireNonEmptyString(
899
946
  token,
900
- `runtime snapshot playerResumeTokens.${playerId}`,
947
+ `runtime snapshot roleResumeTokens.${roleId}`,
901
948
  ),
902
949
  );
903
950
  }
@@ -932,7 +979,7 @@ export function assertPlaybookRuntimeSnapshot(
932
979
  const state = snapshot.state as unknown as PlaybookState;
933
980
  if (state.tags.includes(SUSPENDED_TAG) && suspendedCall === undefined) {
934
981
  throw new TypeError(
935
- `runtime snapshot state tagged ${SUSPENDED_TAG} requires schemaVersion 2 suspendedCall`,
982
+ `runtime snapshot state tagged ${SUSPENDED_TAG} requires suspendedCall`,
936
983
  );
937
984
  }
938
985
  if (suspendedCall) {
@@ -976,15 +1023,36 @@ export function assertPlaybookRuntimeSnapshot(
976
1023
  if (!isRecord(entry)) throw new TypeError(`${path} must be an object`);
977
1024
  rejectUnknownKeys(
978
1025
  entry,
979
- ['questionId', 'player', 'question', 'sourceItem'],
1026
+ ['questionId', 'asker', 'question', 'sourceItem'],
980
1027
  path,
981
1028
  );
1029
+ if (!isRecord(entry.asker)) {
1030
+ throw new TypeError(`${path}.asker must be an object`);
1031
+ }
1032
+ let asker: PlaybookPendingBossQuestion['asker'];
1033
+ if (entry.asker.kind === 'captain') {
1034
+ rejectUnknownKeys(entry.asker, ['kind'], `${path}.asker`);
1035
+ asker = Object.freeze({ kind: 'captain' });
1036
+ } else if (entry.asker.kind === 'role') {
1037
+ rejectUnknownKeys(entry.asker, ['kind', 'roleId'], `${path}.asker`);
1038
+ asker = Object.freeze({
1039
+ kind: 'role',
1040
+ roleId: requireNonEmptyString(
1041
+ entry.asker.roleId,
1042
+ `${path}.asker.roleId`,
1043
+ ),
1044
+ });
1045
+ } else {
1046
+ throw new TypeError(
1047
+ `${path}.asker.kind must be "captain" or "role"`,
1048
+ );
1049
+ }
982
1050
  const question: PlaybookPendingBossQuestion = {
983
1051
  questionId: requireNonEmptyString(
984
1052
  entry.questionId,
985
1053
  `${path}.questionId`,
986
1054
  ),
987
- player: requireNonEmptyString(entry.player, `${path}.player`),
1055
+ asker,
988
1056
  question: requireNonEmptyString(entry.question, `${path}.question`),
989
1057
  ...(entry.sourceItem === undefined
990
1058
  ? {}
@@ -1001,16 +1069,13 @@ export function assertPlaybookRuntimeSnapshot(
1001
1069
  const fields = {
1002
1070
  playbookId,
1003
1071
  machine,
1004
- playerResumeTokens: Object.freeze(playerResumeTokens),
1072
+ roleResumeTokens: Object.freeze(roleResumeTokens),
1005
1073
  sequences: Object.freeze(sequences),
1006
1074
  state,
1007
1075
  pendingBossQuestions: Object.freeze(pendingBossQuestions),
1008
1076
  };
1009
- if (schemaVersion === 1) {
1010
- return Object.freeze({ schemaVersion: 1, ...fields });
1011
- }
1012
1077
  return Object.freeze({
1013
- schemaVersion: 2,
1078
+ schemaVersion: 3,
1014
1079
  ...fields,
1015
1080
  ...(suspendedCall === undefined ? {} : { suspendedCall }),
1016
1081
  });
@@ -1228,6 +1293,9 @@ export function validatePlayerResult(
1228
1293
  );
1229
1294
  validateRunStatus(result.status, `${path}.status`);
1230
1295
  validateOptionalString(result, 'resumeToken', path);
1296
+ if (result.resumeToken !== undefined) {
1297
+ requireNonEmptyString(result.resumeToken, `${path}.resumeToken`);
1298
+ }
1231
1299
  validateOptionalString(result, 'finalText', path);
1232
1300
  validateOptionalString(result, 'error', path);
1233
1301
  return result as unknown as PlayerResult;