@typeonce/effect-machine 0.11.0 → 0.13.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 (83) hide show
  1. package/README.md +89 -13
  2. package/dist/Machine.d.ts +396 -127
  3. package/dist/Machine.d.ts.map +1 -1
  4. package/dist/Machine.js +21 -19
  5. package/dist/Machine.js.map +1 -1
  6. package/dist/internal/machine/atom.d.ts +1 -0
  7. package/dist/internal/machine/atom.d.ts.map +1 -1
  8. package/dist/internal/machine/atom.js +23 -0
  9. package/dist/internal/machine/atom.js.map +1 -1
  10. package/dist/internal/machine/cluster.js +2 -2
  11. package/dist/internal/machine/cluster.js.map +1 -1
  12. package/dist/internal/machine/commandRuntime.d.ts.map +1 -1
  13. package/dist/internal/machine/commandRuntime.js +2 -5
  14. package/dist/internal/machine/commandRuntime.js.map +1 -1
  15. package/dist/internal/machine/configuration.d.ts +12 -7
  16. package/dist/internal/machine/configuration.d.ts.map +1 -1
  17. package/dist/internal/machine/configuration.js +9 -5
  18. package/dist/internal/machine/configuration.js.map +1 -1
  19. package/dist/internal/machine/executionPlan.d.ts +4 -3
  20. package/dist/internal/machine/executionPlan.d.ts.map +1 -1
  21. package/dist/internal/machine/executionPlan.js +68 -43
  22. package/dist/internal/machine/executionPlan.js.map +1 -1
  23. package/dist/internal/machine/initialization.d.ts +5 -0
  24. package/dist/internal/machine/initialization.d.ts.map +1 -0
  25. package/dist/internal/machine/initialization.js +61 -0
  26. package/dist/internal/machine/initialization.js.map +1 -0
  27. package/dist/internal/machine/inspectionRuntime.d.ts +25 -0
  28. package/dist/internal/machine/inspectionRuntime.d.ts.map +1 -0
  29. package/dist/internal/machine/inspectionRuntime.js +79 -0
  30. package/dist/internal/machine/inspectionRuntime.js.map +1 -0
  31. package/dist/internal/machine/invocation.d.ts +2 -1
  32. package/dist/internal/machine/invocation.d.ts.map +1 -1
  33. package/dist/internal/machine/invocation.js +18 -11
  34. package/dist/internal/machine/invocation.js.map +1 -1
  35. package/dist/internal/machine/machine.d.ts +1 -2
  36. package/dist/internal/machine/machine.d.ts.map +1 -1
  37. package/dist/internal/machine/machine.js +21 -6
  38. package/dist/internal/machine/machine.js.map +1 -1
  39. package/dist/internal/machine/planner.d.ts +17 -2
  40. package/dist/internal/machine/planner.d.ts.map +1 -1
  41. package/dist/internal/machine/planner.js +137 -64
  42. package/dist/internal/machine/planner.js.map +1 -1
  43. package/dist/internal/machine/process.d.ts.map +1 -1
  44. package/dist/internal/machine/process.js +7 -5
  45. package/dist/internal/machine/process.js.map +1 -1
  46. package/dist/internal/machine/runtime.d.ts +29 -5
  47. package/dist/internal/machine/runtime.d.ts.map +1 -1
  48. package/dist/internal/machine/runtime.js +440 -64
  49. package/dist/internal/machine/runtime.js.map +1 -1
  50. package/dist/internal/machine/symbols.d.ts +2 -0
  51. package/dist/internal/machine/symbols.d.ts.map +1 -1
  52. package/dist/internal/machine/symbols.js +2 -0
  53. package/dist/internal/machine/symbols.js.map +1 -1
  54. package/dist/internal/machine/topology.d.ts +13 -0
  55. package/dist/internal/machine/topology.d.ts.map +1 -1
  56. package/dist/internal/machine/topology.js +9 -0
  57. package/dist/internal/machine/topology.js.map +1 -1
  58. package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -1
  59. package/dist/internal/testing/machine/finiteModel.js +3 -3
  60. package/dist/internal/testing/machine/finiteModel.js.map +1 -1
  61. package/dist/unstable/reactivity/AtomMachine.d.ts +9 -0
  62. package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
  63. package/dist/unstable/reactivity/AtomMachine.js +9 -0
  64. package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
  65. package/docs/agent-guide.md +130 -20
  66. package/package.json +5 -5
  67. package/src/Machine.ts +548 -217
  68. package/src/internal/machine/atom.ts +26 -0
  69. package/src/internal/machine/cluster.ts +2 -2
  70. package/src/internal/machine/commandRuntime.ts +2 -7
  71. package/src/internal/machine/configuration.ts +38 -13
  72. package/src/internal/machine/executionPlan.ts +85 -52
  73. package/src/internal/machine/initialization.ts +73 -0
  74. package/src/internal/machine/inspectionRuntime.ts +102 -0
  75. package/src/internal/machine/invocation.ts +38 -13
  76. package/src/internal/machine/machine.ts +31 -6
  77. package/src/internal/machine/planner.ts +166 -66
  78. package/src/internal/machine/process.ts +7 -5
  79. package/src/internal/machine/runtime.ts +663 -91
  80. package/src/internal/machine/symbols.ts +3 -0
  81. package/src/internal/machine/topology.ts +27 -0
  82. package/src/internal/testing/machine/finiteModel.ts +8 -7
  83. package/src/unstable/reactivity/AtomMachine.ts +12 -0
@@ -18,8 +18,9 @@ import * as Scope from "effect/Scope"
18
18
  import * as Stream from "effect/Stream"
19
19
  import * as SynchronizedRef from "effect/SynchronizedRef"
20
20
  import type * as Take from "effect/Take"
21
- import type { ActorRef } from "../../Machine.js"
21
+ import type { Inspection, Machine as MachineDefinition, MachineTarget } from "../../Machine.js"
22
22
  import { ChildAlreadyExistsError, StoppedError } from "./errors.js"
23
+ import * as InspectionRuntime from "./inspectionRuntime.js"
23
24
 
