@lovelace_lol/polymer 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +226 -0
  2. package/dist/index.d.ts +2603 -0
  3. package/dist/index.js +1031 -0
  4. package/dist/test.cjs +1519 -0
  5. package/dist/worker/agency-worker.js +972 -0
  6. package/dist/worker/manifest.edn +1 -0
  7. package/package.json +57 -0
  8. package/shadow-cljs.edn +173 -0
  9. package/src/polymer/animation/agency.cljs +84 -0
  10. package/src/polymer/animation/domain.cljs +110 -0
  11. package/src/polymer/animation/planner.cljs +88 -0
  12. package/src/polymer/animation/runtime.cljs +137 -0
  13. package/src/polymer/animation/scheduler.cljs +178 -0
  14. package/src/polymer/animation/service.cljs +981 -0
  15. package/src/polymer/animation/state.cljs +112 -0
  16. package/src/polymer/blink/agency.cljs +131 -0
  17. package/src/polymer/blink/planner.cljs +115 -0
  18. package/src/polymer/blink/scheduler.cljs +90 -0
  19. package/src/polymer/blink/snippet.cljs +50 -0
  20. package/src/polymer/blink/state.cljs +102 -0
  21. package/src/polymer/camera_context/agency.cljs +132 -0
  22. package/src/polymer/camera_context/domain.cljs +186 -0
  23. package/src/polymer/camera_context/planner.cljs +50 -0
  24. package/src/polymer/camera_context/scheduler.cljs +113 -0
  25. package/src/polymer/camera_context/state.cljs +121 -0
  26. package/src/polymer/character.cljs +471 -0
  27. package/src/polymer/conversation/agency.cljs +83 -0
  28. package/src/polymer/conversation/domain.cljs +35 -0
  29. package/src/polymer/conversation/effector.cljs +130 -0
  30. package/src/polymer/conversation/planner.cljs +178 -0
  31. package/src/polymer/conversation/scheduler.cljs +202 -0
  32. package/src/polymer/conversation/service.cljs +329 -0
  33. package/src/polymer/conversation/state.cljs +186 -0
  34. package/src/polymer/core.cljs +172 -0
  35. package/src/polymer/eye_head/agency.cljs +75 -0
  36. package/src/polymer/eye_head/domain.cljs +133 -0
  37. package/src/polymer/eye_head/goap.cljs +106 -0
  38. package/src/polymer/eye_head/scheduler.cljs +176 -0
  39. package/src/polymer/eye_head/service.cljs +259 -0
  40. package/src/polymer/eye_head/snippet.cljs +92 -0
  41. package/src/polymer/eye_head/state.cljs +167 -0
  42. package/src/polymer/gaze/agency.cljs +85 -0
  43. package/src/polymer/gaze/domain.cljs +211 -0
  44. package/src/polymer/gaze/goap.cljs +208 -0
  45. package/src/polymer/gaze/scheduler.cljs +184 -0
  46. package/src/polymer/gaze/state.cljs +202 -0
  47. package/src/polymer/gesture/agency.cljs +185 -0
  48. package/src/polymer/gesture/goap.cljs +461 -0
  49. package/src/polymer/gesture/scheduler.cljs +122 -0
  50. package/src/polymer/gesture/snippet.cljs +198 -0
  51. package/src/polymer/gesture/state.cljs +273 -0
  52. package/src/polymer/lipsync/agency.cljs +593 -0
  53. package/src/polymer/lipsync/articulation/jaw.cljs +258 -0
  54. package/src/polymer/lipsync/articulation/snippet.cljs +605 -0
  55. package/src/polymer/lipsync/articulation/tongue.cljs +418 -0
  56. package/src/polymer/lipsync/articulation/visemes.cljs +429 -0
  57. package/src/polymer/lipsync/goap.cljs +132 -0
  58. package/src/polymer/lipsync/pronunciation/effector.cljs +51 -0
  59. package/src/polymer/lipsync/scheduler.cljs +302 -0
  60. package/src/polymer/lipsync/state.cljs +202 -0
  61. package/src/polymer/nlp/phonemes.cljs +164 -0
  62. package/src/polymer/prosodic/agency.cljs +196 -0
  63. package/src/polymer/prosodic/goap.cljs +122 -0
  64. package/src/polymer/prosodic/scheduler.cljs +99 -0
  65. package/src/polymer/prosodic/snippet.cljs +106 -0
  66. package/src/polymer/prosodic/state.cljs +139 -0
  67. package/src/polymer/runtime/embody.cljs +117 -0
  68. package/src/polymer/runtime/embody_wasm.cljs +177 -0
  69. package/src/polymer/runtime/rust_embody_host.cljs +645 -0
  70. package/src/polymer/society.cljs +239 -0
  71. package/src/polymer/stream.cljs +130 -0
  72. package/src/polymer/transcription/agency.cljs +69 -0
  73. package/src/polymer/transcription/domain.cljs +68 -0
  74. package/src/polymer/transcription/effector.cljs +611 -0
  75. package/src/polymer/transcription/planner.cljs +203 -0
  76. package/src/polymer/transcription/scheduler.cljs +225 -0
  77. package/src/polymer/transcription/service.cljs +209 -0
  78. package/src/polymer/transcription/state.cljs +239 -0
  79. package/src/polymer/tts/agency.cljs +503 -0
  80. package/src/polymer/tts/azure.cljs +290 -0
  81. package/src/polymer/tts/planner.cljs +178 -0
  82. package/src/polymer/tts/runtime.cljs +624 -0
  83. package/src/polymer/tts/scheduler.cljs +162 -0
  84. package/src/polymer/tts/state.cljs +191 -0
  85. package/src/polymer/worker.cljs +70 -0
