@sublang/playbook 15.1.0 → 17.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 (102) hide show
  1. package/README.md +2 -0
  2. package/docs/cli.md +36 -18
  3. package/docs/configuration.md +7 -5
  4. package/docs/embedding.md +28 -7
  5. package/package.json +4 -4
  6. package/reference/sdlc/branch.playbook/branch.fsm.d.ts +63 -33
  7. package/reference/sdlc/branch.playbook/branch.fsm.js +190 -108
  8. package/reference/sdlc/branch.playbook/branch.fsm.ts +269 -147
  9. package/reference/sdlc/branch.playbook/branch.gears.md +7 -17
  10. package/reference/sdlc/branch.playbook/branch.playbook.d.ts +12 -16
  11. package/reference/sdlc/branch.playbook/branch.playbook.js +112 -101
  12. package/reference/sdlc/branch.playbook/branch.playbook.ts +128 -171
  13. package/reference/sdlc/branch.playbook/branch.registry.d.ts +3 -2
  14. package/reference/sdlc/branch.playbook/branch.registry.ts +6 -2
  15. package/reference/sdlc/captain.md +19 -10
  16. package/reference/sdlc/captain.playbook/captain.fsm.d.ts +6 -2
  17. package/reference/sdlc/captain.playbook/captain.fsm.js +19 -7
  18. package/reference/sdlc/captain.playbook/captain.fsm.ts +25 -10
  19. package/reference/sdlc/captain.playbook/captain.gears.md +16 -8
  20. package/reference/sdlc/captain.playbook/captain.playbook.d.ts +1 -1
  21. package/reference/sdlc/captain.playbook/captain.playbook.js +3 -2
  22. package/reference/sdlc/captain.playbook/captain.playbook.ts +4 -3
  23. package/reference/sdlc/code.playbook/bin/interactive-session.js +14 -1
  24. package/reference/sdlc/code.playbook/bin/portable-codec.js +3 -0
  25. package/reference/sdlc/code.playbook/bin/repository-effects.js +1 -0
  26. package/reference/sdlc/code.playbook/bin/run.js +128 -85
  27. package/reference/sdlc/code.playbook/bin/session-host.js +40 -8
  28. package/reference/sdlc/code.playbook/bin/session-store.js +162 -34
  29. package/reference/sdlc/code.playbook/code.fsm.d.ts +146 -93
  30. package/reference/sdlc/code.playbook/code.fsm.introspect.d.ts +8 -8
  31. package/reference/sdlc/code.playbook/code.fsm.introspect.ts +13 -13
  32. package/reference/sdlc/code.playbook/code.fsm.js +609 -649
  33. package/reference/sdlc/code.playbook/code.fsm.ts +890 -821
  34. package/reference/sdlc/code.playbook/code.gears.md +39 -29
  35. package/reference/sdlc/code.playbook/code.playbook.d.ts +13 -16
  36. package/reference/sdlc/code.playbook/code.playbook.js +166 -147
  37. package/reference/sdlc/code.playbook/code.playbook.ts +180 -229
  38. package/reference/sdlc/code.playbook/code.registry.d.ts +3 -2
  39. package/reference/sdlc/code.playbook/code.registry.ts +6 -2
  40. package/reference/sdlc/code.playbook/host-capabilities.d.ts +3 -0
  41. package/reference/sdlc/code.playbook/playbook-captain.d.ts +74 -2
  42. package/reference/sdlc/code.playbook/playbook-captain.js +1548 -468
  43. package/reference/sdlc/code.playbook/playbook-captain.ts +1993 -573
  44. package/reference/sdlc/code.playbook/session-host.d.ts +4 -1
  45. package/reference/sdlc/code.playbook/session-store.d.ts +17 -0
  46. package/reference/sdlc/code.playbook/session-store.js +1 -0
  47. package/reference/sdlc/decide.md +1 -0
  48. package/reference/sdlc/decide.playbook/decide.fsm.d.ts +273 -157
  49. package/reference/sdlc/decide.playbook/decide.fsm.js +765 -732
  50. package/reference/sdlc/decide.playbook/decide.fsm.ts +1167 -945
  51. package/reference/sdlc/decide.playbook/decide.gears.md +28 -21
  52. package/reference/sdlc/decide.playbook/decide.playbook.d.ts +11 -59
  53. package/reference/sdlc/decide.playbook/decide.playbook.js +155 -3696
  54. package/reference/sdlc/decide.playbook/decide.playbook.ts +186 -5244
  55. package/reference/sdlc/decide.playbook/decide.registry.d.ts +8 -4
  56. package/reference/sdlc/decide.playbook/decide.registry.js +2 -2
  57. package/reference/sdlc/decide.playbook/decide.registry.ts +13 -6
  58. package/reference/sdlc/dev.playbook/dev.fsm.d.ts +116 -134
  59. package/reference/sdlc/dev.playbook/dev.fsm.js +517 -575
  60. package/reference/sdlc/dev.playbook/dev.fsm.ts +667 -787
  61. package/reference/sdlc/dev.playbook/dev.gears.md +52 -54
  62. package/reference/sdlc/dev.playbook/dev.playbook.d.ts +13 -10
  63. package/reference/sdlc/dev.playbook/dev.playbook.js +128 -120
  64. package/reference/sdlc/dev.playbook/dev.playbook.ts +143 -193
  65. package/reference/sdlc/dev.playbook/dev.registry.d.ts +3 -2
  66. package/reference/sdlc/dev.playbook/dev.registry.ts +6 -2
  67. package/reference/sdlc/pr.playbook/pr.fsm.d.ts +95 -88
  68. package/reference/sdlc/pr.playbook/pr.fsm.js +461 -518
  69. package/reference/sdlc/pr.playbook/pr.fsm.ts +533 -663
  70. package/reference/sdlc/pr.playbook/pr.gears.md +47 -63
  71. package/reference/sdlc/pr.playbook/pr.playbook.d.ts +13 -23
  72. package/reference/sdlc/pr.playbook/pr.playbook.js +118 -112
  73. package/reference/sdlc/pr.playbook/pr.playbook.ts +136 -190
  74. package/reference/sdlc/pr.playbook/pr.registry.d.ts +3 -2
  75. package/reference/sdlc/pr.playbook/pr.registry.ts +4 -2
  76. package/reference/sdlc/review.md +3 -3
  77. package/reference/sdlc/review.playbook/review.fsm.d.ts +150 -116
  78. package/reference/sdlc/review.playbook/review.fsm.js +479 -550
  79. package/reference/sdlc/review.playbook/review.fsm.ts +717 -662
  80. package/reference/sdlc/review.playbook/review.gears.md +32 -29
  81. package/reference/sdlc/review.playbook/review.playbook.d.ts +12 -12
  82. package/reference/sdlc/review.playbook/review.playbook.js +180 -155
  83. package/reference/sdlc/review.playbook/review.playbook.ts +190 -230
  84. package/reference/sdlc/review.playbook/review.registry.d.ts +6 -5
  85. package/reference/sdlc/review.playbook/review.registry.js +3 -3
  86. package/reference/sdlc/review.playbook/review.registry.ts +9 -5
  87. package/slc/gears2fsm.md +77 -8
  88. package/slc/link.md +127 -60
  89. package/slc/materialize-link.mjs +51 -13
  90. package/slc/optimize.md +4 -1
  91. package/slc/prefix-prompts.mjs +29 -28
  92. package/slc/prefix.md +27 -24
  93. package/slc/slc.pin-inputs.json +11 -2
  94. package/slc/text2gears.md +71 -11
  95. package/src/runtime.d.ts +46 -2
  96. package/src/runtime.ts +42 -2
  97. package/src/xstate-playbook-runtime.d.ts +63 -23
  98. package/src/xstate-playbook-runtime.js +1637 -475
  99. package/src/xstate-playbook-runtime.ts +2105 -532
  100. package/src/xstate-runtime.d.ts +3 -4
  101. package/src/xstate-runtime.js +52 -0
  102. package/src/xstate-runtime.ts +59 -4
