@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,471 @@
1
+ (ns polymer.character
2
+ (:require [polymer.animation.agency :as animation]
3
+ [polymer.blink.agency :as blink]
4
+ [polymer.camera-context.agency :as camera-context]
5
+ [polymer.conversation.agency :as conversation]
6
+ [polymer.eye-head.agency :as eye-head]
7
+ [polymer.gaze.agency :as gaze]
8
+ [polymer.gesture.agency :as gesture]
9
+ [polymer.lipsync.agency :as lipsync]
10
+ [polymer.prosodic.agency :as prosodic]
11
+ [polymer.society :as society]
12
+ [polymer.stream :as stream]
13
+ [polymer.transcription.agency :as transcription]
14
+ [polymer.tts.agency :as tts]))
15
+
16
+ ;; A character is a network of Polymer agencies.
17
+ ;;
18
+ ;; LoomLarge may still consume streams from legacy Latticework services during
19
+ ;; the migration, but it should not consume Polymer animation events and turn
20
+ ;; them into animation calls. Inside Polymer, Blink emits blink intent and fast
21
+ ;; blink facts, Camera Context emits gaze facts, Transcription emits transcript
22
+ ;; facts, Conversation emits speech requests, TTS emits speech timing facts,
23
+ ;; LipSync emits mouth animation intent, Gesture emits arm/hand animation intent,
24
+ ;; Prosodic emits speech/blink expression intent, and Animation talks directly
25
+ ;; to Embody. Hair appearance and physics are Embody runtime concerns, not a
26
+ ;; Polymer behavioral agency.
27
+
28
+ (defn create-character-agencies [config]
29
+ (let [input-stream (stream/create-stream)
30
+ event-stream (stream/create-stream)
31
+ emit-input (:emit input-stream)
32
+ emit-event (:emit event-stream)
33
+ agency-society (society/normalize-agency-society
34
+ (when config (aget config "agencySociety")))
35
+ animation-agency (animation/create-animation-agency
36
+ (society/agency-config config agency-society "animation"))
37
+ blink-agency (blink/create-blink-agency
38
+ (society/agency-config config agency-society "blink"))
39
+ gaze-agency (gaze/create-gaze-agency
40
+ (society/agency-config config agency-society "gaze"))
41
+ eye-head-agency (eye-head/create-eye-head-tracking-agency
42
+ (society/agency-config config agency-society "eyeHeadTracking"))
43
+ gesture-agency (gesture/create-gesture-agency
44
+ (society/agency-config config agency-society "gesture"))
45
+ camera-context-agency (camera-context/create-camera-context-agency
46
+ (society/agency-config config agency-society "cameraContext"))
47
+ transcription-agency (transcription/create-transcription-agency
48
+ (society/agency-config config agency-society "transcription"))
49
+ conversation-config (society/agency-config
50
+ config agency-society "conversation")
51
+ ;; The provider is injected once into Conversation's private effector.
52
+ ;; The character router never invokes provider functions itself.
53
+ conversation-provider (when config (aget config "conversationProvider"))
54
+ conversation-options (if conversation-provider
55
+ (let [options (js/Object.assign
56
+ #js {}
57
+ (or conversation-config #js {}))]
58
+ (aset options "provider" conversation-provider)
59
+ options)
60
+ conversation-config)
61
+ conversation-agency (conversation/create-conversation-agency
62
+ conversation-options)
63
+ tts-agency (tts/create-tts-agency
64
+ (society/agency-config config agency-society "tts"))
65
+ lipsync-runtime (when config (aget config "lipSyncRuntime"))
66
+ lipsync-agency (lipsync/create-lipsync-agency
67
+ (society/agency-config config agency-society "lipSync")
68
+ lipsync-runtime)
69
+ prosodic-agency (prosodic/create-prosodic-agency
70
+ (society/agency-config config agency-society "prosodic"))
71
+ unsubscribers (atom [])
72
+ disposed? (atom false)]
73
+ (letfn [(track! [unsubscribe]
74
+ (swap! unsubscribers conj unsubscribe))
75
+
76
+ (agency-by-name [name]
77
+ (case name
78
+ "animation" animation-agency
79
+ "blink" blink-agency
80
+ "gaze" gaze-agency
81
+ "eyeHeadTracking" eye-head-agency
82
+ "gesture" gesture-agency
83
+ "cameraContext" camera-context-agency
84
+ "transcription" transcription-agency
85
+ "conversation" conversation-agency
86
+ "tts" tts-agency
87
+ "lipSync" lipsync-agency
88
+ "prosodic" prosodic-agency
89
+ nil))
90
+
91
+ (dispatch-to! [name command]
92
+ (when-let [target (agency-by-name name)]
93
+ (when-let [dispatch (aget target "dispatch")]
94
+ (.call dispatch target (clj->js command))
95
+ true)))
96
+
97
+ (route-command! [from event to command]
98
+ (when-let [route (society/route-context agency-society from event to)]
99
+ (dispatch-to! to (assoc command :society route))))
100
+
101
+ (schedule-animation! [event]
102
+ (route-command! (:agency event)
103
+ event
104
+ "animation"
105
+ {:type "scheduleSnippet"
106
+ :sourceAgency (:agency event)
107
+ :snippet (:snippet event)
108
+ :options (:options event)}))
109
+
110
+ (remove-animation! [event]
111
+ (route-command! (:agency event)
112
+ event
113
+ "animation"
114
+ {:type "removeSnippet"
115
+ :sourceAgency (:agency event)
116
+ :name (:name event)}))
117
+
118
+ (seek-animation! [event]
119
+ (route-command! (:agency event)
120
+ event
121
+ "animation"
122
+ {:type "seekSnippet"
123
+ :sourceAgency (:agency event)
124
+ :name (:name event)
125
+ :offsetSec (:offsetSec event)}))
126
+
127
+ (dispatch! [message]
128
+ (when-not @disposed?
129
+ (let [payload (js->clj message :keywordize-keys true)]
130
+ ;; Input is still observable for tests/workers, but the write
131
+ ;; path routes to agencies directly inside Polymer.
132
+ (emit-input {:type "command"
133
+ :agency (:agency payload)
134
+ :message payload})
135
+ (let [name (:agency payload)]
136
+ (cond
137
+ (nil? (agency-by-name name))
138
+ (emit-event {:type "error"
139
+ :agency (or name "unknown")
140
+ :message "Unknown Polymer agency"})
141
+
142
+ (not (society/agency-enabled? agency-society name))
143
+ (emit-event {:type "agencyDisabled"
144
+ :agency name
145
+ :message "Polymer agency is disabled by agencySociety"})
146
+
147
+ :else
148
+ (dispatch-to! name (:command payload)))))))
149
+
150
+ (snapshot! []
151
+ (clj->js {:blink (js->clj (.snapshot ^js blink-agency) :keywordize-keys true)
152
+ :gaze (js->clj (.snapshot ^js gaze-agency) :keywordize-keys true)
153
+ :eyeHeadTracking (js->clj (.snapshot ^js eye-head-agency) :keywordize-keys true)
154
+ :gesture (js->clj (.snapshot ^js gesture-agency) :keywordize-keys true)
155
+ :cameraContext (js->clj (.snapshot ^js camera-context-agency) :keywordize-keys true)
156
+ :transcription (js->clj (.snapshot ^js transcription-agency) :keywordize-keys true)
157
+ :conversation (js->clj (.snapshot ^js conversation-agency) :keywordize-keys true)
158
+ :tts (js->clj (.snapshot ^js tts-agency) :keywordize-keys true)
159
+ :lipSync (js->clj (.snapshot ^js lipsync-agency) :keywordize-keys true)
160
+ :prosodic (js->clj (.snapshot ^js prosodic-agency) :keywordize-keys true)
161
+ :animation (js->clj (.snapshot ^js animation-agency) :keywordize-keys true)
162
+ :agencySociety agency-society}))
163
+
164
+ (route-blink-event! [event]
165
+ (case (:type event)
166
+ "animation.requestScheduleSnippet"
167
+ (schedule-animation! event)
168
+
169
+ "signal"
170
+ (when (and (= "blink" (:agency event))
171
+ (= "blink-fast" (:signal event)))
172
+ (route-command! "blink"
173
+ event
174
+ "prosodic"
175
+ {:type "blinkFast"
176
+ :sourceAgency "blink"
177
+ :plan (:plan event)}))
178
+
179
+ nil))
180
+
181
+ (route-gaze-event! [event]
182
+ (case (:type event)
183
+ ("eyeHeadTracking.requestGaze"
184
+ "eyeHeadTracking.requestReset"
185
+ "eyeHeadTracking.requestCancel")
186
+ (route-command! "gaze" event "eyeHeadTracking" event)
187
+
188
+ nil))
189
+
190
+ (route-eye-head-event! [event]
191
+ (case (:type event)
192
+ "animation.requestScheduleSnippet"
193
+ (schedule-animation! event)
194
+
195
+ "animation.requestRemoveSnippet"
196
+ (remove-animation! event)
197
+
198
+ nil))
199
+
200
+ (route-gesture-event! [event]
201
+ (case (:type event)
202
+ "animation.requestScheduleSnippet"
203
+ (schedule-animation! event)
204
+
205
+ "animation.requestRemoveSnippet"
206
+ (remove-animation! event)
207
+
208
+ nil))
209
+
210
+ (route-camera-context-event! [event]
211
+ (case (:type event)
212
+ "camera.fact"
213
+ (route-command! "cameraContext" event "gaze" event)
214
+
215
+ "camera.stale"
216
+ (route-command! "cameraContext" event "gaze" event)
217
+
218
+ nil))
219
+
220
+ (route-transcription-event! [event]
221
+ (case (:type event)
222
+ "transcription.final"
223
+ (route-command! "transcription"
224
+ event
225
+ "conversation"
226
+ {:type "transcript.final"
227
+ :text (:text event)
228
+ :source "transcription"
229
+ :confidence (:confidence event)
230
+ :sequence (:sequence event)
231
+ :sessionId (:sessionId event)
232
+ :at (:at event)})
233
+
234
+ "transcription.interruption"
235
+ (route-command! "transcription"
236
+ event
237
+ "conversation"
238
+ {:type "interrupt"
239
+ :reason "transcription-interruption"
240
+ :text (:text event)
241
+ :source "transcription"})
242
+
243
+ "transcription.audioInterruption"
244
+ (route-command! "transcription"
245
+ event
246
+ "conversation"
247
+ {:type "interrupt"
248
+ :reason "audio-interruption"
249
+ :event event
250
+ :source "transcription"})
251
+
252
+ nil))
253
+
254
+ (route-conversation-event! [event]
255
+ ;; Router only: forward conversation facts/requests. Gaze and
256
+ ;; Prosodic own local policy through their planners/schedulers.
257
+ (case (:type event)
258
+ ("conversation.userUtterance"
259
+ "conversation.agentUtterance")
260
+ (let [fact (assoc event :sourceAgency "conversation")]
261
+ (route-command! "conversation" event "prosodic" fact)
262
+ (route-command! "conversation" event "gaze" fact))
263
+
264
+ "conversation.requestResponse"
265
+ (route-command! "conversation"
266
+ event
267
+ "prosodic"
268
+ (assoc event :sourceAgency "conversation"))
269
+
270
+ "tts.requestSpeak"
271
+ (route-command! "conversation" event "tts" (:command event))
272
+
273
+ "conversation.cancelRequested"
274
+ (let [cancel (assoc event :sourceAgency "conversation")]
275
+ (route-command! "conversation" event "prosodic" cancel)
276
+ (route-command! "conversation" event "gaze" cancel)
277
+ (route-command! "conversation"
278
+ event
279
+ "tts"
280
+ {:type "stop"
281
+ :reason (:reason event)}))
282
+
283
+ nil))
284
+
285
+ (route-tts-event! [event]
286
+ (case (:type event)
287
+ "lipSync.command"
288
+ (route-command! "tts" event "lipSync" (:command event))
289
+
290
+ "ttsStatusChanged"
291
+ (let [status (get-in event [:state :status])]
292
+ (route-command! "tts"
293
+ event
294
+ "conversation"
295
+ {:type "tts.status"
296
+ :status status})
297
+ (route-command! "tts"
298
+ event
299
+ "transcription"
300
+ {:type "tts.status"
301
+ :status status
302
+ :speaking (get-in event [:state :speaking])
303
+ :state (:state event)}))
304
+
305
+ "ttsSpeechStarted"
306
+ (do
307
+ (route-command! "tts"
308
+ event
309
+ "prosodic"
310
+ {:type "speechStarted"
311
+ :sourceAgency "tts"
312
+ :name (:name event)
313
+ :engine (:engine event)})
314
+ (route-command! "tts" event "transcription" event)
315
+ (route-command! "tts"
316
+ event
317
+ "conversation"
318
+ {:type "tts.status"
319
+ :status "speaking"}))
320
+
321
+ "ttsWordBoundary"
322
+ (route-command! "tts"
323
+ event
324
+ "prosodic"
325
+ {:type "wordBoundary"
326
+ :sourceAgency "tts"
327
+ :word (:word event)
328
+ :wordIndex (:wordIndex event)
329
+ :observedElapsedSec (:observedElapsedSec event)
330
+ :hostElapsedSec (:hostElapsedSec event)})
331
+
332
+ "ttsSpeechStopped"
333
+ (do
334
+ (route-command! "tts"
335
+ event
336
+ "prosodic"
337
+ {:type "speechStopped"
338
+ :sourceAgency "tts"
339
+ :reason (:reason event)})
340
+ (route-command! "tts" event "transcription" event)
341
+ (route-command! "tts"
342
+ event
343
+ "conversation"
344
+ {:type "tts.status"
345
+ :status "idle"}))
346
+
347
+ "ttsSpeechEnded"
348
+ (do
349
+ (route-command! "tts"
350
+ event
351
+ "prosodic"
352
+ {:type "speechStopped"
353
+ :sourceAgency "tts"
354
+ :reason "completed"})
355
+ (route-command! "tts" event "transcription" event)
356
+ (route-command! "tts"
357
+ event
358
+ "conversation"
359
+ {:type "tts.status"
360
+ :status "idle"}))
361
+
362
+ nil))
363
+
364
+ (route-lipsync-event! [event]
365
+ (case (:type event)
366
+ "animation.requestScheduleSnippet"
367
+ (schedule-animation! event)
368
+
369
+ "animation.requestRemoveSnippet"
370
+ (remove-animation! event)
371
+
372
+ "animation.requestSeekSnippet"
373
+ (seek-animation! event)
374
+
375
+ nil))
376
+
377
+ (route-prosodic-event! [event]
378
+ (case (:type event)
379
+ "animation.requestScheduleSnippet"
380
+ (schedule-animation! event)
381
+
382
+ "animation.requestRemoveSnippet"
383
+ (remove-animation! event)
384
+
385
+ nil))]
386
+ ;; Fan-in agency events to one character-level event stream for tests,
387
+ ;; workers, and future non-React observers. LoomLarge does not need to
388
+ ;; consume this stream for Polymer animation playback.
389
+ (track! (.subscribeEvents ^js animation-agency
390
+ #(emit-event (js->clj % :keywordize-keys true))))
391
+ (track! (.subscribeEvents ^js blink-agency
392
+ (fn [event]
393
+ (let [payload (js->clj event :keywordize-keys true)]
394
+ (route-blink-event! payload)
395
+ (emit-event payload)))))
396
+ (track! (.subscribeEvents ^js gaze-agency
397
+ (fn [event]
398
+ (let [payload (js->clj event :keywordize-keys true)]
399
+ (route-gaze-event! payload)
400
+ (emit-event payload)))))
401
+ (track! (.subscribeEvents ^js eye-head-agency
402
+ (fn [event]
403
+ (let [payload (js->clj event :keywordize-keys true)]
404
+ (route-eye-head-event! payload)
405
+ (emit-event payload)))))
406
+ (track! (.subscribeEvents ^js gesture-agency
407
+ (fn [event]
408
+ (let [payload (js->clj event :keywordize-keys true)]
409
+ (route-gesture-event! payload)
410
+ (emit-event payload)))))
411
+ (track! (.subscribeEvents ^js camera-context-agency
412
+ (fn [event]
413
+ (let [payload (js->clj event :keywordize-keys true)]
414
+ (route-camera-context-event! payload)
415
+ (emit-event payload)))))
416
+ (track! (.subscribeEvents ^js transcription-agency
417
+ (fn [event]
418
+ (let [payload (js->clj event :keywordize-keys true)]
419
+ (route-transcription-event! payload)
420
+ (emit-event payload)))))
421
+ (track! (.subscribeEvents ^js conversation-agency
422
+ (fn [event]
423
+ (let [payload (js->clj event :keywordize-keys true)]
424
+ (route-conversation-event! payload)
425
+ (emit-event payload)))))
426
+ (track! (.subscribeEvents ^js tts-agency
427
+ (fn [event]
428
+ (let [payload (js->clj event :keywordize-keys true)]
429
+ (route-tts-event! payload)
430
+ (emit-event payload)))))
431
+ (track! (.subscribeEvents ^js lipsync-agency
432
+ (fn [event]
433
+ (let [payload (js->clj event :keywordize-keys true)]
434
+ (route-lipsync-event! payload)
435
+ (emit-event payload)))))
436
+ (track! (.subscribeEvents ^js prosodic-agency
437
+ (fn [event]
438
+ (let [payload (js->clj event :keywordize-keys true)]
439
+ (route-prosodic-event! payload)
440
+ (emit-event payload)))))
441
+ #js {:dispatch dispatch!
442
+ :input (stream/writable-port input-stream dispatch!)
443
+ :events (stream/readable-port event-stream)
444
+ :agencySociety (clj->js agency-society)
445
+ :snapshot snapshot!
446
+ :agency (fn [name]
447
+ (when (society/agency-enabled? agency-society name)
448
+ (agency-by-name name)))
449
+ :subscribeInput (fn [listener] ((:subscribe input-stream) listener))
450
+ :subscribeEvents (fn [listener] ((:subscribe event-stream) listener))
451
+ :subscribe (fn [listener] ((:subscribe event-stream) listener))
452
+ :subscribeStatus (fn [listener] ((:subscribe event-stream) listener))
453
+ :dispose (fn []
454
+ (when-not @disposed?
455
+ (reset! disposed? true)
456
+ (doseq [unsubscribe @unsubscribers]
457
+ (unsubscribe))
458
+ (reset! unsubscribers [])
459
+ (.dispose ^js blink-agency)
460
+ (.dispose ^js gaze-agency)
461
+ (.dispose ^js eye-head-agency)
462
+ (.dispose ^js gesture-agency)
463
+ (.dispose ^js camera-context-agency)
464
+ (.dispose ^js transcription-agency)
465
+ (.dispose ^js conversation-agency)
466
+ (.dispose ^js tts-agency)
467
+ (.dispose ^js lipsync-agency)
468
+ (.dispose ^js prosodic-agency)
469
+ (.dispose ^js animation-agency)
470
+ ((:dispose input-stream))
471
+ ((:dispose event-stream))))})))
@@ -0,0 +1,83 @@
1
+ (ns polymer.conversation.agency
2
+ (:require [polymer.conversation.domain :as domain]
3
+ [polymer.conversation.effector :as effector]
4
+ [polymer.conversation.planner :as planner]
5
+ [polymer.conversation.scheduler :as scheduler]
6
+ [polymer.conversation.state :as state]
7
+ [polymer.stream :as stream]))
8
+
9
+ ;; Conversation is a peer agency in the Polymer character society. It accepts
10
+ ;; transcript, response, and interruption facts as data; plans local turn-taking
11
+ ;; work; schedules those actions; then emits requests/facts that TTS, Gaze,
12
+ ;; Prosodic, and other agencies can decide how to handle.
13
+
14
+ (defn now-ms
15
+ []
16
+ (.now js/Date))
17
+
18
+ (defn js-command
19
+ [type value]
20
+ #js {:type type :value value})
21
+
22
+ (defn create-conversation-agency
23
+ [config]
24
+ (let [provider (if (map? config)
25
+ (or (:provider config)
26
+ (:conversationProvider config))
27
+ (when config
28
+ (or (aget config "provider")
29
+ (aget config "conversationProvider"))))
30
+ config-map (domain/data-map config)
31
+ state-config (dissoc config-map :provider :conversationProvider)
32
+ input-stream (stream/create-stream)
33
+ event-stream (stream/create-stream)
34
+ state-atom (atom (state/config->state state-config))
35
+ disposed? (atom false)
36
+ emit-input (:emit input-stream)
37
+ emit-event (:emit event-stream)
38
+ scheduler-atom (atom nil)]
39
+ (letfn [(dispatch! [command]
40
+ (when-not @disposed?
41
+ (let [payload (domain/data-map command)
42
+ plan (planner/plan-command payload @state-atom (now-ms))]
43
+ ;; Input is observable for workers/tests and for future
44
+ ;; diagnostics. It is not host application state and should
45
+ ;; not be mirrored into a UI on every agency message.
46
+ (emit-input {:type "command"
47
+ :agency "conversation"
48
+ :command payload})
49
+ (when-let [agency-scheduler @scheduler-atom]
50
+ ((:schedule agency-scheduler) payload plan)))))
51
+ (dispatch-type! [type]
52
+ (dispatch! #js {:type type}))]
53
+ (let [agency-scheduler (scheduler/create-scheduler
54
+ {:state-atom state-atom
55
+ :emit-event emit-event
56
+ :dispatch-command dispatch!
57
+ :provider-effector
58
+ (effector/create-provider-effector provider)})]
59
+ (reset! scheduler-atom agency-scheduler)
60
+ #js {:dispatch dispatch!
61
+ :snapshot (fn [] (state/visible-state @state-atom))
62
+ :input (stream/writable-port input-stream dispatch!)
63
+ :events (stream/readable-port event-stream)
64
+ :subscribeInput (fn [listener] ((:subscribe input-stream) listener))
65
+ :subscribeEvents (fn [listener] ((:subscribe event-stream) listener))
66
+ :subscribe (fn [listener] ((:subscribe event-stream) listener))
67
+ :subscribeStatus (fn [listener] ((:subscribe event-stream) listener))
68
+ :start (fn [] (dispatch-type! "start"))
69
+ :stop (fn [] (dispatch-type! "stop"))
70
+ :reset (fn [] (dispatch-type! "reset"))
71
+ :interrupt (fn
72
+ ([] (dispatch-type! "interrupt"))
73
+ ([reason] (dispatch! #js {:type "interrupt" :reason reason})))
74
+ :setAutoRespond (fn [value]
75
+ (dispatch! #js {:type "configure"
76
+ :config #js {:autoRespond value}}))
77
+ :queue (fn [] (clj->js ((:queue agency-scheduler))))
78
+ :dispose (fn []
79
+ (when-not @disposed?
80
+ (reset! disposed? true)
81
+ ((:dispose agency-scheduler))
82
+ ((:dispose input-stream))
83
+ ((:dispose event-stream))))}))))
@@ -0,0 +1,35 @@
1
+ (ns polymer.conversation.domain
2
+ (:require [clojure.string :as str]))
3
+
4
+ ;; Pure Conversation transforms live here so command normalization is shared by
5
+ ;; state, planner, and the JS-facing agency boundary.
6
+
7
+ (defn data-map
8
+ [value]
9
+ (cond
10
+ (map? value) value
11
+ value (js->clj value :keywordize-keys true)
12
+ :else {}))
13
+
14
+ (defn clean-text
15
+ [value]
16
+ (let [text (str/trim (or value ""))]
17
+ (when (pos? (count text))
18
+ text)))
19
+
20
+ (defn text-or
21
+ [value fallback]
22
+ (or (clean-text value) fallback))
23
+
24
+ (defn user-text
25
+ [command]
26
+ (clean-text (or (:text command)
27
+ (:transcript command)
28
+ (:utterance command))))
29
+
30
+ (defn agent-text
31
+ [command]
32
+ (clean-text (or (:agentText command)
33
+ (:responseText command)
34
+ (:text command)
35
+ (:utterance command))))