24
25
  type ChildDescriptor = {
25
26
  readonly id: string
@@ -69,7 +70,8 @@ const AcknowledgedMessageTypeId: unique symbol = Symbol("effect/Machine/Acknowle
69
70
  export interface AcknowledgedMessage<Event> {
70
71
  readonly [AcknowledgedMessageTypeId]: true
71
72
  readonly event: Event
72
- readonly deferred: Deferred.Deferred<AcknowledgedDelivery<unknown>, unknown>
73
+ readonly deferred?: Deferred.Deferred<AcknowledgedDelivery<unknown>, unknown>
74
+ readonly inspection?: InspectedDelivery
73
75
  }
74
76
 
75
77
  /** @internal */
@@ -90,7 +92,10 @@ const succeedAcknowledgedMessage = <State>(
90
92
  delivery: AcknowledgedDelivery<State>
91
93
  ): void => {
92
94
  if (message !== undefined && isAcknowledgedMessage(message)) {
93
- Deferred.doneUnsafe(message.deferred, Effect.succeed(delivery as AcknowledgedDelivery<unknown>))
95
+ if (message.deferred !== undefined) {
96
+ Deferred.doneUnsafe(message.deferred, Effect.succeed(delivery as AcknowledgedDelivery<unknown>))
97
+ }
98
+ message.inspection?.complete(delivery as AcknowledgedDelivery<unknown>)
94
99
  }
95
100
  }
96
101
 
@@ -99,16 +104,32 @@ const failAcknowledgedMessage = (
99
104
  cause: Cause.Cause<unknown>
100
105
  ): void => {
101
106
  if (message !== undefined && isAcknowledgedMessage(message)) {
102
- Deferred.doneUnsafe(message.deferred, Effect.failCause(cause))
107
+ if (message.deferred !== undefined) Deferred.doneUnsafe(message.deferred, Effect.failCause(cause))
103
108
  }
104
109
  }
105
110
 
106
111
  const stopAcknowledgedMessage = (message: ProcessMessage<unknown> | undefined): void => {
107
112
  if (message !== undefined && isAcknowledgedMessage(message)) {
108
- Deferred.doneUnsafe(message.deferred, Effect.fail(new StoppedError()))
113
+ if (message.deferred !== undefined) Deferred.doneUnsafe(message.deferred, Effect.fail(new StoppedError()))
109
114
  }
110
115
  }
111
116
 
117
+ interface InspectedDelivery {
118
+ readonly deliveryId: number
119
+ readonly macrostepId: number
120
+ readonly source: Inspection.Subject | undefined
121
+ readonly event: unknown
122
+ readonly causedBy: Inspection.Causation | undefined
123
+ readonly complete: (delivery: AcknowledgedDelivery<unknown>) => void
124
+ }
125
+
126
+ type InspectedOffer<Event> = (
127
+ event: Event,
128
+ source: Inspection.Subject | undefined,
129
+ causedBy: Inspection.Causation | undefined,
130
+ deferred?: Deferred.Deferred<AcknowledgedDelivery<unknown>, unknown>
131
+ ) => Effect.Effect<void, StoppedError>
132
+
112
133
  type ChildObservation = Option.Option<MachineRef<any, any, any, any>>
113
134
  type ChildObservationBatch = [ChildObservation, ...Array<ChildObservation>]
114
135
 
@@ -295,6 +316,10 @@ export interface MachineRef<out State, in Event, out Error = never, out Output =
295
316
  readonly join: Effect.Effect<Output, Error | StoppedError>
296
317
  readonly stop: Effect.Effect<void>
297
318
  readonly send: (event: Event) => Effect.Effect<void, StoppedError>
319
+ /** @internal */
320
+ readonly inspectionSubject?: Inspection.Subject
321
+ /** @internal */
322
+ readonly sendInspected?: ProcessAddress<Event>["sendInspected"]
298
323
  readonly [acknowledgedSend]?: (
299
324
  event: Event
300
325
  ) => Effect.Effect<AcknowledgedDelivery<State>, Error | StoppedError>
@@ -315,19 +340,36 @@ export interface PreparedProcess<
315
340
  readonly sessionId: string
316
341
  readonly changes: Stream.Stream<RuntimeSnapshot<State, Error, Output>, StartError>
317
342
  readonly emissions: Stream.Stream<Emitted>
343
+ readonly inspection: Stream.Stream<Inspection.Event>
318
344
  readonly start: Effect.Effect<MachineRef<State, Event, Error, Output, Emitted>, StartError, StartRequirements>
319
345
  }
320
346
 
321
347
  interface ProcessAddress<in Event> {
322
348
  readonly id: string
323
349
  readonly sessionId: string
350
+ readonly inspectionSubject?: Inspection.Subject
324
351
  readonly stop: Effect.Effect<void>
325
352
  readonly send: (event: Event) => Effect.Effect<void, StoppedError>
353
+ readonly sendInspected?: (
354
+ event: Event,
355
+ source: Inspection.Subject | undefined,
356
+ causedBy: Inspection.Causation | undefined
357
+ ) => Effect.Effect<void, StoppedError>
326
358
  }
327
359
 
328
- const isProcessAddress = (value: unknown): value is ActorRef<unknown> =>
360
+ const isMachineTarget = (value: unknown): value is MachineTarget<unknown> =>
329
361
  typeof value === "object" && value !== null && "send" in value && typeof value.send === "function"
330
362
 
363
+ const sendMachineTarget = (
364
+ target: MachineTarget<unknown>,
365
+ event: unknown,
366
+ source: Inspection.Subject | undefined,
367
+ causedBy: Inspection.Causation | undefined
368
+ ): Effect.Effect<void, StoppedError> =>
369
+ "inspectionSubject" in target && "sendInspected" in target && typeof target.sendInspected === "function"
370
+ ? target.sendInspected(event, source, causedBy)
371
+ : target.send(event)
372
+
331
373
  export interface ProcessScope<Event> {
332
374
  readonly self: ProcessAddress<Event>
333
375
  readonly parent: ProcessAddress<unknown> | undefined
@@ -335,12 +377,17 @@ export interface ProcessScope<Event> {
335
377
  readonly sendParent: (event: unknown) => Effect.Effect<void, StoppedError>
336
378
  readonly emit: (event: unknown) => Effect.Effect<void>
337
379
  readonly sendTo: {
338
- <TargetEvent>(target: ActorRef<TargetEvent>, event: TargetEvent): Effect.Effect<void, StoppedError>
380
+ <TargetEvent>(target: MachineTarget<TargetEvent>, event: TargetEvent): Effect.Effect<void, StoppedError>
339
381
  (child: ChildSelector, event: unknown): Effect.Effect<void, StoppedError>
340
382
  }
341
383
  readonly stopChild: (child: ChildSelector) => Effect.Effect<void>
342
384
  /** @internal */
343
385
  readonly failCause: (cause: Cause.Cause<unknown>) => Effect.Effect<void>
386
+ /** @internal */
387
+ readonly inspectInitial: (
388
+ initialEntryPaths: ReadonlyArray<string>,
389
+ microsteps?: ReadonlyArray<unknown>
390
+ ) => void
344
391
  }
345
392
 
346
393
  export interface ProcessContext<State, Event> extends ProcessScope<Event> {
@@ -367,7 +414,7 @@ export interface ProcessContext<State, Event> extends ProcessScope<Event> {
367
414
  *
368
415
  * Unlike `ProcessContext`, synchronous mailbox and state operations do not
369
416
  * introduce an Effect boundary. The compiled drain still returns an Effect so
370
- * actor commands, invokes, observation callbacks, interruption, and the Effect
417
+ * machine commands, invokes, observation callbacks, interruption, and the Effect
371
418
  * scheduler remain explicit at their actual boundaries.
372
419
  *
373
420
  * @internal
@@ -493,6 +540,11 @@ export interface ProcessLogic<
493
540
  > {
494
541
  /** @internal */
495
542
  readonly execution?: ProcessExecution<State, Event, Error, Requirements, Output, InitialError>
543
+ /** @internal */
544
+ readonly inspection?: {
545
+ readonly kind: Inspection.Subject["kind"]
546
+ readonly definition?: MachineDefinition.Any
547
+ }
496
548
  initial(scope: ProcessScope<Event>): Effect.Effect<State, InitialError, Requirements>
497
549
  run(context: ProcessContext<State, Event>): Effect.Effect<Output, Error, Requirements>
498
550
  }
@@ -613,6 +665,7 @@ export const watch = <State, Event, Error = never, Output = never>(
613
665
 
614
666
  interface ProcessRuntime {
615
667
  readonly nextSessionId: Effect.Effect<string>
668
+ inspection?: InspectionRuntime.Runtime
616
669
  }
617
670
 
618
671
  const makeProcessRuntime: Effect.Effect<ProcessRuntime> = Effect.sync(() => {
@@ -622,6 +675,85 @@ const makeProcessRuntime: Effect.Effect<ProcessRuntime> = Effect.sync(() => {
622
675
  }
623
676
  })
624
677
 
678
+ const inspectionSubject = (
679
+ logic: ProcessLogic<any, any, any, any, any, any>,
680
+ id: string,
681
+ sessionId: string
682
+ ): Inspection.Subject => ({
683
+ id,
684
+ sessionId,
685
+ kind: logic.inspection?.kind ?? "Logic"
686
+ })
687
+
688
+ const messageCausation = (
689
+ message: ProcessMessage<unknown> | undefined,
690
+ initializing: boolean
691
+ ): Inspection.Causation | undefined =>
692
+ initializing
693
+ ? { _tag: "Initialization" }
694
+ : message !== undefined && isAcknowledgedMessage(message) && message.inspection !== undefined
695
+ ? { _tag: "Macrostep", macrostepId: message.inspection.macrostepId }
696
+ : undefined
697
+
698
+ const makeInspectedMessage = <Event>(
699
+ inspection: InspectionRuntime.Runtime,
700
+ subject: Inspection.Subject,
701
+ event: Event,
702
+ source: Inspection.Subject | undefined,
703
+ causedBy: Inspection.Causation | undefined,
704
+ deferred?: Deferred.Deferred<AcknowledgedDelivery<unknown>, unknown>
705
+ ): AcknowledgedMessage<Event> => {
706
+ const deliveryId = inspection.nextDeliveryId()
707
+ const macrostepId = inspection.nextMacrostepId()
708
+ const inspected: InspectedDelivery = {
709
+ deliveryId,
710
+ macrostepId,
711
+ source,
712
+ event,
713
+ causedBy,
714
+ complete: (delivery) => {
715
+ const microsteps = InspectionRuntime.microsteps(delivery.plan)
716
+ inspection.publishUnsafe({
717
+ _tag: "EventProcessed",
718
+ subject,
719
+ macrostepId,
720
+ deliveryId,
721
+ source,
722
+ event,
723
+ before: { status: "active", state: delivery.before },
724
+ after: { status: "active", state: delivery.after },
725
+ handled: microsteps.some((microstep) => microstep.transitions.length > 0),
726
+ configurationChanged: microsteps.some((microstep) => microstep.changed),
727
+ microsteps
728
+ })
729
+ }
730
+ }
731
+ return {
732
+ [AcknowledgedMessageTypeId]: true,
733
+ event,
734
+ ...(deferred === undefined ? undefined : { deferred }),
735
+ inspection: inspected
736
+ }
737
+ }
738
+
739
+ const publishInspectedSent = (
740
+ inspection: InspectionRuntime.Runtime,
741
+ subject: Inspection.Subject,
742
+ message: ProcessMessage<unknown>
743
+ ): void => {
744
+ if (!isAcknowledgedMessage(message) || message.inspection === undefined) return
745
+ const delivery = message.inspection
746
+ inspection.publishUnsafe({
747
+ _tag: "EventSent",
748
+ subject,
749
+ deliveryId: delivery.deliveryId,
750
+ source: delivery.source,
751
+ target: InspectionRuntime.endpoint(subject),
752
+ event: delivery.event,
753
+ causedBy: delivery.causedBy
754
+ })
755
+ }
756
+
625
757
  interface StartInternalOptions {
626
758
  readonly detached?: boolean
627
759
  readonly id?: string
@@ -642,6 +774,14 @@ interface StartInternalOptions {
642
774
  readonly parent?: ProcessAddress<unknown>
643
775
  readonly runtime: ProcessRuntime
644
776
  readonly sendParent?: (event: unknown) => Effect.Effect<void, StoppedError>
777
+ readonly origin?: Inspection.Origin
778
+ readonly activity?: {
779
+ readonly id: string
780
+ readonly owner: Inspection.Subject
781
+ readonly ownerPath: string
782
+ readonly kind: Inspection.Activity["kind"]
783
+ }
784
+ readonly inspectionRoot?: boolean
645
785
  }
646
786
 
647
787
  /** @internal */
@@ -653,7 +793,8 @@ export interface OwnedChildSpawnOptions {
653
793
  readonly descriptor?: ChildDescriptor
654
794
  readonly onOutcome: (
655
795
  isCurrent: () => boolean,
656
- outcome: RuntimeOutcome<any, any, any>
796
+ outcome: RuntimeOutcome<any, any, any>,
797
+ activitySessionId: string | undefined
657
798
  ) => Effect.Effect<void>
658
799
  readonly onSnapshot?: (
659
800
  isCurrent: () => boolean,
@@ -663,6 +804,7 @@ export interface OwnedChildSpawnOptions {
663
804
  isCurrent: () => boolean,
664
805
  event: unknown
665
806
  ) => Effect.Effect<void, StoppedError>
807
+ readonly activityKind?: Inspection.Activity["kind"]
666
808
  }
667
809
 
668
810
  /** @internal */
@@ -684,7 +826,12 @@ interface ChildRuntime {
684
826
  readonly changes: <ChildState, ChildEvent, ChildError, ChildOutput>(
685
827
  child: ChildSelector
686
828
  ) => Stream.Stream<Option.Option<MachineRef<ChildState, ChildEvent, ChildError, ChildOutput>>>
687
- readonly sendTo: (child: ChildSelector, event: unknown) => Effect.Effect<void, StoppedError>
829
+ readonly sendTo: (
830
+ child: ChildSelector,
831
+ event: unknown,
832
+ source?: Inspection.Subject,
833
+ causedBy?: Inspection.Causation
834
+ ) => Effect.Effect<void, StoppedError>
688
835
  readonly stop: (child: ChildSelector) => Effect.Effect<void>
689
836
  readonly owned: OwnedChildRuntime
690
837
  }
@@ -737,13 +884,15 @@ class OwnedChildRuntimeImpl implements OwnedChildRuntime {
737
884
  })
738
885
  const parent: ProcessAddress<unknown> = {
739
886
  ...this.self,
740
- send: (event) => options.sendParent(isCurrent, event)
887
+ send: (event) => options.sendParent(isCurrent, event),
888
+ sendInspected: (event, source, causedBy) =>
889
+ isCurrent() ? sendMachineTarget(this.self, event, source, causedBy) : Effect.void
741
890
  }
742
891
  const startOptions: StartInternalOptions = {
743
892
  detached: true,
744
893
  id: options.id,
745
894
  sendParent: (event) => options.sendParent(isCurrent, event),
746
- onOutcome: (outcome) => options.onOutcome(isCurrent, outcome),
895
+ onOutcome: (outcome) => options.onOutcome(isCurrent, outcome, startedChild?.sessionId),
747
896
  ...(options.onSnapshot === undefined
748
897
  ? undefined
749
898
  : { onSnapshot: (snapshot) => options.onSnapshot!(isCurrent, snapshot) }),
@@ -754,7 +903,19 @@ class OwnedChildRuntimeImpl implements OwnedChildRuntime {
754
903
  onStopSync: () => unregisterChild(this.registry, options.id, token),
755
904
  skipStoppedOutcome: true,
756
905
  parent,
757
- runtime: this.runtime
906
+ runtime: this.runtime,
907
+ origin: { _tag: "Invoke", ownerPath: options.path, invokeId: options.duplicateId },
908
+ ...(options.activityKind === undefined || this.runtime.inspection === undefined ||
909
+ this.self.inspectionSubject === undefined
910
+ ? undefined
911
+ : {
912
+ activity: {
913
+ id: options.duplicateId,
914
+ owner: this.self.inspectionSubject,
915
+ ownerPath: options.path,
916
+ kind: options.activityKind
917
+ }
918
+ })
758
919
  }
759
920
  const execution = logic.execution
760
921
  const synchronous = this.services !== undefined && options.onSnapshot === undefined &&
@@ -816,6 +977,8 @@ class OwnedChildRuntimeImpl implements OwnedChildRuntime {
816
977
 
817
978
  const noChildChanges = Stream.succeed(Option.none()).pipe(Stream.concat(Stream.never))
818
979
  const noParentSend = (_event: unknown): Effect.Effect<void, StoppedError> => Effect.void
980
+ const noInspectInitial = (_paths: ReadonlyArray<string>, _microsteps?: ReadonlyArray<unknown>): void => {}
981
+ const noCausation = (): Inspection.Causation | undefined => undefined
819
982
  const EmissionsClosed: unique symbol = Symbol("effect/Machine/EmissionsClosed")
820
983
 
821
984
  type LazyEmissions = PubSub.PubSub<unknown> | typeof EmissionsClosed | undefined
@@ -996,7 +1159,12 @@ const makeChildRuntimeSync = (
996
1159
  )
997
1160
  }
998
1161
 
999
- const sendTo = (child: ChildSelector, event: unknown): Effect.Effect<void, StoppedError> => {
1162
+ const sendTo = (
1163
+ child: ChildSelector,
1164
+ event: unknown,
1165
+ source?: Inspection.Subject,
1166
+ causedBy?: Inspection.Causation
1167
+ ): Effect.Effect<void, StoppedError> => {
1000
1168
  const id = typeof child === "string" ? child : child.id
1001
1169
  return Effect.suspend(() => {
1002
1170
  if (registry.closed) {
@@ -1004,7 +1172,7 @@ const makeChildRuntimeSync = (
1004
1172
  }
1005
1173
  const entry = registry.children.get(id)
1006
1174
  return entry !== undefined && matchesChild(entry, child)
1007
- ? entry.ref.send(event)
1175
+ ? sendMachineTarget(entry.ref, event, source, causedBy)
1008
1176
  : Effect.void
1009
1177
  })
1010
1178
  }
@@ -1100,7 +1268,8 @@ const makeChildRuntimeSync = (
1100
1268
  ),
1101
1269
  onStop: unregister(key, token),
1102
1270
  parent: self,
1103
- runtime
1271
+ runtime,
1272
+ origin: { _tag: "Spawn", address: spawnOptions?.id }
1104
1273
  }).pipe(
1105
1274
  Effect.onExit((exit) =>
1106
1275
  Exit.isFailure(exit)
@@ -1172,6 +1341,13 @@ const startGenericInternal: <
1172
1341
 
1173
1342
  const sessionId = options.sessionId ?? (yield* runtime.nextSessionId)
1174
1343
  const id = requestedId ?? sessionId
1344
+ const inspector = runtime.inspection
1345
+ const subject = inspector === undefined ? undefined : inspectionSubject(logic, id, sessionId)
1346
+ const activity: Inspection.Activity | undefined = inspector === undefined || options.activity === undefined
1347
+ ? undefined
1348
+ : { ...options.activity, sessionId }
1349
+ let initialEntryPaths: ReadonlyArray<string> | undefined
1350
+ let initialMicrosteps: ReadonlyArray<unknown> | undefined
1175
1351
  const queue = yield* Queue.unbounded<ProcessMessage<Event>>()
1176
1352
  const emissions = options.emissions ?? makeEmissionRuntime()
1177
1353
  const termination = yield* Deferred.make<ProcessTermination>()
@@ -1180,6 +1356,26 @@ const startGenericInternal: <
1180
1356
  let initializing = true
1181
1357
  let inFlightMessage: ProcessMessage<Event> | undefined
1182
1358
  const requestStop = Deferred.succeed(termination, { _tag: "Stopped" }).pipe(Effect.asVoid)
1359
+ const offerDirect = (message: ProcessMessage<Event>): Effect.Effect<void, StoppedError> =>
1360
+ Queue.offer(queue, message).pipe(
1361
+ Effect.flatMap((accepted) => accepted ? Effect.void : Effect.fail(new StoppedError()))
1362
+ )
1363
+ const offerInspected: InspectedOffer<Event> | undefined = inspector === undefined ? undefined : (
1364
+ event,
1365
+ source,
1366
+ causedBy,
1367
+ deferred?: Deferred.Deferred<AcknowledgedDelivery<unknown>, unknown>
1368
+ ) =>
1369
+ Effect.suspend(() => {
1370
+ const message: ProcessMessage<Event> = inspector.isActive()
1371
+ ? makeInspectedMessage(inspector, subject!, event, source, causedBy, deferred)
1372
+ : deferred === undefined
1373
+ ? event
1374
+ : { [AcknowledgedMessageTypeId]: true as const, event, deferred }
1375
+ return offerDirect(message).pipe(
1376
+ Effect.tap(() => Effect.sync(() => publishInspectedSent(inspector, subject!, message)))
1377
+ )
1378
+ })
1183
1379
  const self: ProcessAddress<Event> = {
1184
1380
  id,
1185
1381
  sessionId,
@@ -1193,10 +1389,12 @@ const startGenericInternal: <
1193
1389
  ? requestStop
1194
1390
  : requestStop.pipe(Effect.andThen(Effect.never))
1195
1391
  ),
1196
- send: (event: Event) =>
1197
- Queue.offer(queue, event).pipe(
1198
- Effect.flatMap((accepted) => accepted ? Effect.void : Effect.fail(new StoppedError()))
1199
- )
1392
+ send: inspector === undefined
1393
+ ? (event) => offerDirect(event)
1394
+ : (event) => offerInspected!(event, undefined, undefined),
1395
+ ...(inspector === undefined
1396
+ ? undefined
1397
+ : { inspectionSubject: subject!, sendInspected: offerInspected! })
1200
1398
  }
1201
1399
  const sendAcknowledged:
1202
1400
  | ((event: Event) => Effect.Effect<AcknowledgedDelivery<State>, Error | StoppedError>)
@@ -1205,19 +1403,12 @@ const startGenericInternal: <
1205
1403
  : (event) =>
1206
1404
  Effect.uninterruptibleMask((restore) =>
1207
1405
  Deferred.make<AcknowledgedDelivery<unknown>, unknown>().pipe(
1208
- Effect.flatMap((deferred) =>
1209
- Queue.offer(queue, {
1210
- [AcknowledgedMessageTypeId]: true as const,
1211
- event,
1212
- deferred
1213
- }).pipe(
1214
- Effect.flatMap((accepted) =>
1215
- accepted
1216
- ? restore(Deferred.await(deferred))
1217
- : Effect.fail(new StoppedError())
1218
- )
1219
- )
1220
- ),
1406
+ Effect.flatMap((deferred) => {
1407
+ const offered = inspector === undefined
1408
+ ? offerDirect({ [AcknowledgedMessageTypeId]: true as const, event, deferred })
1409
+ : offerInspected!(event, undefined, undefined, deferred)
1410
+ return offered.pipe(Effect.andThen(restore(Deferred.await(deferred))))
1411
+ }),
1221
1412
  Effect.map((delivery) => delivery as AcknowledgedDelivery<State>)
1222
1413
  )
1223
1414
  ) as Effect.Effect<AcknowledgedDelivery<State>, Error | StoppedError>
@@ -1242,29 +1433,87 @@ const startGenericInternal: <
1242
1433
  stop: stopChild
1243
1434
  } = yield* makeChildRuntime(self, runtime))
1244
1435
  }
1245
- const cleanupStartupFailure = <A, E>(exit: Exit.Exit<A, E>): Effect.Effect<void> =>
1246
- Exit.isFailure(exit)
1247
- ? closeChildren(exit).pipe(Effect.andThen(emissions.close()))
1248
- : Effect.void
1436
+ const cleanupStartupFailure = <A, E>(exit: Exit.Exit<A, E>): Effect.Effect<void> => {
1437
+ if (Exit.isSuccess(exit)) return Effect.void
1438
+ if (inspector !== undefined) {
1439
+ inspector.publishUnsafe(
1440
+ activity === undefined
1441
+ ? { _tag: "StartFailed", subject: subject!, cause: exit.cause }
1442
+ : { _tag: "ActivityStopped", subject: activity.owner, activity, exit }
1443
+ )
1444
+ }
1445
+ return closeChildren(exit).pipe(
1446
+ Effect.andThen(emissions.close()),
1447
+ Effect.andThen(options.inspectionRoot === true && inspector !== undefined ? inspector.close : Effect.void)
1448
+ )
1449
+ }
1249
1450
  const cleanup = onStopSync === undefined ? onStop ?? Effect.void : Effect.sync(onStopSync)
1250
- const sendParent = overrideSendParent ?? (parent === undefined ? noParentSend : parent.send)
1451
+ const currentCausation = inspector === undefined
1452
+ ? noCausation
1453
+ : (): Inspection.Causation | undefined => messageCausation(inFlightMessage, initializing)
1454
+ const sendParent = overrideSendParent ?? (parent === undefined
1455
+ ? noParentSend
1456
+ : inspector === undefined
1457
+ ? parent.send
1458
+ : (event) => sendMachineTarget(parent, event, subject, currentCausation()))
1459
+ const emit = inspector === undefined
1460
+ ? emissions.emit
1461
+ : (event: unknown) =>
1462
+ emissions.emit(event).pipe(
1463
+ Effect.tap(() =>
1464
+ Effect.sync(() =>
1465
+ inspector.publishUnsafe({
1466
+ _tag: "Emitted",
1467
+ subject: subject!,
1468
+ emission: event,
1469
+ causedBy: currentCausation()
1470
+ })
1471
+ )
1472
+ )
1473
+ )
1474
+ const sendToTarget: ProcessScope<Event>["sendTo"] = inspector === undefined
1475
+ ? ((target: unknown, event: unknown) =>
1476
+ isMachineTarget(target) ? target.send(event) : sendTo(target as ChildSelector, event)) as ProcessScope<
1477
+ Event
1478
+ >["sendTo"]
1479
+ : ((target: unknown, event: unknown) =>
1480
+ isMachineTarget(target)
1481
+ ? sendMachineTarget(target, event, subject, currentCausation())
1482
+ : sendTo(target as ChildSelector, event, subject, currentCausation())) as ProcessScope<Event>["sendTo"]
1251
1483
 
1252
1484
  const scope: ProcessScope<Event> = {
1253
1485
  self,
1254
1486
  parent,
1255
1487
  spawn,
1256
1488
  sendParent,
1257
- emit: emissions.emit,
1258
- sendTo: ((target: unknown, event: unknown) =>
1259
- isProcessAddress(target) ? target.send(event) : sendTo(target as ChildSelector, event)) as ProcessScope<
1260
- Event
1261
- >["sendTo"],
1489
+ emit,
1490
+ sendTo: sendToTarget,
1262
1491
  stopChild,
1263
1492
  failCause: (cause) =>
1264
1493
  Deferred.succeed(termination, {
1265
1494
  _tag: "Failure",
1266
1495
  cause: cause as Cause.Cause<Error>
1267
- }).pipe(Effect.asVoid)
1496
+ }).pipe(Effect.asVoid),
1497
+ inspectInitial: inspector === undefined
1498
+ ? noInspectInitial
1499
+ : (paths, microsteps = []) => {
1500
+ initialEntryPaths = paths
1501
+ initialMicrosteps = microsteps
1502
+ }
1503
+ }
1504
+
1505
+ if (inspector !== undefined) {
1506
+ inspector.publishUnsafe(
1507
+ activity === undefined
1508
+ ? {
1509
+ _tag: "Created",
1510
+ subject: subject!,
1511
+ parent: parent?.inspectionSubject,
1512
+ origin: options.origin ?? { _tag: "Root" },
1513
+ definition: logic.inspection?.definition
1514
+ }
1515
+ : { _tag: "ActivityStarted", subject: activity.owner, activity }
1516
+ )
1268
1517
  }
1269
1518
 
1270
1519
  const initial = yield* logic.initial(scope).pipe(
@@ -1282,6 +1531,15 @@ const startGenericInternal: <
1282
1531
  state: initial
1283
1532
  }
1284
1533
  })
1534
+ if (activity === undefined) {
1535
+ inspector?.publishUnsafe({
1536
+ _tag: "Initialized",
1537
+ subject: subject!,
1538
+ snapshot: { status: "active", state: initial },
1539
+ initialEntryPaths: initialEntryPaths ?? [],
1540
+ microsteps: InspectionRuntime.microsteps({ microsteps: initialMicrosteps ?? [] })
1541
+ })
1542
+ }
1285
1543
  const publishSnapshot: (
1286
1544
  snapshot: VersionedSnapshot<State, Error, Output>
1287
1545
  ) => Effect.Effect<VersionedSnapshot<State, Error, Output>> = onSnapshot === undefined
@@ -1296,9 +1554,7 @@ const startGenericInternal: <
1296
1554
  const runtimeSnapshot = snapshot.snapshot
1297
1555
  return runtimeSnapshot.status !== "active"
1298
1556
  ? publish
1299
- : publish.pipe(Effect.tap(() =>
1300
- notifyActiveSnapshot(onSnapshot, runtimeSnapshot)
1301
- ))
1557
+ : publish.pipe(Effect.tap(() => notifyActiveSnapshot(onSnapshot, runtimeSnapshot)))
1302
1558
  }
1303
1559
 
1304
1560
  const completeChanges = (
@@ -1402,7 +1658,7 @@ const startGenericInternal: <
1402
1658
  Effect.asVoid
1403
1659
  )
1404
1660
 
1405
- const setActiveState = (state: State) =>
1661
+ const setActiveStateDirect = (state: State) =>
1406
1662
  updateSnapshot((snapshot) =>
1407
1663
  Effect.succeed(
1408
1664
  snapshot.status === "active"
@@ -1414,6 +1670,40 @@ const startGenericInternal: <
1414
1670
  )
1415
1671
  ).pipe(Effect.asVoid)
1416
1672
 
1673
+ const setActiveState = inspector === undefined ?
1674
+ setActiveStateDirect :
1675
+ (state: State) =>
1676
+ SynchronizedRef.get(current).pipe(
1677
+ Effect.flatMap((before) =>
1678
+ updateSnapshot((snapshot) =>
1679
+ Effect.succeed(
1680
+ snapshot.status === "active"
1681
+ ? {
1682
+ status: "active",
1683
+ state
1684
+ }
1685
+ : undefined
1686
+ )
1687
+ ).pipe(
1688
+ Effect.tap((after) =>
1689
+ Effect.sync(() => {
1690
+ if (logic.inspection?.kind === "Machine" || after === undefined) return
1691
+ inspector.publishUnsafe({
1692
+ _tag: "StateChanged",
1693
+ subject: subject!,
1694
+ before: before.snapshot.state,
1695
+ after: state,
1696
+ causedByDeliveryId: inFlightMessage !== undefined && isAcknowledgedMessage(inFlightMessage)
1697
+ ? inFlightMessage.inspection?.deliveryId
1698
+ : undefined
1699
+ })
1700
+ })
1701
+ )
1702
+ )
1703
+ ),
1704
+ Effect.asVoid
1705
+ )
1706
+
1417
1707
  const terminalizeWith = (
1418
1708
  snapshot: RuntimeSnapshot<State, Error, Output>,
1419
1709
  exit: Exit.Exit<unknown, unknown>,
@@ -1426,6 +1716,22 @@ const startGenericInternal: <
1426
1716
  Effect.exit,
1427
1717
  Effect.asVoid
1428
1718
  )
1719
+ const closeEmissionsAndInspect = inspector === undefined
1720
+ ? emissions.close()
1721
+ : emissions.close().pipe(
1722
+ Effect.andThen(Effect.sync(() =>
1723
+ inspector.publishUnsafe(
1724
+ activity === undefined
1725
+ ? { _tag: "Terminated", subject: subject!, snapshot }
1726
+ : {
1727
+ _tag: "ActivityStopped",
1728
+ subject: activity.owner,
1729
+ activity,
1730
+ exit: snapshot.status === "stopped" ? Exit.interrupt() : exit
1731
+ }
1732
+ )
1733
+ ))
1734
+ )
1429
1735
  return Effect.uninterruptible(
1430
1736
  Effect.sync(() => {
1431
1737
  while (true) {
@@ -1437,7 +1743,7 @@ const startGenericInternal: <
1437
1743
  Effect.andThen(Queue.shutdown(queue)),
1438
1744
  Effect.andThen(closeChildren(exit)),
1439
1745
  Effect.andThen(setAndPublishSnapshot(snapshot)),
1440
- Effect.andThen(emissions.close()),
1746
+ Effect.andThen(closeEmissionsAndInspect),
1441
1747
  Effect.andThen(Effect.sync(() => {
1442
1748
  if (Exit.isFailure(exit)) {
1443
1749
  failAcknowledgedMessage(inFlightMessage, exit.cause)
@@ -1448,6 +1754,7 @@ const startGenericInternal: <
1448
1754
  })),
1449
1755
  Effect.andThen(notifyOutcome),
1450
1756
  Effect.andThen(cleanup),
1757
+ Effect.andThen(options.inspectionRoot === true && inspector !== undefined ? inspector.close : Effect.void),
1451
1758
  Effect.andThen(completeDone)
1452
1759
  )
1453
1760
  )
@@ -1530,25 +1837,79 @@ const startGenericInternal: <
1530
1837
  inFlightMessage = undefined
1531
1838
  }
1532
1839
  }
1840
+ const receive = inspector === undefined || logic.execution?._tag === "Compiled"
1841
+ ? Queue.take(queue).pipe(Effect.map(messageEvent))
1842
+ : Queue.take(queue).pipe(
1843
+ Effect.tap((message) =>
1844
+ Effect.sync(() => {
1845
+ inFlightMessage = message
1846
+ })
1847
+ ),
1848
+ Effect.map(messageEvent)
1849
+ )
1850
+ const poll = inspector === undefined || logic.execution?._tag === "Compiled"
1851
+ ? Queue.poll(queue).pipe(Effect.map(Option.map(messageEvent)))
1852
+ : Queue.poll(queue).pipe(
1853
+ Effect.tap((message) =>
1854
+ Effect.sync(() => {
1855
+ if (Option.isSome(message)) inFlightMessage = message.value
1856
+ })
1857
+ ),
1858
+ Effect.map(Option.map(messageEvent))
1859
+ )
1860
+ const updateStateDirect = <E2, R2>(f: (state: State) => Effect.Effect<State, E2, R2>) =>
1861
+ updateSnapshot((snapshot) =>
1862
+ snapshot.status === "active"
1863
+ ? f(snapshot.state).pipe(
1864
+ Effect.map((state) => ({
1865
+ status: "active" as const,
1866
+ state
1867
+ }))
1868
+ )
1869
+ : Effect.succeed(undefined)
1870
+ ).pipe(Effect.asVoid)
1871
+ const updateState: ProcessContext<State, Event>["updateState"] = inspector === undefined
1872
+ ? updateStateDirect
1873
+ : (f) =>
1874
+ SynchronizedRef.get(current).pipe(
1875
+ Effect.flatMap((before) =>
1876
+ updateSnapshot((snapshot) =>
1877
+ snapshot.status === "active"
1878
+ ? f(snapshot.state).pipe(
1879
+ Effect.map((state) => ({
1880
+ status: "active" as const,
1881
+ state
1882
+ }))
1883
+ )
1884
+ : Effect.succeed(undefined)
1885
+ ).pipe(
1886
+ Effect.tap((after) =>
1887
+ Effect.sync(() => {
1888
+ if (logic.inspection?.kind === "Machine" || after?.status !== "active") return
1889
+ inspector.publishUnsafe({
1890
+ _tag: "StateChanged",
1891
+ subject: subject!,
1892
+ before: before.snapshot.state,
1893
+ after: after.state,
1894
+ causedByDeliveryId: inFlightMessage !== undefined && isAcknowledgedMessage(inFlightMessage)
1895
+ ? inFlightMessage.inspection?.deliveryId
1896
+ : undefined
1897
+ })
1898
+ })
1899
+ )
1900
+ )
1901
+ ),
1902
+ Effect.asVoid
1903
+ )
1533
1904
  const context: ProcessContext<State, Event> = {
1534
1905
  ...scope,
1535
1906
  ...(logic.execution?._tag === "Compiled" && !logic.execution.childless ? { ownedChildren } : undefined),
1536
1907
  ...acknowledgedContext,
1537
- receive: Queue.take(queue).pipe(Effect.map(messageEvent)),
1538
- poll: Queue.poll(queue).pipe(Effect.map(Option.map(messageEvent))),
1908
+ receive,
1909
+ poll,
1539
1910
  state: SynchronizedRef.get(current).pipe(Effect.map((current) => current.snapshot.state)),
1540
1911
  setState: setActiveState,
1541
- updateState: (f) =>
1542
- updateSnapshot((snapshot) =>
1543
- snapshot.status === "active"
1544
- ? f(snapshot.state).pipe(
1545
- Effect.map((state) => ({
1546
- status: "active" as const,
1547
- state
1548
- }))
1549
- )
1550
- : Effect.succeed(undefined)
1551
- ).pipe(Effect.asVoid)
1912
+ updateState
1552
1913
  }
1553
1914
 
1554
1915
  const getOrCreateChanges = SynchronizedRef.modifyEffect(
@@ -1591,6 +1952,9 @@ const startGenericInternal: <
1591
1952
  const ref: MachineRef<State, Event, Error, Output> = {
1592
1953
  id,
1593
1954
  sessionId,
1955
+ ...(inspector === undefined
1956
+ ? undefined
1957
+ : { inspectionSubject: subject!, sendInspected: offerInspected! }),
1594
1958
  state: SynchronizedRef.get(current).pipe(Effect.map((current) => current.snapshot.state)),
1595
1959
  snapshot: SynchronizedRef.get(current).pipe(Effect.map((current) => current.snapshot)),
1596
1960
  changes: changesStream,
@@ -1715,7 +2079,15 @@ type CompiledRunState = "Initializing" | "Idle" | "Draining"
1715
2079
  class CompiledProcess implements MachineRef<any, any, any, any> {
1716
2080
  readonly id: string
1717
2081
  readonly sessionId: string
1718
- readonly send: (event: unknown) => Effect.Effect<void, StoppedError>;
2082
+ readonly inspectionSubject?: Inspection.Subject
2083
+ readonly send: (event: unknown) => Effect.Effect<void, StoppedError>
2084
+ sendInspected(
2085
+ event: unknown,
2086
+ source: Inspection.Subject | undefined,
2087
+ causedBy: Inspection.Causation | undefined
2088
+ ): Effect.Effect<void, StoppedError> {
2089
+ return this.offerEvent(event, source, causedBy)
2090
+ }
1719
2091
  [acknowledgedSend](
1720
2092
  event: unknown
1721
2093
  ): Effect.Effect<AcknowledgedDelivery<unknown>, unknown | StoppedError> {
@@ -1750,6 +2122,10 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
1750
2122
  private inFlightMessage: ProcessMessage<unknown> | undefined
1751
2123
  private readonly externalEmissions: EmissionRuntime | undefined
1752
2124
  private emissionsPubSub: LazyEmissions
2125
+ private readonly activity?: Inspection.Activity
2126
+ private initialEntryPaths?: ReadonlyArray<string>
2127
+ private initialMicrosteps?: ReadonlyArray<unknown>
2128
+ private initializing?: boolean
1753
2129
 
1754
2130
  constructor(
1755
2131
  private readonly logic: ProcessLogic<any, any, any, any, any, any>,
@@ -1759,26 +2135,106 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
1759
2135
  ) {
1760
2136
  this.sessionId = sessionId
1761
2137
  this.id = options.id ?? sessionId
2138
+ const inspector = options.runtime.inspection
2139
+ if (inspector !== undefined) {
2140
+ this.inspectionSubject = inspectionSubject(logic, this.id, sessionId)
2141
+ this.initializing = true
2142
+ if (options.activity !== undefined) this.activity = { ...options.activity, sessionId }
2143
+ }
1762
2144
  this.externalEmissions = options.emissions
1763
- this.send = (event) => this.offerMessage(event)
2145
+ this.send = inspector === undefined
2146
+ ? (event) => this.offerMessage(event)
2147
+ : (event) => this.offerEvent(event, undefined, undefined)
1764
2148
  this.address = {
1765
2149
  id: this.id,
1766
2150
  sessionId,
1767
2151
  stop: Effect.suspend(() => this.stopFromProcess()),
1768
- send: this.send
2152
+ send: this.send,
2153
+ ...(inspector === undefined
2154
+ ? undefined
2155
+ : {
2156
+ inspectionSubject: this.inspectionSubject!,
2157
+ sendInspected: (
2158
+ event: unknown,
2159
+ source: Inspection.Subject | undefined,
2160
+ causedBy: Inspection.Causation | undefined
2161
+ ) => this.offerEvent(event, source, causedBy)
2162
+ })
1769
2163
  }
1770
2164
  }
1771
2165
 
2166
+ private get inspector(): InspectionRuntime.Runtime | undefined {
2167
+ return this.options.runtime.inspection
2168
+ }
2169
+
2170
+ private causation(): Inspection.Causation | undefined {
2171
+ return messageCausation(this.inFlightMessage, this.initializing === true)
2172
+ }
2173
+
2174
+ private publishCreated(): void {
2175
+ const inspector = this.inspector
2176
+ const subject = this.inspectionSubject
2177
+ if (inspector === undefined || subject === undefined) return
2178
+ inspector.publishUnsafe(
2179
+ this.activity === undefined
2180
+ ? {
2181
+ _tag: "Created",
2182
+ subject,
2183
+ parent: this.options.parent?.inspectionSubject,
2184
+ origin: this.options.origin ?? { _tag: "Root" },
2185
+ definition: this.logic.inspection?.definition
2186
+ }
2187
+ : { _tag: "ActivityStarted", subject: this.activity.owner, activity: this.activity }
2188
+ )
2189
+ }
2190
+
2191
+ private publishInitialized(state: unknown): void {
2192
+ this.initializing = false
2193
+ if (this.activity !== undefined) return
2194
+ const inspector = this.inspector
2195
+ const subject = this.inspectionSubject
2196
+ if (inspector === undefined || subject === undefined) return
2197
+ inspector.publishUnsafe({
2198
+ _tag: "Initialized",
2199
+ subject,
2200
+ snapshot: { status: "active", state },
2201
+ initialEntryPaths: this.initialEntryPaths ?? [],
2202
+ microsteps: InspectionRuntime.microsteps({ microsteps: this.initialMicrosteps ?? [] })
2203
+ })
2204
+ }
2205
+
2206
+ private publishStartFailed(cause: Cause.Cause<unknown>): void {
2207
+ this.initializing = false
2208
+ const inspector = this.inspector
2209
+ const subject = this.inspectionSubject
2210
+ if (inspector === undefined || subject === undefined) return
2211
+ inspector.publishUnsafe(
2212
+ this.activity === undefined
2213
+ ? { _tag: "StartFailed", subject, cause }
2214
+ : {
2215
+ _tag: "ActivityStopped",
2216
+ subject: this.activity.owner,
2217
+ activity: this.activity,
2218
+ exit: Exit.failCause(cause)
2219
+ }
2220
+ )
2221
+ }
2222
+
1772
2223
  private get execution(): CompiledProcessExecution<any, any, any, any, any, any> {
1773
2224
  return this.logic.execution as CompiledProcessExecution<any, any, any, any, any, any>
1774
2225
  }
1775
2226
 
1776
2227
  initializeCompiledSync(): Effect.Effect<MachineRef<any, any, any, any>, unknown> {
2228
+ this.publishCreated()
1777
2229
  if (!this.execution.childless) {
1778
2230
  this.childRuntime = makeChildRuntimeSync(this.address, this.options.runtime, this.services)
1779
2231
  }
1780
2232
  const parent = this.options.parent
1781
- const sendParent = this.options.sendParent ?? (parent === undefined ? noParentSend : parent.send)
2233
+ const sendParent = this.options.sendParent ?? (parent === undefined
2234
+ ? noParentSend
2235
+ : this.inspector === undefined
2236
+ ? parent.send
2237
+ : (event: unknown) => sendMachineTarget(parent, event, this.inspectionSubject, this.causation()))
1782
2238
  this.processScope = {
1783
2239
  self: this.address,
1784
2240
  parent,
@@ -1786,11 +2242,24 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
1786
2242
  sendParent,
1787
2243
  emit: (event) => this.emitEvent(event),
1788
2244
  sendTo: ((target: unknown, event: unknown) =>
1789
- isProcessAddress(target)
1790
- ? target.send(event)
1791
- : this.childRuntime.sendTo(target as ChildSelector, event)) as ProcessScope<unknown>["sendTo"],
2245
+ isMachineTarget(target)
2246
+ ? this.inspector === undefined
2247
+ ? target.send(event)
2248
+ : sendMachineTarget(target, event, this.inspectionSubject, this.causation())
2249
+ : this.childRuntime.sendTo(
2250
+ target as ChildSelector,
2251
+ event,
2252
+ this.inspectionSubject,
2253
+ this.causation()
2254
+ )) as ProcessScope<unknown>["sendTo"],
1792
2255
  stopChild: this.childRuntime.stop,
1793
- failCause: (cause: Cause.Cause<unknown>) => this.failCause(cause)
2256
+ failCause: (cause: Cause.Cause<unknown>) => this.failCause(cause),
2257
+ inspectInitial: this.inspector === undefined
2258
+ ? noInspectInitial
2259
+ : (paths, microsteps = []) => {
2260
+ this.initialEntryPaths = paths
2261
+ this.initialMicrosteps = microsteps
2262
+ }
1794
2263
  }
1795
2264
  const compiledInitial = this.execution.initialSync!
1796
2265
  let initialized: CompiledInitialized
@@ -1798,6 +2267,7 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
1798
2267
  initialized = compiledInitial(this.processScope)
1799
2268
  } catch (error) {
1800
2269
  this.runState = "Idle"
2270
+ this.publishStartFailed(Cause.fail(error))
1801
2271
  return Effect.fail(error)
1802
2272
  }
1803
2273
  this.runState = "Idle"
@@ -1807,6 +2277,7 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
1807
2277
  changes: undefined,
1808
2278
  snapshot: { status: "active", state: initialized.state }
1809
2279
  }
2280
+ this.publishInitialized(initialized.state)
1810
2281
  this.compiledContext = new CompiledProcessContextImpl(this.processScope, this.childRuntime.owned, this)
1811
2282
  if ("executionState" in initialized) {
1812
2283
  this.compiledContext.executionState = initialized.executionState
@@ -1831,11 +2302,16 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
1831
2302
  initialize(): Effect.Effect<MachineRef<any, any, any, any>, unknown, any> {
1832
2303
  const self = this
1833
2304
  return Effect.gen(function*() {
2305
+ self.publishCreated()
1834
2306
  if (!self.execution.childless) {
1835
2307
  self.childRuntime = yield* makeChildRuntime(self.address, self.options.runtime, self.services)
1836
2308
  }
1837
2309
  const parent = self.options.parent
1838
- const sendParent = self.options.sendParent ?? (parent === undefined ? noParentSend : parent.send)
2310
+ const sendParent = self.options.sendParent ?? (parent === undefined
2311
+ ? noParentSend
2312
+ : self.inspector === undefined
2313
+ ? parent.send
2314
+ : (event: unknown) => sendMachineTarget(parent, event, self.inspectionSubject, self.causation()))
1839
2315
  self.processScope = {
1840
2316
  self: self.address,
1841
2317
  parent,
@@ -1843,11 +2319,24 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
1843
2319
  sendParent,
1844
2320
  emit: (event) => self.emitEvent(event),
1845
2321
  sendTo: ((target: unknown, event: unknown) =>
1846
- isProcessAddress(target)
1847
- ? target.send(event)
1848
- : self.childRuntime.sendTo(target as ChildSelector, event)) as ProcessScope<unknown>["sendTo"],
2322
+ isMachineTarget(target)
2323
+ ? self.inspector === undefined
2324
+ ? target.send(event)
2325
+ : sendMachineTarget(target, event, self.inspectionSubject, self.causation())
2326
+ : self.childRuntime.sendTo(
2327
+ target as ChildSelector,
2328
+ event,
2329
+ self.inspectionSubject,
2330
+ self.causation()
2331
+ )) as ProcessScope<unknown>["sendTo"],
1849
2332
  stopChild: self.childRuntime.stop,
1850
- failCause: (cause: Cause.Cause<unknown>) => self.failCause(cause)
2333
+ failCause: (cause: Cause.Cause<unknown>) => self.failCause(cause),
2334
+ inspectInitial: self.inspector === undefined
2335
+ ? noInspectInitial
2336
+ : (paths, microsteps = []) => {
2337
+ self.initialEntryPaths = paths
2338
+ self.initialMicrosteps = microsteps
2339
+ }
1851
2340
  }
1852
2341
 
1853
2342
  const cleanupStartupFailure = <A, E>(exit: Exit.Exit<A, E>): Effect.Effect<void> =>
@@ -1867,7 +2356,10 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
1867
2356
  )
1868
2357
  : compiledInitial(self.processScope)
1869
2358
  const initialized = yield* initializeEffect.pipe(
1870
- Effect.onExit(cleanupStartupFailure),
2359
+ Effect.onExit((exit) => {
2360
+ if (Exit.isFailure(exit)) self.publishStartFailed(exit.cause)
2361
+ return cleanupStartupFailure(exit)
2362
+ }),
1871
2363
  Effect.ensuring(Effect.sync(() => {
1872
2364
  self.runState = "Idle"
1873
2365
  }))
@@ -1879,6 +2371,7 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
1879
2371
  changes: undefined,
1880
2372
  snapshot: { status: "active", state: initial }
1881
2373
  }
2374
+ self.publishInitialized(initial)
1882
2375
  if (self.execution.drain._tag === "Process") {
1883
2376
  self.processContext = {
1884
2377
  ...self.processScope,
@@ -2022,11 +2515,7 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
2022
2515
  return Effect.uninterruptibleMask((restore) =>
2023
2516
  Deferred.make<AcknowledgedDelivery<unknown>, unknown>().pipe(
2024
2517
  Effect.flatMap((deferred) =>
2025
- this.offerMessage({
2026
- [AcknowledgedMessageTypeId]: true as const,
2027
- event,
2028
- deferred
2029
- }).pipe(
2518
+ this.offerEvent(event, undefined, undefined, deferred).pipe(
2030
2519
  Effect.andThen(restore(Deferred.await(deferred)))
2031
2520
  )
2032
2521
  )
@@ -2034,6 +2523,31 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
2034
2523
  )
2035
2524
  }
2036
2525
 
2526
+ private offerEvent(
2527
+ event: unknown,
2528
+ source: Inspection.Subject | undefined,
2529
+ causedBy: Inspection.Causation | undefined,
2530
+ deferred?: Deferred.Deferred<AcknowledgedDelivery<unknown>, unknown>
2531
+ ): Effect.Effect<void, StoppedError> {
2532
+ return Effect.suspend(() => {
2533
+ const inspector = this.inspector
2534
+ const subject = this.inspectionSubject
2535
+ const message: ProcessMessage<unknown> = inspector?.isActive() === true && subject !== undefined
2536
+ ? makeInspectedMessage(
2537
+ inspector,
2538
+ subject,
2539
+ event,
2540
+ source,
2541
+ causedBy,
2542
+ deferred
2543
+ )
2544
+ : deferred === undefined
2545
+ ? event
2546
+ : { [AcknowledgedMessageTypeId]: true as const, event, deferred }
2547
+ return this.offerMessage(message)
2548
+ })
2549
+ }
2550
+
2037
2551
  private offerMessage(message: ProcessMessage<unknown>): Effect.Effect<void, StoppedError> {
2038
2552
  return Effect.uninterruptible(
2039
2553
  Effect.suspend(() => {
@@ -2041,6 +2555,9 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
2041
2555
  return Effect.fail(new StoppedError())
2042
2556
  }
2043
2557
  offerCompactMailbox(this.mailbox, message)
2558
+ const inspector = this.inspector
2559
+ const subject = this.inspectionSubject
2560
+ if (inspector !== undefined && subject !== undefined) publishInspectedSent(inspector, subject, message)
2044
2561
  this.offerRevision += 1
2045
2562
  if (this.runState === "Draining") {
2046
2563
  return Effect.void
@@ -2096,6 +2613,7 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
2096
2613
  this.lifecycle !== "Active" ||
2097
2614
  (this.options.onOutcome !== undefined && this.options.skipStoppedOutcome !== true) ||
2098
2615
  this.options.onStop !== undefined ||
2616
+ this.inspector !== undefined ||
2099
2617
  this.current.changes !== undefined ||
2100
2618
  this.current.terminalizing || this.current.snapshot.status !== "active"
2101
2619
  ) {
@@ -2245,13 +2763,31 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
2245
2763
  Effect.exit,
2246
2764
  Effect.asVoid
2247
2765
  )
2766
+ const inspector = this.inspector
2767
+ const subject = this.inspectionSubject
2768
+ const closeEmissionsAndInspect = inspector === undefined || subject === undefined
2769
+ ? this.closeEmissions()
2770
+ : this.closeEmissions().pipe(
2771
+ Effect.andThen(Effect.sync(() =>
2772
+ inspector.publishUnsafe(
2773
+ this.activity === undefined
2774
+ ? { _tag: "Terminated", subject, snapshot }
2775
+ : {
2776
+ _tag: "ActivityStopped",
2777
+ subject: this.activity.owner,
2778
+ activity: this.activity,
2779
+ exit: snapshot.status === "stopped" ? Exit.interrupt() : exit
2780
+ }
2781
+ )
2782
+ ))
2783
+ )
2248
2784
  return Effect.uninterruptible(
2249
2785
  Effect.sync(() => {
2250
2786
  closeCompactMailbox(this.mailbox)
2251
2787
  }).pipe(
2252
2788
  Effect.andThen(this.childRuntime.close(exit)),
2253
2789
  Effect.andThen(this.setAndPublishSnapshot(snapshot)),
2254
- Effect.andThen(this.closeEmissions()),
2790
+ Effect.andThen(closeEmissionsAndInspect),
2255
2791
  Effect.andThen(Effect.sync(() => {
2256
2792
  if (requested._tag === "Failure") {
2257
2793
  failAcknowledgedMessage(this.inFlightMessage, requested.cause)
@@ -2262,6 +2798,11 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
2262
2798
  })),
2263
2799
  Effect.andThen(notifyOutcome),
2264
2800
  Effect.andThen(this.options.onStop ?? Effect.void),
2801
+ Effect.andThen(
2802
+ this.options.inspectionRoot === true && this.inspector !== undefined
2803
+ ? this.inspector.close
2804
+ : Effect.void
2805
+ ),
2265
2806
  Effect.andThen(Effect.sync(() => {
2266
2807
  this.options.onStopSync?.()
2267
2808
  this.runState = "Idle"
@@ -2301,12 +2842,26 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
2301
2842
  }
2302
2843
 
2303
2844
  private emitEvent(event: unknown): Effect.Effect<void> {
2304
- if (this.externalEmissions !== undefined) return this.externalEmissions.emit(event)
2305
- return Effect.suspend(() =>
2306
- this.emissionsPubSub === undefined || this.emissionsPubSub === EmissionsClosed
2307
- ? Effect.void
2308
- : PubSub.publish(this.emissionsPubSub, event).pipe(Effect.asVoid)
2309
- )
2845
+ const publish = this.externalEmissions !== undefined ?
2846
+ this.externalEmissions.emit(event) :
2847
+ Effect.suspend(() =>
2848
+ this.emissionsPubSub === undefined || this.emissionsPubSub === EmissionsClosed
2849
+ ? Effect.void
2850
+ : PubSub.publish(this.emissionsPubSub, event).pipe(Effect.asVoid)
2851
+ )
2852
+ const inspector = this.inspector
2853
+ const subject = this.inspectionSubject
2854
+ if (inspector === undefined || subject === undefined) return publish
2855
+ return publish.pipe(Effect.tap(() =>
2856
+ Effect.sync(() =>
2857
+ inspector.publishUnsafe({
2858
+ _tag: "Emitted",
2859
+ subject,
2860
+ emission: event,
2861
+ causedBy: this.causation()
2862
+ })
2863
+ )
2864
+ ))
2310
2865
  }
2311
2866
 
2312
2867
  shutdownEmissions(): Effect.Effect<void> {
@@ -2570,7 +3125,17 @@ const startCompactCompiledInternal: typeof startGenericInternal = Effect.fnUntra
2570
3125
  ? process.initializeCompiledSync()
2571
3126
  : process.initialize()
2572
3127
  return yield* initialize.pipe(
2573
- Effect.onExit((exit) => Exit.isFailure(exit) ? process.shutdownEmissions() : Effect.void)
3128
+ Effect.onExit((exit) =>
3129
+ Exit.isFailure(exit)
3130
+ ? process.shutdownEmissions().pipe(
3131
+ Effect.andThen(
3132
+ options.inspectionRoot === true && options.runtime.inspection !== undefined
3133
+ ? options.runtime.inspection.close
3134
+ : Effect.void
3135
+ )
3136
+ )
3137
+ : Effect.void
3138
+ )
2574
3139
  )
2575
3140
  }) as typeof startGenericInternal
2576
3141
 
@@ -2689,6 +3254,8 @@ const prepareProcessWithStrategy = Effect.fnUntraced(function*<
2689
3254
  ) {
2690
3255
  const runtime = yield* makeProcessRuntime
2691
3256
  const sessionId = yield* runtime.nextSessionId
3257
+ const inspection = yield* InspectionRuntime.make(sessionId)
3258
+ runtime.inspection = inspection
2692
3259
  const emissions = makeEmissionRuntime()
2693
3260
  const started = yield* Deferred.make<MachineRef<State, Event, Error, Output, Emitted>, InitialError>()
2694
3261
  const internalOptions: StartInternalOptions = options === undefined
@@ -2696,14 +3263,18 @@ const prepareProcessWithStrategy = Effect.fnUntraced(function*<
2696
3263
  detached: true,
2697
3264
  emissions,
2698
3265
  runtime,
2699
- sessionId
3266
+ sessionId,
3267
+ inspectionRoot: true,
3268
+ origin: { _tag: "Root" }
2700
3269
  }
2701
3270
  : {
2702
3271
  ...options,
2703
3272
  detached: true,
2704
3273
  emissions,
2705
3274
  runtime,
2706
- sessionId
3275
+ sessionId,
3276
+ inspectionRoot: true,
3277
+ origin: { _tag: "Root" }
2707
3278
  }
2708
3279
  const initialize = strategy === "generic"
2709
3280
  ? startGenericInternal(logic, internalOptions)
@@ -2724,6 +3295,7 @@ const prepareProcessWithStrategy = Effect.fnUntraced(function*<
2724
3295
  Deferred.await(started).pipe(Effect.map((ref) => ref.changes))
2725
3296
  ),
2726
3297
  emissions: emissions.stream as Stream.Stream<Emitted>,
3298
+ inspection: inspection.stream,
2727
3299
  start
2728
3300
  }
2729
3301
  })