@@ -0,0 +1,75 @@
1
+ (ns polymer.eye-head.agency
2
+ (:require [polymer.eye-head.domain :as domain]
3
+ [polymer.eye-head.goap :as goap]
4
+ [polymer.eye-head.scheduler :as scheduler]
5
+ [polymer.eye-head.state :as state]
6
+ [polymer.stream :as stream]))
7
+
8
+ ;; Eye/Head owns synchronized gaze movement. Peer agencies publish look
9
+ ;; requests; this agency plans and schedules eye/head AU snippets for Animation.
10
+
11
+ (defn now-ms
12
+ []
13
+ (.now js/Date))
14
+
15
+ (defn create-eye-head-tracking-agency
16
+ [config]
17
+ (let [input-stream (stream/create-stream)
18
+ event-stream (stream/create-stream)
19
+ state-atom (atom (state/config->state config))
20
+ disposed? (atom false)
21
+ emit-input (:emit input-stream)
22
+ emit-event (:emit event-stream)
23
+ scheduler-atom (atom nil)]
24
+ (letfn [(dispatch! [command]
25
+ (when-not @disposed?
26
+ (let [payload (domain/data-map command)
27
+ plan (goap/plan-command payload @state-atom (now-ms))]
28
+ (emit-input {:type "command"
29
+ :agency "eyeHeadTracking"
30
+ :command payload})
31
+ (when-let [agency-scheduler @scheduler-atom]
32
+ ((:schedule agency-scheduler) plan)))))
33
+
34
+ (dispatch-type! [type]
35
+ (dispatch! #js {:type type}))]
36
+ (let [agency-scheduler (scheduler/create-scheduler
37
+ {:state-atom state-atom
38
+ :emit-event emit-event})]
39
+ (reset! scheduler-atom agency-scheduler)
40
+ #js {:dispatch dispatch!
41
+ :snapshot (fn [] (state/visible-state @state-atom))
42
+ :input (stream/writable-port input-stream dispatch!)
43
+ :events (stream/readable-port event-stream)
44
+ :subscribeInput (fn [listener] ((:subscribe input-stream) listener))
45
+ :subscribeEvents (fn [listener] ((:subscribe event-stream) listener))
46
+ :subscribe (fn [listener] ((:subscribe event-stream) listener))
47
+ :subscribeStatus (fn [listener] ((:subscribe event-stream) listener))
48
+ :setTarget (fn [target]
49
+ (dispatch! #js {:type "setTarget"
50
+ :target target}))
51
+ :configure (fn [config]
52
+ (dispatch! #js {:type "configure"
53
+ :config config}))
54
+ :enable (fn [] (dispatch-type! "enable"))
55
+ :disable (fn [] (dispatch-type! "disable"))
56
+ :reset (fn
57
+ ([] (dispatch-type! "reset"))
58
+ ([duration-ms] (dispatch! #js {:type "reset"
59
+ :durationMs duration-ms})))
60
+ :cancel (fn
61
+ ([] (dispatch-type! "cancel"))
62
+ ([reason] (dispatch! #js {:type "cancel"
63
+ :reason reason})))
64
+ :flush (fn []
65
+ (when-let [agency-scheduler @scheduler-atom]
66
+ ((:flush agency-scheduler))))
67
+ :queue (fn []
68
+ (when-let [agency-scheduler @scheduler-atom]
69
+ (clj->js ((:queue agency-scheduler)))))
70
+ :dispose (fn []
71
+ (when-not @disposed?
72
+ (reset! disposed? true)
73
+ ((:dispose agency-scheduler))
74
+ ((:dispose input-stream))
75
+ ((:dispose event-stream))))}))))
@@ -0,0 +1,133 @@
1
+ (ns polymer.eye-head.domain)
2
+
3
+ ;; Eye/Head domain code normalizes incoming gaze requests and configuration.
4
+ ;; It stays pure so movement policy can be tested without any animation runtime.
5
+
6
+ (def zero-target {:x 0 :y 0 :z 0})
7
+
8
+ (defn finite-number?
9
+ [value]
10
+ (and (number? value) (js/isFinite value)))
11
+
12
+ (defn number-or
13
+ [value fallback]
14
+ (if (finite-number? value) value fallback))
15
+
16
+ (defn clamp
17
+ [lo hi value]
18
+ (min hi (max lo value)))
19
+
20
+ (defn data-map
21
+ [value]
22
+ (cond
23
+ (map? value) value
24
+ value (js->clj value :keywordize-keys true)
25
+ :else {}))
26
+
27
+ (defn normalize-target
28
+ ([value] (normalize-target value zero-target))
29
+ ([value fallback]
30
+ (let [input (data-map value)
31
+ fallback (merge zero-target fallback)]
32
+ {:x (clamp -1 1 (number-or (:x input) (:x fallback)))
33
+ :y (clamp -1 1 (number-or (:y input) (:y fallback)))
34
+ :z (number-or (:z input) (:z fallback))})))
35
+
36
+ (defn request-id
37
+ [prefix now-ms]
38
+ (str prefix ":" now-ms ":" (rand-int 1000000)))
39
+
40
+ (defn requested-target
41
+ [command]
42
+ (let [payload (data-map command)]
43
+ (or (:target payload)
44
+ (:gazeTarget payload)
45
+ (:lookTarget payload)
46
+ (:value payload))))
47
+
48
+ (defn has-requested-target?
49
+ [command]
50
+ (some? (requested-target command)))
51
+
52
+ (defn request-source
53
+ [command]
54
+ (let [payload (data-map command)]
55
+ (or (:source payload)
56
+ (:agency payload)
57
+ "gaze")))
58
+
59
+ (defn normalize-gaze-request
60
+ [command state now-ms]
61
+ (let [payload (data-map command)
62
+ config (:config state)
63
+ target (normalize-target (requested-target payload))
64
+ eye-enabled (if (contains? payload :eyeEnabled)
65
+ (boolean (:eyeEnabled payload))
66
+ true)
67
+ head-enabled (if (contains? payload :headEnabled)
68
+ (boolean (:headEnabled payload))
69
+ true)
70
+ head-follow-eyes (if (contains? payload :headFollowEyes)
71
+ (boolean (:headFollowEyes payload))
72
+ (:headFollowEyes config))]
73
+ {:type "eyeHeadTracking.requestGaze"
74
+ :agency "eyeHeadTracking"
75
+ :sourceAgency (request-source payload)
76
+ :requestId (or (:requestId payload) (request-id "eyeHeadTracking" now-ms))
77
+ :target target
78
+ :rawTarget (normalize-target (:rawTarget payload) target)
79
+ :mode (or (:mode payload) (:mode state) "manual")
80
+ :eyeEnabled (and (:eyeTrackingEnabled config) eye-enabled)
81
+ :headEnabled (and (:headTrackingEnabled config) head-enabled)
82
+ :headFollowEyes head-follow-eyes
83
+ :eyeIntensity (number-or (:eyeIntensity payload) (:eyeIntensity config))
84
+ :headIntensity (number-or (:headIntensity payload) (:headIntensity config))
85
+ :headRoll (number-or (:headRoll payload) (:headRoll config))
86
+ :eyeDurationMs (number-or (:eyeDurationMs payload)
87
+ (:transitionDurationMs config))
88
+ :headDurationMs (number-or (:headDurationMs payload)
89
+ (:transitionDurationMs config))
90
+ :createdAt (or (:createdAt payload) now-ms)}))
91
+
92
+ (defn movement-enabled?
93
+ [request]
94
+ (or (:eyeEnabled request)
95
+ (and (:headEnabled request) (:headFollowEyes request))))
96
+
97
+ (defn normalize-reset-request
98
+ [command state now-ms]
99
+ (let [payload (data-map command)
100
+ config (:config state)
101
+ duration (number-or (:durationMs payload)
102
+ (number-or (:duration-ms payload)
103
+ (:returnToCenterDurationMs config)))]
104
+ {:type "eyeHeadTracking.requestReset"
105
+ :agency "eyeHeadTracking"
106
+ :sourceAgency (request-source payload)
107
+ :requestId (or (:requestId payload) (request-id "eyeHeadTracking:reset" now-ms))
108
+ :target zero-target
109
+ :rawTarget zero-target
110
+ :mode (:mode state)
111
+ :eyeEnabled (if (contains? payload :eyes)
112
+ (and (:eyeTrackingEnabled config) (boolean (:eyes payload)))
113
+ (:eyeTrackingEnabled config))
114
+ :headEnabled (if (contains? payload :head)
115
+ (and (:headTrackingEnabled config) (boolean (:head payload)))
116
+ (:headTrackingEnabled config))
117
+ :headFollowEyes true
118
+ :eyeIntensity (:eyeIntensity config)
119
+ :headIntensity (:headIntensity config)
120
+ :headRoll 0
121
+ :eyeDurationMs duration
122
+ :headDurationMs duration
123
+ :createdAt now-ms}))
124
+
125
+ (defn normalize-cancel-request
126
+ [command now-ms]
127
+ (let [payload (data-map command)]
128
+ {:type "eyeHeadTracking.requestCancel"
129
+ :agency "eyeHeadTracking"
130
+ :sourceAgency (request-source payload)
131
+ :requestId (or (:requestId payload) (request-id "eyeHeadTracking:cancel" now-ms))
132
+ :reason (or (:reason payload) "cancelled")
133
+ :createdAt now-ms}))
@@ -0,0 +1,106 @@
1
+ (ns polymer.eye-head.goap
2
+ (:require [polymer.eye-head.domain :as domain]
3
+ [polymer.eye-head.snippet :as snippet]))
4
+
5
+ ;; Eye/Head planning accepts gaze requests from peer agencies and turns them
6
+ ;; into local movement work. Animation requests are produced only after the
7
+ ;; scheduler accepts the plan.
8
+
9
+ (def supported-command-types
10
+ #{"configure"
11
+ "enable"
12
+ "disable"
13
+ "setTarget"
14
+ "set-target"
15
+ "requestGaze"
16
+ "eyeHeadTracking.requestGaze"
17
+ "reset"
18
+ "eyeHeadTracking.requestReset"
19
+ "cancel"
20
+ "eyeHeadTracking.requestCancel"})
21
+
22
+ (defn command-type
23
+ [command]
24
+ (:type command))
25
+
26
+ (defn plan-gaze
27
+ [command state now-ms]
28
+ (if-not (domain/has-requested-target? command)
29
+ [{:op "fail"
30
+ :reason "missing-target"
31
+ :commandType (command-type command)}]
32
+ (let [request (domain/normalize-gaze-request command state now-ms)]
33
+ (cond
34
+ (not (get-in state [:config :enabled]))
35
+ [{:op "ignore-request"
36
+ :request request
37
+ :reason "disabled"}]
38
+
39
+ (not (domain/movement-enabled? request))
40
+ [{:op "ignore-request"
41
+ :request request
42
+ :reason "movement-disabled"}]
43
+
44
+ :else
45
+ [{:op "queue-gaze"
46
+ :request request
47
+ :snippet (snippet/build-gaze-snippet request (:config state))}]))))
48
+
49
+ (defn plan-reset
50
+ [command state now-ms]
51
+ (let [request (domain/normalize-reset-request command state now-ms)]
52
+ (if (domain/movement-enabled? request)
53
+ [{:op "queue-reset"
54
+ :request request
55
+ :snippet (snippet/build-gaze-snippet request (:config state))}]
56
+ [{:op "ignore-request"
57
+ :request request
58
+ :reason "movement-disabled"}])))
59
+
60
+ (defn plan-cancel
61
+ [command now-ms]
62
+ [{:op "cancel-active"
63
+ :request (domain/normalize-cancel-request command now-ms)}])
64
+
65
+ (defn command-steps
66
+ [command state now-ms]
67
+ (let [type (command-type command)]
68
+ (cond
69
+ (not (contains? supported-command-types type))
70
+ [{:op "fail"
71
+ :reason "unsupported-command"
72
+ :commandType type}]
73
+
74
+ (= "configure" type)
75
+ [{:op "apply-config" :config (:config command)}
76
+ {:op "publish-status" :reason "configured"}]
77
+
78
+ (= "enable" type)
79
+ [{:op "set-enabled" :enabled true}
80
+ {:op "publish-status" :reason "enabled"}]
81
+
82
+ (= "disable" type)
83
+ [{:op "set-enabled" :enabled false}
84
+ {:op "cancel-active"
85
+ :request (domain/normalize-cancel-request
86
+ (assoc command :reason "disabled")
87
+ now-ms)}
88
+ {:op "publish-status" :reason "disabled"}]
89
+
90
+ (#{"reset" "eyeHeadTracking.requestReset"} type)
91
+ (plan-reset command state now-ms)
92
+
93
+ (#{"cancel" "eyeHeadTracking.requestCancel"} type)
94
+ (plan-cancel command now-ms)
95
+
96
+ :else
97
+ (plan-gaze command state now-ms))))
98
+
99
+ (defn plan-command
100
+ [command state now-ms]
101
+ (let [steps (command-steps command state now-ms)]
102
+ {:agency "eyeHeadTracking"
103
+ :commandType (command-type command)
104
+ :createdAt now-ms
105
+ :ok (not= "fail" (:op (first steps)))
106
+ :steps steps}))
@@ -0,0 +1,176 @@
1
+ (ns polymer.eye-head.scheduler
2
+ (:require [polymer.eye-head.state :as state]))
3
+
4
+ ;; The Eye/Head scheduler is the boundary that replaces or cancels movement
5
+ ;; snippets. It publishes animation requests as data; the Animation agency owns
6
+ ;; the runtime side effect of playing those snippets.
7
+
8
+ (defn now-ms
9
+ []
10
+ (.now js/Date))
11
+
12
+ (defn clear-timeout!
13
+ [timeout-atom]
14
+ (when-let [timeout @timeout-atom]
15
+ (js/clearTimeout timeout)
16
+ (reset! timeout-atom nil)))
17
+
18
+ (defn queue-entry
19
+ [step index]
20
+ (assoc step
21
+ :agency "eyeHeadTracking"
22
+ :queueIndex index
23
+ :queuedAt (now-ms)))
24
+
25
+ (defn create-scheduler
26
+ [{:keys [state-atom emit-event]}]
27
+ (let [coalesce-timeout (atom nil)
28
+ pending-work (atom nil)
29
+ queue (atom [])
30
+ disposed? (atom false)]
31
+ (letfn [(enqueue! [step]
32
+ (let [entry (queue-entry step (count @queue))]
33
+ (swap! queue conj entry)
34
+ entry))
35
+
36
+ (emit-status! [reason]
37
+ (emit-event {:type "eyeHeadTracking.status"
38
+ :agency "eyeHeadTracking"
39
+ :status (:status @state-atom)
40
+ :enabled (get-in @state-atom [:config :enabled])
41
+ :activeSnippetNames (:activeSnippetNames @state-atom)
42
+ :reason reason
43
+ :at (now-ms)}))
44
+
45
+ (emit-remove-active! [reason]
46
+ (doseq [name (:activeSnippetNames @state-atom)]
47
+ (let [removed-at (now-ms)]
48
+ (swap! state-atom state/record-removed name reason removed-at)
49
+ (emit-event {:type "animation.requestRemoveSnippet"
50
+ :agency "eyeHeadTracking"
51
+ :requestId (str "eyeHeadTracking:remove:" removed-at)
52
+ :name name
53
+ :reason reason
54
+ :queuedAt removed-at}))))
55
+
56
+ (publish-work-now! []
57
+ (when-not @disposed?
58
+ (clear-timeout! coalesce-timeout)
59
+ (when-let [{:keys [kind request snippet]} @pending-work]
60
+ (reset! pending-work nil)
61
+ (when (get-in @state-atom [:config :replaceExisting])
62
+ (emit-remove-active! "replaced"))
63
+ (let [published-at (now-ms)]
64
+ (enqueue! {:op "publish-animation"
65
+ :requestId (:requestId request)
66
+ :name (:name snippet)})
67
+ (if (= kind "reset")
68
+ (swap! state-atom state/record-reset request snippet published-at)
69
+ (swap! state-atom state/record-scheduled request snippet published-at))
70
+ (emit-event {:type "animation.requestScheduleSnippet"
71
+ :agency "eyeHeadTracking"
72
+ :requestId (:requestId request)
73
+ :snippet snippet
74
+ :options {:autoPlay true
75
+ :sourceAgency "eyeHeadTracking"}})))))
76
+
77
+ (queue-work! [kind request snippet]
78
+ (when-not @disposed?
79
+ (let [queued-at (now-ms)
80
+ work {:kind kind
81
+ :request (assoc request :queuedAt queued-at)
82
+ :snippet snippet}
83
+ delay-ms (get-in @state-atom [:config :coalesceMs])]
84
+ (reset! pending-work work)
85
+ (swap! state-atom state/record-pending (:request work))
86
+ (clear-timeout! coalesce-timeout)
87
+ (enqueue! {:op "coalesce-work"
88
+ :kind kind
89
+ :requestId (:requestId request)
90
+ :coalesceMs delay-ms})
91
+ (if (pos? delay-ms)
92
+ (reset! coalesce-timeout
93
+ (js/setTimeout publish-work-now! delay-ms))
94
+ (publish-work-now!)))))
95
+
96
+ (cancel-pending! []
97
+ (clear-timeout! coalesce-timeout)
98
+ (reset! pending-work nil)
99
+ (swap! state-atom assoc :pendingRequest nil))
100
+
101
+ (ignore-request! [request reason]
102
+ (let [ignored {:type "eyeHeadTracking.requestIgnored"
103
+ :agency "eyeHeadTracking"
104
+ :requestId (:requestId request)
105
+ :reason reason
106
+ :target (:target request)
107
+ :ignoredAt (now-ms)}]
108
+ (cancel-pending!)
109
+ (swap! state-atom state/record-ignored ignored)
110
+ (emit-event ignored)))
111
+
112
+ (cancel-active! [request]
113
+ (cancel-pending!)
114
+ (emit-remove-active! (:reason request))
115
+ (swap! state-atom state/record-cancel request)
116
+ (emit-event {:type "eyeHeadTracking.cancelled"
117
+ :agency "eyeHeadTracking"
118
+ :requestId (:requestId request)
119
+ :reason (:reason request)
120
+ :at (now-ms)}))
121
+
122
+ (execute-step! [step]
123
+ (enqueue! step)
124
+ (case (:op step)
125
+ "apply-config"
126
+ (do
127
+ (swap! state-atom state/configure (:config step))
128
+ (emit-status! (:reason step)))
129
+
130
+ "set-enabled"
131
+ (do
132
+ (swap! state-atom state/set-enabled (:enabled step))
133
+ (emit-status! (if (:enabled step) "enabled" "disabled")))
134
+
135
+ "publish-status"
136
+ (emit-status! (:reason step))
137
+
138
+ "queue-gaze"
139
+ (queue-work! "gaze" (:request step) (:snippet step))
140
+
141
+ "queue-reset"
142
+ (queue-work! "reset" (:request step) (:snippet step))
143
+
144
+ "cancel-active"
145
+ (cancel-active! (:request step))
146
+
147
+ "ignore-request"
148
+ (ignore-request! (:request step) (:reason step))
149
+
150
+ "fail"
151
+ (emit-event {:type "error"
152
+ :agency "eyeHeadTracking"
153
+ :message (:reason step)
154
+ :commandType (:commandType step)})
155
+
156
+ nil))]
157
+ {:schedule
158
+ (fn [plan]
159
+ (when-not @disposed?
160
+ (swap! state-atom state/record-plan plan)
161
+ (doseq [step (:steps plan)]
162
+ (execute-step! step))))
163
+
164
+ :flush
165
+ (fn []
166
+ (publish-work-now!))
167
+
168
+ :queue
169
+ (fn []
170
+ @queue)
171
+
172
+ :dispose
173
+ (fn []
174
+ (reset! disposed? true)
175
+ (cancel-pending!)
176
+ (reset! queue []))})))