@sublang/playbook 10.0.0 → 11.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.
@@ -35,6 +35,10 @@ import {
35
35
  validateFrozenExecutionConfig,
36
36
  } from './run.js';
37
37
  import { prepareConfiguredRegistries } from './provision.js';
38
+ import {
39
+ createReplayRecordObserver,
40
+ replayIncompleteMessage,
41
+ } from './replay-observer.js';
38
42
  import {
39
43
  assertCaptainSessionExecutionCompatible,
40
44
  createCaptainSessionStore,
@@ -86,10 +90,7 @@ export function validateManagedInteractivePayload(value) {
86
90
  }
87
91
  assertUuid(value.sessionId, 'managed interactive session id');
88
92
  assertCanonicalAbsolutePath(value.cwd, 'managed interactive working directory');
89
- assertCanonicalAbsolutePath(
90
- value.sessionsDir,
91
- 'managed interactive sessions directory',
92
- );
93
+ assertAbsolutePath(value.sessionsDir, 'managed interactive sessions directory');
93
94
  if (typeof value.noProvision !== 'boolean') {
94
95
  throw new Error('managed interactive noProvision must be a boolean');
95
96
  }
@@ -317,11 +318,19 @@ export function createManagedInteractiveLifecycle(payloadValue, options = {}) {
317
318
  let activeTurn;
318
319
  let executionProjection;
319
320
  let freshLaunchRecord;
321
+ let replayChannel;
320
322
 
321
323
  const release = async () => {
322
324
  if (released || lease === undefined) return;
323
325
  const owned = lease;
324
- await owned.release();
326
+ let status;
327
+ try {
328
+ status = await owned.release();
329
+ } catch (error) {
330
+ await replayChannel?.reportIfIncomplete();
331
+ throw error;
332
+ }
333
+ await replayChannel?.reportIfIncomplete(status);
325
334
  if (lease === owned) lease = undefined;
326
335
  released = true;
327
336
  };
@@ -369,6 +378,11 @@ export function createManagedInteractiveLifecycle(payloadValue, options = {}) {
369
378
  let host;
370
379
  try {
371
380
  lease = await store.acquire(payload.sessionId);
381
+ replayChannel = createManagedReplayChannel({
382
+ lease,
383
+ sessionId: payload.sessionId,
384
+ presentationGate: context.observers[0],
385
+ });
372
386
  const authoritative =
373
387
  typeof lease.recoverUnresolvedEffectAbandonment === 'function'
374
388
  ? await lease.recoverUnresolvedEffectAbandonment()
@@ -435,7 +449,12 @@ export function createManagedInteractiveLifecycle(payloadValue, options = {}) {
435
449
  cwd: payload.cwd,
436
450
  sessionLease: lease,
437
451
  loadModule,
438
- observers: context.observers,
452
+ observers: [
453
+ ...context.observers,
454
+ ...(replayChannel === undefined
455
+ ? []
456
+ : [replayChannel.observer]),
457
+ ],
439
458
  ...(options.adapterImports
440
459
  ? { adapterImports: options.adapterImports }
441
460
  : {}),
@@ -491,6 +510,7 @@ export function createManagedInteractiveLifecycle(payloadValue, options = {}) {
491
510
  retainedGenerations,
492
511
  reconcileRepositoryEffects: created.reconcileRepositoryEffects,
493
512
  });
513
+ await replayChannel?.reportIfIncomplete();
494
514
  initialized = true;
495
515
  return {
496
516
  abortActiveTurn: (...args) => host.abortActiveTurn(...args),
@@ -600,6 +620,7 @@ export function createManagedInteractiveLifecycle(payloadValue, options = {}) {
600
620
  unresolvedEffects: settlement.unresolvedEffects,
601
621
  retentionUpdates: settlement.retentionUpdates,
602
622
  });
623
+ await replayChannel?.reportIfIncomplete();
603
624
  activeTurn = undefined;
604
625
  },
605
626
 
@@ -631,6 +652,31 @@ export function createManagedInteractiveLifecycle(payloadValue, options = {}) {
631
652
  });
632
653
  }
633
654
 
655
+ function createManagedReplayChannel({
656
+ lease,
657
+ sessionId,
658
+ presentationGate,
659
+ }) {
660
+ if (
661
+ typeof lease?.append !== 'function' ||
662
+ typeof lease?.streamStatus !== 'function'
663
+ ) {
664
+ return undefined;
665
+ }
666
+ return createReplayRecordObserver({
667
+ lease,
668
+ async onIncomplete() {
669
+ if (typeof presentationGate?.onRecord !== 'function') return;
670
+ await presentationGate.onRecord({
671
+ type: 'captain_status',
672
+ turnId: null,
673
+ timestamp: Date.now(),
674
+ message: replayIncompleteMessage(sessionId),
675
+ });
676
+ },
677
+ });
678
+ }
679
+
634
680
  async function writeManagedInteractiveReadinessClaim({
635
681
  workDir,
636
682
  sessionId,
@@ -912,6 +958,12 @@ function assertCanonicalAbsolutePath(value, path) {
912
958
  }
913
959
  }
914
960
 
961
+ function assertAbsolutePath(value, path) {
962
+ if (typeof value !== 'string' || !isAbsolute(value)) {
963
+ throw new Error(`${path} must be an absolute path`);
964
+ }
965
+ }
966
+
915
967
  function assertManagedDescriptorPath(path) {
916
968
  if (basename(path) !== MANAGED_INTERACTIVE_PAYLOAD_FILE) {
917
969
  throw new Error(