@@ -69,11 +69,9 @@ export type PlaybookReconciledSemanticOutput = Readonly<Record<string, string>>
69
69
  /** Retained presentation and semantic evidence, kept separate from output. */
70
70
  export interface PlaybookRetainedSemanticEvidence {
71
71
  readonly finalText?: string;
72
- readonly semanticCandidate: Readonly<Record<string, string>> & {
73
- readonly guard: string;
74
- };
72
+ readonly semanticCandidate: Readonly<Record<string, string>>;
75
73
  }
76
- export type PlaybookSemanticReconciliationReason = 'missing-presentation-evidence' | 'missing-repository-receipt' | 'invalid-repository-receipt' | 'repository-disposition-mismatch' | 'missing-effect-evidence' | 'missing-runtime-evidence' | 'inconsistent-runtime-evidence';
74
+ export type PlaybookSemanticReconciliationReason = 'no-matching-outcome' | 'missing-presentation-evidence' | 'missing-repository-receipt' | 'invalid-repository-receipt' | 'repository-disposition-mismatch' | 'missing-effect-evidence' | 'missing-runtime-evidence' | 'inconsistent-runtime-evidence';
77
75
  /** A fail-closed semantic/effect reconciliation decision. */
78
76
  export type PlaybookSemanticReconciliation = {
79
77
  readonly status: 'resolved' | 'deferred';
@@ -163,6 +161,7 @@ export interface NestedPlaybookBridge<TInput extends NestedPlaybookInput = Neste
163
161
  confirmRestore(): void;
164
162
  /** Complete durable identity; undefined until a normal or restored call suspends. */
165
163
  getSuspendedCall(): PlaybookSuspendedCall | undefined;
164
+ checkpointCall(child: PlaybookPendingCall): PlaybookSuspendedCall | undefined;
166
165
  resume(input: {
167
166
  callId: string;
168
167
  result: PlaybookCallResult;
@@ -351,6 +351,7 @@ export function snapshotPlaybookSession(session) {
351
351
  callPlaybook: capturedPort(portDescriptors, 'callPlaybook'),
352
352
  emitStatus: capturedPort(portDescriptors, 'emitStatus'),
353
353
  emitTelemetry: capturedPort(portDescriptors, 'emitTelemetry'),
354
+ ...(portDescriptors.recordStep === undefined ? {} : { recordStep: capturedPort(portDescriptors, 'recordStep') }),
354
355
  });
355
356
  const playerSessions = hasPlayerSessions
356
357
  ? capturedSessionStore(sessionDescriptors)
@@ -1102,6 +1103,20 @@ function unresolvedSemanticEvidence(reason, evidence, cause = assertPlaybookFail
1102
1103
  * repository fact is inferred from that prose or from the semantic candidate.
1103
1104
  */
1104
1105
  export function reconcilePlaybookSemanticEvidence(input) {
1106
+ let report;
1107
+ try {
1108
+ report = snapshotJsonValue(input.semanticCandidate, 'semantic candidate');
1109
+ }
1110
+ catch (error) {
1111
+ semanticCandidateStructureError(`must be detached plain JSON: ${error instanceof Error ? error.message : String(error)}`);
1112
+ }
1113
+ if (isRecord(report) && Object.keys(report).length === 1 &&
1114
+ typeof report.blocked === 'string' && report.blocked.trim().length > 0) {
1115
+ return unresolvedSemanticEvidence('no-matching-outcome', retainedSemanticEvidence(report, input.finalText), assertPlaybookFailureCause({
1116
+ code: 'runtime-defect',
1117
+ evidence: { reason: `No declared outcome matches: ${report.blocked}` },
1118
+ }));
1119
+ }
1105
1120
  const { candidate, outcome } = snapshotSemanticCandidate(input.semanticCandidate, input.outcomes);
1106
1121
  const evidence = retainedSemanticEvidence(candidate, input.finalText);
1107
1122
  const finalText = typeof input.finalText === 'string' ? input.finalText.trim() : undefined;
@@ -1256,6 +1271,7 @@ function effectBoundary(value, index) {
1256
1271
  'baseline',
1257
1272
  'after',
1258
1273
  'physicalReceipt',
1274
+ 'restored',
1259
1275
  'finalText',
1260
1276
  'semanticCandidate',
1261
1277
  'initialSemanticCandidate',
@@ -1315,6 +1331,15 @@ function effectBoundary(value, index) {
1315
1331
  if (own(value, 'finalText') && typeof value.finalText !== 'string') {
1316
1332
  throw new TypeError(`${path}.finalText must be a string`);
1317
1333
  }
1334
+ if (own(value, 'restored')) {
1335
+ const restored = effectObservation(value.restored, `${path}.restored`);
1336
+ if (!jsonValuesEqual(restored, baseline) ||
1337
+ !isRecord(value.physicalReceipt) || after?.head !== baseline.head ||
1338
+ value.dispositions.some((item) => item !== 'unchanged') ||
1339
+ value.cohortId !== undefined || value.logicalOperationId !== undefined) {
1340
+ throw new TypeError(`${path}.restored requires a standalone read-only call restored exactly without commit movement`);
1341
+ }
1342
+ }
1318
1343
  if (own(value, 'initialSemanticCandidate')) {
1319
1344
  if (!own(value, 'semanticCandidate') ||
1320
1345
  !isRecord(value.correctionBudget) ||
@@ -1578,6 +1603,7 @@ export function isPlaybookEffectLedgerMonotonicExtension(baselineValue, currentV
1578
1603
  const boundaryEvidenceKeys = [
1579
1604
  'after',
1580
1605
  'physicalReceipt',
1606
+ 'restored',
1581
1607
  'finalText',
1582
1608
  'initialSemanticCandidate',
1583
1609
  ];
@@ -1698,6 +1724,7 @@ export function assertPlaybookRuntimeSnapshot(value, expectedPlaybookId, options
1698
1724
  'retainedEffectSourceSessionId',
1699
1725
  'retainedEffectReconciliation',
1700
1726
  'failedEffectAttempt',
1727
+ 'recoveryCheckpoint',
1701
1728
  'suspendedCall',
1702
1729
  ], 'runtime snapshot');
1703
1730
  let suspendedCall;
@@ -1862,6 +1889,27 @@ export function assertPlaybookRuntimeSnapshot(value, expectedPlaybookId, options
1862
1889
  }
1863
1890
  failedEffectAttempt = Object.freeze({ boundaryPrefix, attemptId });
1864
1891
  }
1892
+ let recoveryCheckpoint;
1893
+ if (own(snapshot, 'recoveryCheckpoint')) {
1894
+ const checkpoint = snapshot.recoveryCheckpoint;
1895
+ if ((state.stateId !== 'failed' && pendingBossQuestions.length === 0) || !isRecord(checkpoint)) {
1896
+ throw new TypeError('runtime recoveryCheckpoint requires an interrupted state and an object');
1897
+ }
1898
+ rejectUnknownKeys(checkpoint, ['stateId', 'prompt', 'machine', 'boundaryPrefix', 'result', 'id', 'delivered'], 'runtime recoveryCheckpoint');
1899
+ const stateId = requireNonEmptyString(checkpoint.stateId, 'recoveryCheckpoint.stateId');
1900
+ const prompt = requireNonEmptyString(checkpoint.prompt, 'recoveryCheckpoint.prompt');
1901
+ const boundaryPrefix = checkpoint.boundaryPrefix;
1902
+ if (!Number.isSafeInteger(boundaryPrefix) || typeof boundaryPrefix !== 'number' ||
1903
+ boundaryPrefix < 0 || boundaryPrefix > effectLedger.boundaries.length ||
1904
+ !isRecord(checkpoint.machine) || checkpoint.machine.status !== 'active' ||
1905
+ checkpoint.machine.value !== stateId) {
1906
+ throw new TypeError('runtime recoveryCheckpoint has an invalid machine or boundary prefix');
1907
+ }
1908
+ if (own(checkpoint, 'delivered') && checkpoint.delivered !== true) {
1909
+ throw new TypeError('recoveryCheckpoint.delivered must be true');
1910
+ }
1911
+ recoveryCheckpoint = Object.freeze({ stateId, prompt, boundaryPrefix, machine: snapshotJsonValue(checkpoint.machine), ...(own(checkpoint, 'id') ? { id: effectUuid(checkpoint.id, 'recoveryCheckpoint.id') } : {}), ...(own(checkpoint, 'result') ? { result: snapshotJsonValue(checkpoint.result) } : {}), ...(checkpoint.delivered === true ? { delivered: true } : {}) });
1912
+ }
1865
1913
  const fields = {
1866
1914
  playbookId,
1867
1915
  machine,
@@ -1879,6 +1927,7 @@ export function assertPlaybookRuntimeSnapshot(value, expectedPlaybookId, options
1879
1927
  ...(failedEffectAttempt === undefined
1880
1928
  ? {}
1881
1929
  : { failedEffectAttempt }),
1930
+ ...(recoveryCheckpoint === undefined ? {} : { recoveryCheckpoint }),
1882
1931
  };
1883
1932
  return Object.freeze({
1884
1933
  schemaVersion: 4,
@@ -2718,6 +2767,9 @@ export function createNestedPlaybookBridge(options) {
2718
2767
  actorLogic,
2719
2768
  getPendingCall: () => pendingIdentity(current),
2720
2769
  getSuspendedCall: () => suspendedIdentity(current),
2770
+ checkpointCall: (child) => current && current.callId === child.callId && current.input.playbookId === child.playbookId
2771
+ ? { ...child, stateId: current.input.stateId, text: current.input.text, ...(current.turnId === undefined ? {} : { turnId: current.turnId }) }
2772
+ : undefined,
2721
2773
  prepareRestore(call) {
2722
2774
  // Capture the complete host-owned descriptor before observing or
2723
2775
  // mutating bridge state, so a rejected preparation cannot leave state.
@@ -598,6 +598,7 @@ export function snapshotPlaybookSession(
598
598
  callPlaybook: capturedPort(portDescriptors, 'callPlaybook'),
599
599
  emitStatus: capturedPort(portDescriptors, 'emitStatus'),
600
600
  emitTelemetry: capturedPort(portDescriptors, 'emitTelemetry'),
601
+ ...(portDescriptors.recordStep === undefined ? {} : { recordStep: capturedPort(portDescriptors, 'recordStep') }),
601
602
  });
602
603
  const playerSessions = hasPlayerSessions
603
604
  ? capturedSessionStore(sessionDescriptors)
@@ -1364,12 +1365,11 @@ export type PlaybookReconciledSemanticOutput = Readonly<
1364
1365
  /** Retained presentation and semantic evidence, kept separate from output. */
1365
1366
  export interface PlaybookRetainedSemanticEvidence {
1366
1367
  readonly finalText?: string;
1367
- readonly semanticCandidate: Readonly<Record<string, string>> & {
1368
- readonly guard: string;
1369
- };
1368
+ readonly semanticCandidate: Readonly<Record<string, string>>;
1370
1369
  }
1371
1370
 
1372
1371
  export type PlaybookSemanticReconciliationReason =
1372
+ | 'no-matching-outcome'
1373
1373
  | 'missing-presentation-evidence'
1374
1374
  | 'missing-repository-receipt'
1375
1375
  | 'invalid-repository-receipt'
@@ -1468,7 +1468,7 @@ function snapshotSemanticCandidate(
1468
1468
  }
1469
1469
 
1470
1470
  function retainedSemanticEvidence(
1471
- candidate: Readonly<Record<string, string>> & { readonly guard: string },
1471
+ candidate: Readonly<Record<string, string>>,
1472
1472
  finalText: unknown,
1473
1473
  ): PlaybookRetainedSemanticEvidence {
1474
1474
  return Object.freeze({
@@ -1654,6 +1654,23 @@ function unresolvedSemanticEvidence(
1654
1654
  export function reconcilePlaybookSemanticEvidence(
1655
1655
  input: PlaybookSemanticEvidenceInput,
1656
1656
  ): PlaybookSemanticReconciliation {
1657
+ let report: JsonValue;
1658
+ try {
1659
+ report = snapshotJsonValue(input.semanticCandidate, 'semantic candidate');
1660
+ } catch (error) {
1661
+ semanticCandidateStructureError(`must be detached plain JSON: ${error instanceof Error ? error.message : String(error)}`);
1662
+ }
1663
+ if (isRecord(report) && Object.keys(report).length === 1 &&
1664
+ typeof report.blocked === 'string' && report.blocked.trim().length > 0) {
1665
+ return unresolvedSemanticEvidence(
1666
+ 'no-matching-outcome',
1667
+ retainedSemanticEvidence(report as Readonly<Record<string, string>>, input.finalText),
1668
+ assertPlaybookFailureCause({
1669
+ code: 'runtime-defect',
1670
+ evidence: { reason: `No declared outcome matches: ${report.blocked}` },
1671
+ }),
1672
+ );
1673
+ }
1657
1674
  const { candidate, outcome } = snapshotSemanticCandidate(
1658
1675
  input.semanticCandidate,
1659
1676
  input.outcomes,
@@ -1859,6 +1876,7 @@ function effectBoundary(value: unknown, index: number): PlaybookEffectBoundary {
1859
1876
  'baseline',
1860
1877
  'after',
1861
1878
  'physicalReceipt',
1879
+ 'restored',
1862
1880
  'finalText',
1863
1881
  'semanticCandidate',
1864
1882
  'initialSemanticCandidate',
@@ -1939,6 +1957,15 @@ function effectBoundary(value: unknown, index: number): PlaybookEffectBoundary {
1939
1957
  if (own(value, 'finalText') && typeof value.finalText !== 'string') {
1940
1958
  throw new TypeError(`${path}.finalText must be a string`);
1941
1959
  }
1960
+ if (own(value, 'restored')) {
1961
+ const restored = effectObservation(value.restored, `${path}.restored`);
1962
+ if (!jsonValuesEqual(restored as unknown as JsonValue, baseline as unknown as JsonValue) ||
1963
+ !isRecord(value.physicalReceipt) || after?.head !== baseline.head ||
1964
+ (value.dispositions as string[]).some((item) => item !== 'unchanged') ||
1965
+ value.cohortId !== undefined || value.logicalOperationId !== undefined) {
1966
+ throw new TypeError(`${path}.restored requires a standalone read-only call restored exactly without commit movement`);
1967
+ }
1968
+ }
1942
1969
  if (own(value, 'initialSemanticCandidate')) {
1943
1970
  if (
1944
1971
  !own(value, 'semanticCandidate') ||
@@ -2359,6 +2386,7 @@ export function isPlaybookEffectLedgerMonotonicExtension(
2359
2386
  const boundaryEvidenceKeys = [
2360
2387
  'after',
2361
2388
  'physicalReceipt',
2389
+ 'restored',
2362
2390
  'finalText',
2363
2391
  'initialSemanticCandidate',
2364
2392
  ] as const;
@@ -2567,6 +2595,7 @@ export function assertPlaybookRuntimeSnapshot(
2567
2595
  'retainedEffectSourceSessionId',
2568
2596
  'retainedEffectReconciliation',
2569
2597
  'failedEffectAttempt',
2598
+ 'recoveryCheckpoint',
2570
2599
  'suspendedCall',
2571
2600
  ],
2572
2601
  'runtime snapshot',
@@ -2854,6 +2883,27 @@ export function assertPlaybookRuntimeSnapshot(
2854
2883
  }
2855
2884
  failedEffectAttempt = Object.freeze({ boundaryPrefix, attemptId });
2856
2885
  }
2886
+ let recoveryCheckpoint: PlaybookRuntimeSnapshot['recoveryCheckpoint'];
2887
+ if (own(snapshot, 'recoveryCheckpoint')) {
2888
+ const checkpoint = snapshot.recoveryCheckpoint;
2889
+ if ((state.stateId !== 'failed' && pendingBossQuestions.length === 0) || !isRecord(checkpoint)) {
2890
+ throw new TypeError('runtime recoveryCheckpoint requires an interrupted state and an object');
2891
+ }
2892
+ rejectUnknownKeys(checkpoint, ['stateId', 'prompt', 'machine', 'boundaryPrefix', 'result', 'id', 'delivered'], 'runtime recoveryCheckpoint');
2893
+ const stateId = requireNonEmptyString(checkpoint.stateId, 'recoveryCheckpoint.stateId');
2894
+ const prompt = requireNonEmptyString(checkpoint.prompt, 'recoveryCheckpoint.prompt');
2895
+ const boundaryPrefix = checkpoint.boundaryPrefix;
2896
+ if (!Number.isSafeInteger(boundaryPrefix) || typeof boundaryPrefix !== 'number' ||
2897
+ boundaryPrefix < 0 || boundaryPrefix > effectLedger.boundaries.length ||
2898
+ !isRecord(checkpoint.machine) || checkpoint.machine.status !== 'active' ||
2899
+ checkpoint.machine.value !== stateId) {
2900
+ throw new TypeError('runtime recoveryCheckpoint has an invalid machine or boundary prefix');
2901
+ }
2902
+ if (own(checkpoint, 'delivered') && checkpoint.delivered !== true) {
2903
+ throw new TypeError('recoveryCheckpoint.delivered must be true');
2904
+ }
2905
+ recoveryCheckpoint = Object.freeze({stateId, prompt, boundaryPrefix, machine: snapshotJsonValue(checkpoint.machine), ...(own(checkpoint, 'id') ? { id: effectUuid(checkpoint.id, 'recoveryCheckpoint.id') } : {}), ...(own(checkpoint, 'result') ? { result: snapshotJsonValue(checkpoint.result) } : {}), ...(checkpoint.delivered === true ? { delivered: true as const } : {})});
2906
+ }
2857
2907
  const fields = {
2858
2908
  playbookId,
2859
2909
  machine,
@@ -2871,6 +2921,7 @@ export function assertPlaybookRuntimeSnapshot(
2871
2921
  ...(failedEffectAttempt === undefined
2872
2922
  ? {}
2873
2923
  : { failedEffectAttempt }),
2924
+ ...(recoveryCheckpoint === undefined ? {} : { recoveryCheckpoint }),
2874
2925
  };
2875
2926
  return Object.freeze({
2876
2927
  schemaVersion: 4,
@@ -3004,6 +3055,7 @@ export interface NestedPlaybookBridge<
3004
3055
  confirmRestore(): void;
3005
3056
  /** Complete durable identity; undefined until a normal or restored call suspends. */
3006
3057
  getSuspendedCall(): PlaybookSuspendedCall | undefined;
3058
+ checkpointCall(child: PlaybookPendingCall): PlaybookSuspendedCall | undefined;
3007
3059
  resume(input: {
3008
3060
  callId: string;
3009
3061
  result: PlaybookCallResult;
@@ -4102,6 +4154,9 @@ export function createNestedPlaybookBridge<
4102
4154
  actorLogic,
4103
4155
  getPendingCall: () => pendingIdentity(current),
4104
4156
  getSuspendedCall: () => suspendedIdentity(current),
4157
+ checkpointCall: (child) => current && current.callId === child.callId && current.input.playbookId === child.playbookId
4158
+ ? { ...child, stateId: current.input.stateId, text: current.input.text, ...(current.turnId === undefined ? {} : { turnId: current.turnId }) }
4159
+ : undefined,
4105
4160
  prepareRestore(call) {
4106
4161
  // Capture the complete host-owned descriptor before observing or
4107
4162
  // mutating bridge state, so a rejected preparation cannot leave state.