sessionvoice 0.3.2__tar.gz → 0.4.0__tar.gz

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 (345) hide show
  1. {sessionvoice-0.3.2/src/sessionvoice.egg-info → sessionvoice-0.4.0}/PKG-INFO +95 -37
  2. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/README.md +94 -36
  3. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/pyproject.toml +4 -3
  4. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_keeper/call-ears +38 -5
  5. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_keeper/ears-keeper.sh +94 -40
  6. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_keeper/ears-mail.py +650 -42
  7. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_keeper/ears-sounds.py +60 -35
  8. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_keeper/mouth-sounds.py +79 -33
  9. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_keeper/phone_roots.py +16 -2
  10. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_settings/ears.schema.json +15 -1
  11. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_settings/mouth.schema.json +104 -3
  12. sessionvoice-0.4.0/src/ears/_settings/talk.schema.json +20 -0
  13. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_speakers/speaker-sidecar2.py +33 -14
  14. sessionvoice-0.4.0/src/ears/boxes.py +166 -0
  15. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/cli.py +13 -0
  16. sessionvoice-0.4.0/src/ears/codestamp.py +84 -0
  17. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/heard.py +55 -14
  18. sessionvoice-0.4.0/src/ears/heardbox.py +105 -0
  19. sessionvoice-0.4.0/src/ears/home.py +1115 -0
  20. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/loop.py +8 -3
  21. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/sources.py +23 -4
  22. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/maildir.py +12 -8
  23. sessionvoice-0.4.0/src/ears/mic_check.py +476 -0
  24. sessionvoice-0.4.0/src/ears/migrate.py +1299 -0
  25. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/recording.py +23 -9
  26. sessionvoice-0.4.0/src/ears/service_files.py +145 -0
  27. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/services.py +544 -96
  28. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/speakers.py +10 -0
  29. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/speech.py +10 -4
  30. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/vad.py +47 -10
  31. sessionvoice-0.4.0/src/ears/wake.py +445 -0
  32. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/__init__.py +328 -29
  33. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/__main__.py +170 -30
  34. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/audio.py +2 -1
  35. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/backends.py +39 -0
  36. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/bed.py +12 -2
  37. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/box.py +48 -7
  38. sessionvoice-0.4.0/src/mouth/fetch.py +440 -0
  39. sessionvoice-0.4.0/src/mouth/finder.py +180 -0
  40. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/hold.py +85 -3
  41. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/inbox.py +4 -2
  42. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/level.py +50 -3
  43. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/maillog.py +4 -2
  44. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/notice.py +219 -19
  45. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/output.py +6 -2
  46. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/player.py +11 -3
  47. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/reply.py +23 -15
  48. sessionvoice-0.4.0/src/mouth/sender.py +117 -0
  49. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/sidecar.py +86 -29
  50. sessionvoice-0.4.0/src/mouth/sourcekey.py +178 -0
  51. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/split.py +26 -12
  52. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/voices.py +229 -14
  53. {sessionvoice-0.3.2 → sessionvoice-0.4.0/src/sessionvoice.egg-info}/PKG-INFO +95 -37
  54. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/sessionvoice.egg-info/SOURCES.txt +49 -0
  55. sessionvoice-0.4.0/src/talk/apple-touch-icon.png +0 -0
  56. sessionvoice-0.4.0/src/talk/favicon.ico +0 -0
  57. sessionvoice-0.4.0/src/talk/icon-192.png +0 -0
  58. sessionvoice-0.4.0/src/talk/icon-512.png +0 -0
  59. sessionvoice-0.4.0/src/talk/icon-mask-512.png +0 -0
  60. sessionvoice-0.4.0/src/talk/icon.svg +1 -0
  61. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/talk/index.html +94 -33
  62. sessionvoice-0.4.0/src/talk/manifest.json +6 -0
  63. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/talk/server.py +253 -68
  64. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_arm_finds_heard_first.py +1 -1
  65. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_arm_help.py +3 -2
  66. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_audio_fence.py +1 -1
  67. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_call_sock_search.py +123 -0
  68. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_call_tap_help.py +3 -0
  69. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_call_tap_listen_off.py +3 -0
  70. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_call_tap_silero.py +3 -0
  71. sessionvoice-0.4.0/tests/test_codestamp.py +119 -0
  72. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_configuration_docs.py +61 -3
  73. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_conftest_order.py +1 -1
  74. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_ears_keys.py +11 -0
  75. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_ears_mail.py +525 -1
  76. sessionvoice-0.4.0/tests/test_ears_mail_alert.py +460 -0
  77. sessionvoice-0.4.0/tests/test_ears_mail_lookup.py +159 -0
  78. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_ears_settings_schema.py +18 -2
  79. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_ears_sounds_call.py +37 -0
  80. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_ears_wake.py +96 -1
  81. sessionvoice-0.4.0/tests/test_echo_own_line.py +140 -0
  82. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_export_ignore.py +49 -2
  83. sessionvoice-0.4.0/tests/test_heardbox.py +139 -0
  84. sessionvoice-0.4.0/tests/test_home.py +1085 -0
  85. sessionvoice-0.4.0/tests/test_install_fetch_retry.py +265 -0
  86. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_install_silero_pin.py +8 -5
  87. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_instrumental_docs.py +30 -17
  88. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_adopt.py +151 -5
  89. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_again.py +26 -0
  90. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_defaults.py +42 -13
  91. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_listen_off.py +4 -3
  92. sessionvoice-0.4.0/tests/test_keeper_mail_alert.py +354 -0
  93. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_room.py +2 -1
  94. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_stop.py +3 -1
  95. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_loop.py +10 -0
  96. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_log_dir.py +13 -7
  97. sessionvoice-0.4.0/tests/test_mail_alert_twins.py +105 -0
  98. sessionvoice-0.4.0/tests/test_mail_fence.py +453 -0
  99. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_maildir_private.py +11 -3
  100. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_message_bin.py +6 -1
  101. sessionvoice-0.4.0/tests/test_mic_check.py +638 -0
  102. sessionvoice-0.4.0/tests/test_mic_check_docs.py +157 -0
  103. sessionvoice-0.4.0/tests/test_migrate.py +1130 -0
  104. sessionvoice-0.4.0/tests/test_mouth_sounds_defaults.py +66 -0
  105. sessionvoice-0.4.0/tests/test_mouth_thread_settle.py +56 -0
  106. sessionvoice-0.4.0/tests/test_nothing_born_outside.py +237 -0
  107. sessionvoice-0.4.0/tests/test_plists_after_migrate.py +338 -0
  108. sessionvoice-0.4.0/tests/test_pypi_dist.py +374 -0
  109. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_recording.py +9 -1
  110. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_say_again_e2e.py +1 -1
  111. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_scanlist.py +3 -2
  112. sessionvoice-0.4.0/tests/test_seat_home.py +745 -0
  113. sessionvoice-0.4.0/tests/test_service_files.py +396 -0
  114. sessionvoice-0.4.0/tests/test_service_files_not_ours.py +260 -0
  115. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_adopt_cold.py +2 -2
  116. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_install_checks.py +202 -21
  117. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_install_load_fail.py +9 -9
  118. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_install_preflight.py +121 -3
  119. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_label_move.py +1 -1
  120. sessionvoice-0.4.0/tests/test_services_listen_flag.py +168 -0
  121. sessionvoice-0.4.0/tests/test_services_mic_check.py +444 -0
  122. sessionvoice-0.4.0/tests/test_services_no_launchd.py +105 -0
  123. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_one_keeper.py +1 -1
  124. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_sounds.py +7 -5
  125. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_speakers.py +29 -8
  126. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_speakers_checks.py +7 -7
  127. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_speech_cache.py +1 -1
  128. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_stt_model.py +1 -1
  129. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_talk.py +154 -0
  130. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_settings_in_wheel.py +2 -2
  131. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_settings_schema_refresh.py +3 -1
  132. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_settings_stranded.py +15 -1
  133. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_speakers_enrol.py +2 -2
  134. sessionvoice-0.4.0/tests/test_state_items.py +543 -0
  135. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_talk_cli.py +22 -3
  136. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_talk_generalise.py +53 -44
  137. sessionvoice-0.4.0/tests/test_talk_hangup.py +62 -0
  138. sessionvoice-0.4.0/tests/test_talk_hearers.py +56 -0
  139. sessionvoice-0.4.0/tests/test_talk_helpers.py +114 -0
  140. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_talk_host_check.py +23 -24
  141. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_talk_pair.py +11 -17
  142. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_talk_prefix.py +24 -7
  143. sessionvoice-0.4.0/tests/test_talk_server.py +429 -0
  144. sessionvoice-0.4.0/tests/test_talk_tail_race.py +270 -0
  145. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_talk_token_gate.py +53 -36
  146. sessionvoice-0.4.0/tests/test_talk_uncrowd.py +133 -0
  147. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_talk_unix.py +16 -22
  148. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_waiter_shims.py +1 -1
  149. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_waiters.py +2 -2
  150. sessionvoice-0.4.0/tests/test_wake_rules_live.py +352 -0
  151. sessionvoice-0.4.0/tests/test_wake_self.py +259 -0
  152. sessionvoice-0.4.0/tests/test_wake_spoken_forms.py +249 -0
  153. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_wheel_build.py +1 -1
  154. sessionvoice-0.3.2/src/ears/home.py +0 -307
  155. sessionvoice-0.3.2/src/ears/wake.py +0 -261
  156. sessionvoice-0.3.2/src/talk/manifest.json +0 -1
  157. sessionvoice-0.3.2/tests/test_home.py +0 -355
  158. sessionvoice-0.3.2/tests/test_talk_server.py +0 -198
  159. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/LICENSE +0 -0
  160. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/setup.cfg +0 -0
  161. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/__init__.py +0 -0
  162. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_keeper/held_stream.py +0 -0
  163. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_keeper/input-watch.swift +0 -0
  164. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_settings/ears-sounds.schema.json +0 -0
  165. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_speakers/cluster-room.py +0 -0
  166. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_speakers/make-refs.py +0 -0
  167. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_speakers/requirements.lock +0 -0
  168. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_speakers/run.sh +0 -0
  169. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_speech/build-constraints.txt +0 -0
  170. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_speech/requirements.lock +0 -0
  171. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/backends/__init__.py +0 -0
  172. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/backends/apple.py +0 -0
  173. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/backends/nemotron.py +0 -0
  174. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/backends/proc.py +0 -0
  175. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/backends/whisper.py +0 -0
  176. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/callrec.py +0 -0
  177. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/envnames.py +0 -0
  178. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/eval.py +0 -0
  179. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/eval_mlx.py +0 -0
  180. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/events.py +0 -0
  181. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/extras.py +0 -0
  182. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/heard_wait.py +0 -0
  183. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/keys.py +0 -0
  184. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/lag.py +0 -0
  185. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/__init__.py +0 -0
  186. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/__main__.py +0 -0
  187. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/aged.py +0 -0
  188. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/chunker.py +0 -0
  189. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/detector.py +0 -0
  190. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/missed.py +0 -0
  191. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/silero.py +0 -0
  192. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/sink.py +0 -0
  193. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/speakers.py +0 -0
  194. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/streaming.py +0 -0
  195. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/stt.py +0 -0
  196. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/turnaudio.py +0 -0
  197. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/watchdog.py +0 -0
  198. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/message_bin.py +0 -0
  199. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/operator_config.py +0 -0
  200. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/previews.py +0 -0
  201. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/redecode.py +0 -0
  202. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/serve.py +0 -0
  203. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/smartturn.py +0 -0
  204. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/turns.py +0 -0
  205. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/_separator/requirements.lock +0 -0
  206. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/_separator/run.py +0 -0
  207. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/autosplit.py +0 -0
  208. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/bargeswitch.py +0 -0
  209. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/canplay.py +0 -0
  210. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/coreaudio.py +0 -0
  211. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/devices.py +0 -0
  212. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/duckswitch.py +0 -0
  213. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/instrumental.py +0 -0
  214. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/mpv.py +0 -0
  215. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/onset.py +0 -0
  216. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/paths.py +0 -0
  217. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/resolver.py +0 -0
  218. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/separator.py +0 -0
  219. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/splitwait.py +0 -0
  220. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/stems.py +0 -0
  221. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/timeline.py +0 -0
  222. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/sessionvoice.egg-info/dependency_links.txt +0 -0
  223. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/sessionvoice.egg-info/entry_points.txt +0 -0
  224. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/sessionvoice.egg-info/requires.txt +0 -0
  225. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/sessionvoice.egg-info/top_level.txt +0 -0
  226. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/talk/__init__.py +0 -0
  227. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/talk/__main__.py +0 -0
  228. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/talk/cli.py +0 -0
  229. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/talk/token_store.py +0 -0
  230. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/talk/web.py +0 -0
  231. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_anchor.py +0 -0
  232. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_apple_assets_why_not.py +0 -0
  233. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_audio_devices_schema.py +0 -0
  234. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_bin_tool_fallback.py +0 -0
  235. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_callrec.py +0 -0
  236. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_capture_mislabel.py +0 -0
  237. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_changelog_union.py +0 -0
  238. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_child_pytest.py +0 -0
  239. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_clock_check.py +0 -0
  240. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_conftest_ffmpeg.py +0 -0
  241. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_dependencies_docs.py +0 -0
  242. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_docs_layout.py +0 -0
  243. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_ears_no_backend.py +0 -0
  244. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_ears_serve_cli.py +0 -0
  245. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_ears_sounds_stream.py +0 -0
  246. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_ears_stt_key.py +0 -0
  247. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_echo_cut.py +0 -0
  248. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_echo_speaker.py +0 -0
  249. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_emitted_neutral.py +0 -0
  250. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_envnames.py +0 -0
  251. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_eval.py +0 -0
  252. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_extras.py +0 -0
  253. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_github_links.py +0 -0
  254. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_heard_contract.py +0 -0
  255. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_heard_cursor_by.py +0 -0
  256. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_heard_partials_only.py +0 -0
  257. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_heard_wait.py +0 -0
  258. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_heard_writer.py +0 -0
  259. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_install_apple_assets.py +0 -0
  260. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_install_apple_optin.py +0 -0
  261. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_beat.py +0 -0
  262. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_calls.py +0 -0
  263. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_deaf.py +0 -0
  264. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_fallback.py +0 -0
  265. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_in_wheel.py +0 -0
  266. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_lag.py +0 -0
  267. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_lid.py +0 -0
  268. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_missed_reply.py +0 -0
  269. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_model.py +0 -0
  270. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_speakers_deaf.py +0 -0
  271. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_stt_deaf.py +0 -0
  272. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_swiftc_guard.py +0 -0
  273. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_zeros.py +0 -0
  274. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_launchd_no_machine_paths.py +0 -0
  275. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_launchd_templates.py +0 -0
  276. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_aged.py +0 -0
  277. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_audio_stamp.py +0 -0
  278. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_call_source.py +0 -0
  279. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_clean_exit.py +0 -0
  280. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_hygiene.py +0 -0
  281. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_missed_reply.py +0 -0
  282. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_parts.py +0 -0
  283. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_redecode.py +0 -0
  284. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_silero.py +0 -0
  285. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_speaker_agent.py +0 -0
  286. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_speaker_split.py +0 -0
  287. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_stack_dump.py +0 -0
  288. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_stream_feed.py +0 -0
  289. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_streaming.py +0 -0
  290. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_stt_follow.py +0 -0
  291. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_stt_waits.py +0 -0
  292. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_turn_speaker.py +0 -0
  293. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_vad_edges.py +0 -0
  294. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_watchdog.py +0 -0
  295. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_wedge_scenarios.py +0 -0
  296. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_live_home_guard.py +0 -0
  297. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_lock_wheels.py +0 -0
  298. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_marketplace_description.py +0 -0
  299. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_mouth_notice_fence.py +0 -0
  300. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_mouth_schema_plain.py +0 -0
  301. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_mouth_sounds_scoop_by.py +0 -0
  302. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_mouth_sounds_wake.py +0 -0
  303. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_name_hint_bench.py +0 -0
  304. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_no_real_times.py +0 -0
  305. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_no_time_of_day_stamps.py +0 -0
  306. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_no_tracker_ids.py +0 -0
  307. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_no_work_hour_stamps.py +0 -0
  308. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_operator_config.py +0 -0
  309. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_persona_defaults.py +0 -0
  310. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_plugin_install_doc.py +0 -0
  311. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_readme_links.py +0 -0
  312. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_recording_retention.py +0 -0
  313. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_release_script.py +0 -0
  314. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_sendmail_tripwire.py +0 -0
  315. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_separator_runner.py +0 -0
  316. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_service_build_constraints.py +0 -0
  317. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_service_hash_pins.py +0 -0
  318. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_bootstrap.py +0 -0
  319. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_dry_run.py +0 -0
  320. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_install_golden.py +0 -0
  321. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_kit_labels.py +0 -0
  322. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_legacy_plists.py +0 -0
  323. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_phone_only.py +0 -0
  324. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_prefetch.py +0 -0
  325. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_speech.py +0 -0
  326. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_stt_row.py +0 -0
  327. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_weights.py +0 -0
  328. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_settings_dir.py +0 -0
  329. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_speakers.py +0 -0
  330. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_speakers_call.py +0 -0
  331. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_speakers_follow.py +0 -0
  332. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_speakers_in_package.py +0 -0
  333. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_speakers_name.py +0 -0
  334. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_speakers_parity.py +0 -0
  335. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_speech.py +0 -0
  336. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_stt_name_hint.py +0 -0
  337. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_system_python_scripts.py +0 -0
  338. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_talk_kit.py +0 -0
  339. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_talk_launchd.py +0 -0
  340. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_talk_page_pair.py +0 -0
  341. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_talk_token.py +0 -0
  342. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_talk_web_room.py +0 -0
  343. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_turn_audio.py +0 -0
  344. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_turns.py +0 -0
  345. {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_version_sync.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sessionvoice
3
- Version: 0.3.2
3
+ Version: 0.4.0
4
4
  Summary: One listening engine for coding agents and the Delta bot: Apple / Nemotron / whisper -> one event stream -> VAD -> turns
5
5
  Author-email: Mike Bailey <mike@sessionkit.ai>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -72,7 +72,7 @@ streams transcription, and decides when the speaker has finished.
72
72
  for a while.
73
73
  - **Barge-in:** on headphones or a call, talk over the assistant and its voice
74
74
  dips at once; a couple of your own words, or "stop", cut it off
75
- ([barge-in](docs/barge-in.md)).
75
+ ([barge-in](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/barge-in.md)).
76
76
 
77
77
  ## Requirements
78
78
 
@@ -82,19 +82,21 @@ streams transcription, and decides when the speaker has finished.
82
82
  - [ ] Windows
83
83
 
84
84
  What sessionvoice depends on, what needs each one, and how to upgrade it:
85
- [dependencies](docs/dependencies.md).
85
+ [dependencies](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/dependencies.md).
86
86
 
87
87
  ## Components
88
88
 
89
89
  The engine above is the core. Around it, these parts make it an always-on
90
- pair of ears on a Mac (depth in [docs/architecture.md](docs/architecture.md)):
90
+ pair of ears on a Mac (depth in [docs/architecture.md](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/architecture.md)):
91
91
 
92
92
  - **Capture** (`src/ears/listen`): `ears.listen capture` reads one input
93
93
  device and writes every partial, turn and heartbeat to the **heard log**
94
94
  (`src/ears/heard.py`):
95
- `<home>/logs/conversations/heard_YYYY-MM-DD.jsonl`, where `<home>` is
96
- `~/.voicemode` on a host that already keeps it there, else
97
- `~/.sessionvoice` ([docs/logs.md](docs/logs.md#sessionvoices-home)).
95
+ `<root>/logs/conversations/heard_YYYY-MM-DD.jsonl`, where `<root>` is the
96
+ state root: `~/.sessionkit/voice/state` on a new host, or the old
97
+ `~/.sessionvoice` or `~/.voicemode` on a host that already keeps its heard
98
+ log there ([docs/logs.md](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/logs.md#the-state-root)).
99
+ `ears home state-dir` prints it.
98
100
  On a fresh host that also runs VoiceMode's `converse` as a heard guest,
99
101
  set `SESSIONVOICE_HOME=~/.voicemode` (or `VOICEMODE_BASE_DIR`), so that
100
102
  both write one log.
@@ -128,9 +130,11 @@ pair of ears on a Mac (depth in [docs/architecture.md](docs/architecture.md)):
128
130
  - **Sounds** (`keeper/ears-sounds.py`, launchd `dev.sessionkit.voice.ears-sounds`):
129
131
  a short sound in the user's ears when the ears log an event, so they know
130
132
  they were heard. Its partner, `keeper/mouth-sounds.py`
131
- (`dev.sessionkit.voice.mouth-sounds`), plays the mouth's cues. Opt-in:
132
- `ears services install --only sounds` loads both, from an installed
133
- package or a checkout; a bare install never does.
133
+ (`dev.sessionkit.voice.mouth-sounds`), plays the mouth's cues. A bare
134
+ `ears services install` loads both, from an installed package or a
135
+ checkout. From the first turn the ears' turn and partial ticks are on
136
+ (Settings turns them off), and so are the mouth's five cues
137
+ (`mouth-sounds.json` in the settings folder turns each off).
134
138
  - **Turn mail** (`keeper/ears-mail.py`, launchd `dev.sessionkit.voice.ears-mail`):
135
139
  mails each of the user's finished turns To and From
136
140
  `heard+room@<host>`, whatever input device the capture is following, with
@@ -159,6 +163,12 @@ pair of ears on a Mac (depth in [docs/architecture.md](docs/architecture.md)):
159
163
  label is dropped: the remaining words get `--speaker` (else
160
164
  `unknown@<host>`), never the seat whose echo was cut, and they can be a
161
165
  reply to that seat. A label naming anyone else is kept.
166
+ `Ears-Addressed` lists the seats a turn names, as `<seat>@<host>`, wherever
167
+ the name falls in the turn, plus `everyone` for "hey everyone":
168
+ `Ears-Addressed: everyone, ivy@<host>`. A seat is named by its `names` in
169
+ the wake file. The header carries names only, never the turn's words, and a
170
+ turn that names nobody has no `Ears-Addressed`. It goes on the room mail
171
+ only. [docs/ears.md](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/ears.md) gives the rules.
162
172
  `Ears-Echo` says a mouth line heard back through the mic was cut out of
163
173
  the turn. On the speakers the mic hears the mouth, so a line's
164
174
  words are cut wherever they sit in the user's turn: at its start (up to `3 + 5
@@ -187,6 +197,25 @@ pair of ears on a Mac (depth in [docs/architecture.md](docs/architecture.md)):
187
197
  have opened within 2 s of the line beginning, unless the turn is mostly
188
198
  the line. On the `ts − (t1 − t0)` estimate, only the first two guards
189
199
  apply. Numerals (`9%` for "nine percent") still slip past the word match.
200
+ `Ears-Said` names each session's own line the turn came after, so the
201
+ pager can keep that session's window open (its linger):
202
+ `Ears-Said: <session>@<host>=<end>, ...`. `<session>` is the full session
203
+ id on the line's `said` row, `<host>` the keeper's, and `<end>` that
204
+ row's `ts`, as it was written. Each session gets one entry: its newest line
205
+ that finished at or before the turn's `Ears-Start`, within 600 s. It
206
+ carries spoken lines only: a line on `lane: file` (a music bed or a sound
207
+ file) is left out, so a bed that ends after a line leaves that line the
208
+ session's newest. A line with `interest: zero`, or one cut before it
209
+ played a word (`played_s` 0), is left out. A line the user barged into after part of it played counts,
210
+ at its cut. Entries are sorted, and the header holds names and times,
211
+ never a line's words. It goes on the device mail and the `--to seats`
212
+ mail, never on a cut notice or a call turn; with no entry there is no
213
+ header.
214
+ `Ears-Film: playing` marks a turn the keeper heard while a film was
215
+ playing (`--kodi`: the state that holds `reply` and `light`), so the
216
+ pager can tell film dialogue from the user. It is a fact about the turn,
217
+ with one value, on the device mail only; with no film playing, or no
218
+ `--kodi`, there is no header. Kodi is asked at most once per turn.
190
219
  - **Waiter** (`bin/heard-first`): an agent blocks on the heard log
191
220
  until a line is for it, by `--rule`: its name in a partial or a
192
221
  turn (`partial:REGEX`, `turn:REGEX`), a reply to its last line
@@ -213,7 +242,7 @@ pair of ears on a Mac (depth in [docs/architecture.md](docs/architecture.md)):
213
242
  - **Speaker names** (`speakers/`, opt-in:
214
243
  `ears services install --only speakers`): a side-car tags each heard line
215
244
  with who spoke, in `speakers_YYYY-MM-DD.jsonl` beside the heard log, and
216
- the waiters read it. See [speakers/README.md](speakers/README.md).
245
+ the waiters read it. See [speakers/README.md](https://github.com/sessionkit/sessionvoice/blob/HEAD/speakers/README.md).
217
246
 
218
247
  ## Configuration
219
248
 
@@ -229,21 +258,22 @@ Three JSON settings files, each reread with no restart:
229
258
  read only their own file.
230
259
 
231
260
  Environment variables win over single keys (`EARS_RECORD` over `record`,
232
- `MOUTH_DEVICE` over `output`). `SESSIONVOICE_HOME` moves the home that holds
233
- the heard log and each part's state; by default it is `~/.sessionvoice`, or
234
- `~/.voicemode` on a host that already keeps its heard log there. The launch
235
- agents are in `~/Library/LaunchAgents/`.
261
+ `MOUTH_DEVICE` over `output`). `SESSIONVOICE_HOME` moves the state root,
262
+ which holds the heard log and each part's state; by default it is
263
+ `~/.sessionkit/voice/state`, or the old `~/.sessionvoice` or `~/.voicemode` on
264
+ a host that already keeps its heard log there. The launch agents are in
265
+ `~/Library/LaunchAgents/`.
236
266
 
237
267
  Which files and which variables each part reads, and where each one lives:
238
- [docs/configuration.md](docs/configuration.md).
268
+ [docs/configuration.md](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/configuration.md).
239
269
  Every key, its type and its default:
240
- [docs/settings.md](docs/settings.md).
270
+ [docs/settings.md](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/settings.md).
241
271
 
242
272
  ## Logs
243
273
 
244
- - **The heard log:** `<home>/logs/conversations/heard_YYYY-MM-DD.jsonl`,
274
+ - **The heard log:** `<root>/logs/conversations/heard_YYYY-MM-DD.jsonl`,
245
275
  one JSON line for each thing the ears heard and the mouth said, where
246
- `<home>` is the home above. A new file starts each day.
276
+ `<root>` is the state root above (`ears home state-dir` prints it). A new file starts each day.
247
277
  - **The services' logs:** `~/.sessionkit/voice/log/` (`EARS_LOG_DIR` moves it).
248
278
  `ears-keeper.log` is what the keeper did (devices, restarts, calls, a
249
279
  deaf mic); `ears-keeper-capture.log` is the room capture's own output;
@@ -253,9 +283,9 @@ Every key, its type and its default:
253
283
 
254
284
  Follow one with `tail -F`, for example
255
285
  `tail -F ~/.sessionkit/voice/log/ears-keeper.log`, or today's heard log with
256
- `tail -F <home>/logs/conversations/heard_$(date +%F).jsonl`.
286
+ `tail -F "$(ears home state-dir)/logs/conversations/heard_$(date +%F).jsonl"`.
257
287
  Every log file and what each line means:
258
- [docs/logs.md](docs/logs.md).
288
+ [docs/logs.md](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/logs.md).
259
289
 
260
290
  - **Health:** `ears services status` prints one row per launch agent
261
291
  (`running, pid N`, `loaded, not running` or `not installed`; the
@@ -302,12 +332,13 @@ conversation either. On the host, run `talk pair` in a terminal and open the
302
332
  link it prints; `talk token --rotate` makes every browser pair again. Open talk
303
333
  at `http://127.0.0.1:<port>/`, never at `localhost`. The code is `src/talk/`.
304
334
  `ears services install --only talk` keeps it running as a launch agent
305
- (opt-in; a bare install never does).
335
+ (opt-in; a bare install never does); add `--allow-host
336
+ machine.example.ts.net` for a phone behind `tailscale serve`.
306
337
 
307
338
  In the kit's web page, Talk is a room: the kit's front door runs
308
339
  `talk serve --kit` on a unix socket and logs you in, so there is nothing to
309
340
  pair and you never run `--kit` by hand
310
- ([docs/talk.md](docs/talk.md#in-the-kits-web-page)).
341
+ ([docs/talk.md](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/talk.md#in-the-kits-web-page)).
311
342
 
312
343
  ## Install
313
344
 
@@ -321,8 +352,7 @@ import that needs one fails naming it, as `pip install 'sessionvoice[EXTRA]'`.
321
352
 
322
353
  uv venv && uv pip install -e '.[all]' # onnxruntime + mlx-audio
323
354
  ears install
324
- ears services install --agent NAME # the launch agents: mouth, keeper, mail
325
- ears services install --only sounds # opt-in: the ears and mouth sounds
355
+ ears services install --agent NAME # the launch agents: mouth, keeper, mail, sounds
326
356
  ears services install --only talk # opt-in: talk serve, the live view
327
357
 
328
358
  A phone-only Mac (for example one that runs a Delta bot, with no agent
@@ -332,7 +362,24 @@ an agent seat should be woken when you speak. Before it writes anything,
332
362
  install says how big the speech models are (about 2.9 GB, fetched once,
333
363
  after the speech server loads) and that the speech server runs as a login
334
364
  service; `--dry-run` prints the same line, and `--no-prefetch` leaves the
335
- download to the first call.
365
+ download to the first call. `--no-prefetch` does not cover the Silero VAD
366
+ model (2.3 MB): install still fetches a missing one, because the keeper
367
+ cannot hear without it.
368
+
369
+ A fresh install leaves the room's ear off. To turn it on now, and check the
370
+ microphone while you are at the Mac:
371
+
372
+ ears services install --agent NAME --listen
373
+
374
+ - Typing `--listen` is the consent: it writes `"listen": true` to `ears.json`
375
+ before the keeper loads.
376
+ - macOS asks to let the keeper's Python use the microphone: click Allow.
377
+ - Install waits up to 60 s for the keeper to hear something. If it does not,
378
+ one line names System Settings > Privacy & Security > Microphone and the
379
+ app, and install exits 2
380
+ ([troubleshooting](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/troubleshooting.md#install-the-microphone-was-not-heard)).
381
+ - A re-install with listening already on runs the same check.
382
+ `EARS_MIC_CHECK_S=0` turns it off.
336
383
 
337
384
  ### The proof of a working install: you speak, and the agent WAKES and answers
338
385
 
@@ -343,7 +390,8 @@ it pages nobody: the agent is woken by the turn MAIL (`mail`,
343
390
  An install that stops at "the user speaks, a heard line appears" can leave the
344
391
  agent deaf to its person, and one that retires room mode on that proof goes
345
392
  deaf for good. So the proof is: say "hey NAME", and NAME wakes and
346
- answers.
393
+ answers. Install's `mic heard:` line proves the keeper hears, not that
394
+ anyone wakes: when the mic is NOT heard, install leaves out its proof line.
347
395
 
348
396
  `ears services install` (`--dry-run` lists the same checks and writes nothing).
349
397
  Before it writes anything, it checks every prerequisite it can check without
@@ -353,10 +401,10 @@ also makes it exit 2, once every other service has been tried: one block on
353
401
  stderr names each service that did not load, the install log and `ears
354
402
  services status`. Every real run appends its output and launchctl's answers,
355
403
  timestamped, to `install.log` in the log folder, `~/.sessionkit/voice/log/`, and prints that path
356
- as its `log` row ([logs](docs/logs.md#the-log-files)). It then:
404
+ as its `log` row ([logs](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/logs.md#the-log-files)). It then:
357
405
 
358
406
  - first finds the mlx-audio speech server on `:8890`, or installs its own
359
- (`dev.sessionkit.voice.mlx-audio`), as [the speech server](docs/speech-server.md)
407
+ (`dev.sessionkit.voice.mlx-audio`), as [the speech server](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/speech-server.md)
360
408
  page says, and prints which step it took. When it loads its own, the new
361
409
  `final` below is written without the probe: that server has not started
362
410
  yet;
@@ -395,11 +443,12 @@ as its `log` row ([logs](docs/logs.md#the-log-files)). It then:
395
443
  postfix and makes the `heard` box with `message setup --box heard`
396
444
  (which also makes sessionmail's mail root, your own box and its notmuch
397
445
  profile, if they are missing, and prints each one). On a host that
398
- sends by postfix, it makes the `heard` box NESTED under the store dir
399
- that carries `.tm-store`, as `~/.mail/agents/<store>/heard/`, when there
400
- is exactly one such dir, then runs `vmailbox-gen` if that is on `PATH`,
401
- and otherwise prints the commands (`~/.mail/.postfix/vmailbox-gen`:
402
- sessionmail keeps it off `PATH`). When sessionmail's answer cannot be
446
+ sends by postfix, it makes the `heard` box NESTED under the store box
447
+ (`message box list` names it; `message path <store>` is its folder), as
448
+ `<store>/heard/`, when there is exactly one, then runs this host's
449
+ `vmailbox-gen` as sessionmail names it (`message path --generator`), and
450
+ otherwise prints the commands. sessionvoice never spells the mail root:
451
+ every box is sessionmail's answer. When sessionmail's answer cannot be
403
452
  read (no `message`, or one older than 0.2.0), it warns, makes no box,
404
453
  checks neither, and goes on.
405
454
  On a postfix host it also prints the box to subscribe to:
@@ -413,7 +462,9 @@ as its `log` row ([logs](docs/logs.md#the-log-files)). It then:
413
462
  `ears services install --agent NAME` for a Mac where an agent seat should
414
463
  be woken when you speak, and `ears services install --no-agent` for a
415
464
  phone-only Mac (for example a Delta bot), where nobody is woken and the
416
- notices go to the log; then `ears wake add NAME`, to add a seat later. Inside
465
+ notices go to the log; then `ears wake add NAME`, to add a seat later, or
466
+ `ears wake add --self` in the seat's agent file, to register it at every
467
+ launch ([docs/ears.md](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/ears.md#wake-seats)). Inside
417
468
  an agent seat (`TM_AGENT_ADDRESS` set) it suggests that address's local
418
469
  part as NAME; it never installs on the suggestion. `--no-agent` installs
419
470
  anyway, and install warns that NOBODY is woken. A wake file that already
@@ -443,7 +494,14 @@ as its `log` row ([logs](docs/logs.md#the-log-files)). It then:
443
494
  STT models: this project's own extra pins `mlx-audio>=0.5.4`. An mlx-audio
444
495
  that answers every Parakeet request 500 (`No module named
445
496
  mlx_audio.tts.models.parakeet_tdt_0`) is not one, and the probe catches it.
446
- That the cause is an older mlx-audio is an inference, not measured.
497
+ That the cause is an older mlx-audio is an inference, not measured;
498
+ - with listening on (`--listen`, or already on), after the last service loads
499
+ and before the speech weights download, waits up to 60 s
500
+ (`EARS_MIC_CHECK_S`) for the keeper's first heartbeat with sound. Heard: one
501
+ `mic` row. Not heard: one line on stderr naming the Privacy setting and the
502
+ app, and exit 2. Every case it cannot check (listening off, no microphone,
503
+ a live call, a hand-made keeper, `EARS_MIC_CHECK_S=0`) gets one
504
+ `mic not checked:` row instead, with the exit unchanged.
447
505
 
448
506
  **The ride-along hook is a step of its own.** Installing the Claude Code
449
507
  plugin does not turn it on: the plugin ships no hooks, so no agent sees what
@@ -474,7 +532,7 @@ shows only the partial preview of a turn still being spoken and the shown
474
532
  events (wake word, barge-in, device change), never the finished turn a
475
533
  second time. `EARS_HEARD_PARTIALS_ONLY=1`, or `"heard_partials_only": true`
476
534
  in `ears.json`, does the same without editing the command
477
- ([docs/ears.md](docs/ears.md#the-heard-hook)). `ears heard hook --help`
535
+ ([docs/ears.md](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/ears.md#the-heard-hook)). `ears heard hook --help`
478
536
  prints its usage.
479
537
 
480
538
  ## Tests
@@ -28,7 +28,7 @@ streams transcription, and decides when the speaker has finished.
28
28
  for a while.
29
29
  - **Barge-in:** on headphones or a call, talk over the assistant and its voice
30
30
  dips at once; a couple of your own words, or "stop", cut it off
31
- ([barge-in](docs/barge-in.md)).
31
+ ([barge-in](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/barge-in.md)).
32
32
 
33
33
  ## Requirements
34
34
 
@@ -38,19 +38,21 @@ streams transcription, and decides when the speaker has finished.
38
38
  - [ ] Windows
39
39
 
40
40
  What sessionvoice depends on, what needs each one, and how to upgrade it:
41
- [dependencies](docs/dependencies.md).
41
+ [dependencies](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/dependencies.md).
42
42
 
43
43
  ## Components
44
44
 
45
45
  The engine above is the core. Around it, these parts make it an always-on
46
- pair of ears on a Mac (depth in [docs/architecture.md](docs/architecture.md)):
46
+ pair of ears on a Mac (depth in [docs/architecture.md](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/architecture.md)):
47
47
 
48
48
  - **Capture** (`src/ears/listen`): `ears.listen capture` reads one input
49
49
  device and writes every partial, turn and heartbeat to the **heard log**
50
50
  (`src/ears/heard.py`):
51
- `<home>/logs/conversations/heard_YYYY-MM-DD.jsonl`, where `<home>` is
52
- `~/.voicemode` on a host that already keeps it there, else
53
- `~/.sessionvoice` ([docs/logs.md](docs/logs.md#sessionvoices-home)).
51
+ `<root>/logs/conversations/heard_YYYY-MM-DD.jsonl`, where `<root>` is the
52
+ state root: `~/.sessionkit/voice/state` on a new host, or the old
53
+ `~/.sessionvoice` or `~/.voicemode` on a host that already keeps its heard
54
+ log there ([docs/logs.md](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/logs.md#the-state-root)).
55
+ `ears home state-dir` prints it.
54
56
  On a fresh host that also runs VoiceMode's `converse` as a heard guest,
55
57
  set `SESSIONVOICE_HOME=~/.voicemode` (or `VOICEMODE_BASE_DIR`), so that
56
58
  both write one log.
@@ -84,9 +86,11 @@ pair of ears on a Mac (depth in [docs/architecture.md](docs/architecture.md)):
84
86
  - **Sounds** (`keeper/ears-sounds.py`, launchd `dev.sessionkit.voice.ears-sounds`):
85
87
  a short sound in the user's ears when the ears log an event, so they know
86
88
  they were heard. Its partner, `keeper/mouth-sounds.py`
87
- (`dev.sessionkit.voice.mouth-sounds`), plays the mouth's cues. Opt-in:
88
- `ears services install --only sounds` loads both, from an installed
89
- package or a checkout; a bare install never does.
89
+ (`dev.sessionkit.voice.mouth-sounds`), plays the mouth's cues. A bare
90
+ `ears services install` loads both, from an installed package or a
91
+ checkout. From the first turn the ears' turn and partial ticks are on
92
+ (Settings turns them off), and so are the mouth's five cues
93
+ (`mouth-sounds.json` in the settings folder turns each off).
90
94
  - **Turn mail** (`keeper/ears-mail.py`, launchd `dev.sessionkit.voice.ears-mail`):
91
95
  mails each of the user's finished turns To and From
92
96
  `heard+room@<host>`, whatever input device the capture is following, with
@@ -115,6 +119,12 @@ pair of ears on a Mac (depth in [docs/architecture.md](docs/architecture.md)):
115
119
  label is dropped: the remaining words get `--speaker` (else
116
120
  `unknown@<host>`), never the seat whose echo was cut, and they can be a
117
121
  reply to that seat. A label naming anyone else is kept.
122
+ `Ears-Addressed` lists the seats a turn names, as `<seat>@<host>`, wherever
123
+ the name falls in the turn, plus `everyone` for "hey everyone":
124
+ `Ears-Addressed: everyone, ivy@<host>`. A seat is named by its `names` in
125
+ the wake file. The header carries names only, never the turn's words, and a
126
+ turn that names nobody has no `Ears-Addressed`. It goes on the room mail
127
+ only. [docs/ears.md](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/ears.md) gives the rules.
118
128
  `Ears-Echo` says a mouth line heard back through the mic was cut out of
119
129
  the turn. On the speakers the mic hears the mouth, so a line's
120
130
  words are cut wherever they sit in the user's turn: at its start (up to `3 + 5
@@ -143,6 +153,25 @@ pair of ears on a Mac (depth in [docs/architecture.md](docs/architecture.md)):
143
153
  have opened within 2 s of the line beginning, unless the turn is mostly
144
154
  the line. On the `ts − (t1 − t0)` estimate, only the first two guards
145
155
  apply. Numerals (`9%` for "nine percent") still slip past the word match.
156
+ `Ears-Said` names each session's own line the turn came after, so the
157
+ pager can keep that session's window open (its linger):
158
+ `Ears-Said: <session>@<host>=<end>, ...`. `<session>` is the full session
159
+ id on the line's `said` row, `<host>` the keeper's, and `<end>` that
160
+ row's `ts`, as it was written. Each session gets one entry: its newest line
161
+ that finished at or before the turn's `Ears-Start`, within 600 s. It
162
+ carries spoken lines only: a line on `lane: file` (a music bed or a sound
163
+ file) is left out, so a bed that ends after a line leaves that line the
164
+ session's newest. A line with `interest: zero`, or one cut before it
165
+ played a word (`played_s` 0), is left out. A line the user barged into after part of it played counts,
166
+ at its cut. Entries are sorted, and the header holds names and times,
167
+ never a line's words. It goes on the device mail and the `--to seats`
168
+ mail, never on a cut notice or a call turn; with no entry there is no
169
+ header.
170
+ `Ears-Film: playing` marks a turn the keeper heard while a film was
171
+ playing (`--kodi`: the state that holds `reply` and `light`), so the
172
+ pager can tell film dialogue from the user. It is a fact about the turn,
173
+ with one value, on the device mail only; with no film playing, or no
174
+ `--kodi`, there is no header. Kodi is asked at most once per turn.
146
175
  - **Waiter** (`bin/heard-first`): an agent blocks on the heard log
147
176
  until a line is for it, by `--rule`: its name in a partial or a
148
177
  turn (`partial:REGEX`, `turn:REGEX`), a reply to its last line
@@ -169,7 +198,7 @@ pair of ears on a Mac (depth in [docs/architecture.md](docs/architecture.md)):
169
198
  - **Speaker names** (`speakers/`, opt-in:
170
199
  `ears services install --only speakers`): a side-car tags each heard line
171
200
  with who spoke, in `speakers_YYYY-MM-DD.jsonl` beside the heard log, and
172
- the waiters read it. See [speakers/README.md](speakers/README.md).
201
+ the waiters read it. See [speakers/README.md](https://github.com/sessionkit/sessionvoice/blob/HEAD/speakers/README.md).
173
202
 
174
203
  ## Configuration
175
204
 
@@ -185,21 +214,22 @@ Three JSON settings files, each reread with no restart:
185
214
  read only their own file.
186
215
 
187
216
  Environment variables win over single keys (`EARS_RECORD` over `record`,
188
- `MOUTH_DEVICE` over `output`). `SESSIONVOICE_HOME` moves the home that holds
189
- the heard log and each part's state; by default it is `~/.sessionvoice`, or
190
- `~/.voicemode` on a host that already keeps its heard log there. The launch
191
- agents are in `~/Library/LaunchAgents/`.
217
+ `MOUTH_DEVICE` over `output`). `SESSIONVOICE_HOME` moves the state root,
218
+ which holds the heard log and each part's state; by default it is
219
+ `~/.sessionkit/voice/state`, or the old `~/.sessionvoice` or `~/.voicemode` on
220
+ a host that already keeps its heard log there. The launch agents are in
221
+ `~/Library/LaunchAgents/`.
192
222
 
193
223
  Which files and which variables each part reads, and where each one lives:
194
- [docs/configuration.md](docs/configuration.md).
224
+ [docs/configuration.md](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/configuration.md).
195
225
  Every key, its type and its default:
196
- [docs/settings.md](docs/settings.md).
226
+ [docs/settings.md](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/settings.md).
197
227
 
198
228
  ## Logs
199
229
 
200
- - **The heard log:** `<home>/logs/conversations/heard_YYYY-MM-DD.jsonl`,
230
+ - **The heard log:** `<root>/logs/conversations/heard_YYYY-MM-DD.jsonl`,
201
231
  one JSON line for each thing the ears heard and the mouth said, where
202
- `<home>` is the home above. A new file starts each day.
232
+ `<root>` is the state root above (`ears home state-dir` prints it). A new file starts each day.
203
233
  - **The services' logs:** `~/.sessionkit/voice/log/` (`EARS_LOG_DIR` moves it).
204
234
  `ears-keeper.log` is what the keeper did (devices, restarts, calls, a
205
235
  deaf mic); `ears-keeper-capture.log` is the room capture's own output;
@@ -209,9 +239,9 @@ Every key, its type and its default:
209
239
 
210
240
  Follow one with `tail -F`, for example
211
241
  `tail -F ~/.sessionkit/voice/log/ears-keeper.log`, or today's heard log with
212
- `tail -F <home>/logs/conversations/heard_$(date +%F).jsonl`.
242
+ `tail -F "$(ears home state-dir)/logs/conversations/heard_$(date +%F).jsonl"`.
213
243
  Every log file and what each line means:
214
- [docs/logs.md](docs/logs.md).
244
+ [docs/logs.md](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/logs.md).
215
245
 
216
246
  - **Health:** `ears services status` prints one row per launch agent
217
247
  (`running, pid N`, `loaded, not running` or `not installed`; the
@@ -258,12 +288,13 @@ conversation either. On the host, run `talk pair` in a terminal and open the
258
288
  link it prints; `talk token --rotate` makes every browser pair again. Open talk
259
289
  at `http://127.0.0.1:<port>/`, never at `localhost`. The code is `src/talk/`.
260
290
  `ears services install --only talk` keeps it running as a launch agent
261
- (opt-in; a bare install never does).
291
+ (opt-in; a bare install never does); add `--allow-host
292
+ machine.example.ts.net` for a phone behind `tailscale serve`.
262
293
 
263
294
  In the kit's web page, Talk is a room: the kit's front door runs
264
295
  `talk serve --kit` on a unix socket and logs you in, so there is nothing to
265
296
  pair and you never run `--kit` by hand
266
- ([docs/talk.md](docs/talk.md#in-the-kits-web-page)).
297
+ ([docs/talk.md](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/talk.md#in-the-kits-web-page)).
267
298
 
268
299
  ## Install
269
300
 
@@ -277,8 +308,7 @@ import that needs one fails naming it, as `pip install 'sessionvoice[EXTRA]'`.
277
308
 
278
309
  uv venv && uv pip install -e '.[all]' # onnxruntime + mlx-audio
279
310
  ears install
280
- ears services install --agent NAME # the launch agents: mouth, keeper, mail
281
- ears services install --only sounds # opt-in: the ears and mouth sounds
311
+ ears services install --agent NAME # the launch agents: mouth, keeper, mail, sounds
282
312
  ears services install --only talk # opt-in: talk serve, the live view
283
313
 
284
314
  A phone-only Mac (for example one that runs a Delta bot, with no agent
@@ -288,7 +318,24 @@ an agent seat should be woken when you speak. Before it writes anything,
288
318
  install says how big the speech models are (about 2.9 GB, fetched once,
289
319
  after the speech server loads) and that the speech server runs as a login
290
320
  service; `--dry-run` prints the same line, and `--no-prefetch` leaves the
291
- download to the first call.
321
+ download to the first call. `--no-prefetch` does not cover the Silero VAD
322
+ model (2.3 MB): install still fetches a missing one, because the keeper
323
+ cannot hear without it.
324
+
325
+ A fresh install leaves the room's ear off. To turn it on now, and check the
326
+ microphone while you are at the Mac:
327
+
328
+ ears services install --agent NAME --listen
329
+
330
+ - Typing `--listen` is the consent: it writes `"listen": true` to `ears.json`
331
+ before the keeper loads.
332
+ - macOS asks to let the keeper's Python use the microphone: click Allow.
333
+ - Install waits up to 60 s for the keeper to hear something. If it does not,
334
+ one line names System Settings > Privacy & Security > Microphone and the
335
+ app, and install exits 2
336
+ ([troubleshooting](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/troubleshooting.md#install-the-microphone-was-not-heard)).
337
+ - A re-install with listening already on runs the same check.
338
+ `EARS_MIC_CHECK_S=0` turns it off.
292
339
 
293
340
  ### The proof of a working install: you speak, and the agent WAKES and answers
294
341
 
@@ -299,7 +346,8 @@ it pages nobody: the agent is woken by the turn MAIL (`mail`,
299
346
  An install that stops at "the user speaks, a heard line appears" can leave the
300
347
  agent deaf to its person, and one that retires room mode on that proof goes
301
348
  deaf for good. So the proof is: say "hey NAME", and NAME wakes and
302
- answers.
349
+ answers. Install's `mic heard:` line proves the keeper hears, not that
350
+ anyone wakes: when the mic is NOT heard, install leaves out its proof line.
303
351
 
304
352
  `ears services install` (`--dry-run` lists the same checks and writes nothing).
305
353
  Before it writes anything, it checks every prerequisite it can check without
@@ -309,10 +357,10 @@ also makes it exit 2, once every other service has been tried: one block on
309
357
  stderr names each service that did not load, the install log and `ears
310
358
  services status`. Every real run appends its output and launchctl's answers,
311
359
  timestamped, to `install.log` in the log folder, `~/.sessionkit/voice/log/`, and prints that path
312
- as its `log` row ([logs](docs/logs.md#the-log-files)). It then:
360
+ as its `log` row ([logs](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/logs.md#the-log-files)). It then:
313
361
 
314
362
  - first finds the mlx-audio speech server on `:8890`, or installs its own
315
- (`dev.sessionkit.voice.mlx-audio`), as [the speech server](docs/speech-server.md)
363
+ (`dev.sessionkit.voice.mlx-audio`), as [the speech server](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/speech-server.md)
316
364
  page says, and prints which step it took. When it loads its own, the new
317
365
  `final` below is written without the probe: that server has not started
318
366
  yet;
@@ -351,11 +399,12 @@ as its `log` row ([logs](docs/logs.md#the-log-files)). It then:
351
399
  postfix and makes the `heard` box with `message setup --box heard`
352
400
  (which also makes sessionmail's mail root, your own box and its notmuch
353
401
  profile, if they are missing, and prints each one). On a host that
354
- sends by postfix, it makes the `heard` box NESTED under the store dir
355
- that carries `.tm-store`, as `~/.mail/agents/<store>/heard/`, when there
356
- is exactly one such dir, then runs `vmailbox-gen` if that is on `PATH`,
357
- and otherwise prints the commands (`~/.mail/.postfix/vmailbox-gen`:
358
- sessionmail keeps it off `PATH`). When sessionmail's answer cannot be
402
+ sends by postfix, it makes the `heard` box NESTED under the store box
403
+ (`message box list` names it; `message path <store>` is its folder), as
404
+ `<store>/heard/`, when there is exactly one, then runs this host's
405
+ `vmailbox-gen` as sessionmail names it (`message path --generator`), and
406
+ otherwise prints the commands. sessionvoice never spells the mail root:
407
+ every box is sessionmail's answer. When sessionmail's answer cannot be
359
408
  read (no `message`, or one older than 0.2.0), it warns, makes no box,
360
409
  checks neither, and goes on.
361
410
  On a postfix host it also prints the box to subscribe to:
@@ -369,7 +418,9 @@ as its `log` row ([logs](docs/logs.md#the-log-files)). It then:
369
418
  `ears services install --agent NAME` for a Mac where an agent seat should
370
419
  be woken when you speak, and `ears services install --no-agent` for a
371
420
  phone-only Mac (for example a Delta bot), where nobody is woken and the
372
- notices go to the log; then `ears wake add NAME`, to add a seat later. Inside
421
+ notices go to the log; then `ears wake add NAME`, to add a seat later, or
422
+ `ears wake add --self` in the seat's agent file, to register it at every
423
+ launch ([docs/ears.md](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/ears.md#wake-seats)). Inside
373
424
  an agent seat (`TM_AGENT_ADDRESS` set) it suggests that address's local
374
425
  part as NAME; it never installs on the suggestion. `--no-agent` installs
375
426
  anyway, and install warns that NOBODY is woken. A wake file that already
@@ -399,7 +450,14 @@ as its `log` row ([logs](docs/logs.md#the-log-files)). It then:
399
450
  STT models: this project's own extra pins `mlx-audio>=0.5.4`. An mlx-audio
400
451
  that answers every Parakeet request 500 (`No module named
401
452
  mlx_audio.tts.models.parakeet_tdt_0`) is not one, and the probe catches it.
402
- That the cause is an older mlx-audio is an inference, not measured.
453
+ That the cause is an older mlx-audio is an inference, not measured;
454
+ - with listening on (`--listen`, or already on), after the last service loads
455
+ and before the speech weights download, waits up to 60 s
456
+ (`EARS_MIC_CHECK_S`) for the keeper's first heartbeat with sound. Heard: one
457
+ `mic` row. Not heard: one line on stderr naming the Privacy setting and the
458
+ app, and exit 2. Every case it cannot check (listening off, no microphone,
459
+ a live call, a hand-made keeper, `EARS_MIC_CHECK_S=0`) gets one
460
+ `mic not checked:` row instead, with the exit unchanged.
403
461
 
404
462
  **The ride-along hook is a step of its own.** Installing the Claude Code
405
463
  plugin does not turn it on: the plugin ships no hooks, so no agent sees what
@@ -430,7 +488,7 @@ shows only the partial preview of a turn still being spoken and the shown
430
488
  events (wake word, barge-in, device change), never the finished turn a
431
489
  second time. `EARS_HEARD_PARTIALS_ONLY=1`, or `"heard_partials_only": true`
432
490
  in `ears.json`, does the same without editing the command
433
- ([docs/ears.md](docs/ears.md#the-heard-hook)). `ears heard hook --help`
491
+ ([docs/ears.md](https://github.com/sessionkit/sessionvoice/blob/HEAD/docs/ears.md#the-heard-hook)). `ears heard hook --help`
434
492
  prints its usage.
435
493
 
436
494
  ## Tests
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sessionvoice"
3
- version = "0.3.2"
3
+ version = "0.4.0"
4
4
  description = "One listening engine for coding agents and the Delta bot: Apple / Nemotron / whisper -> one event stream -> VAD -> turns"
5
5
  readme = "README.md"
6
6
  license = "AGPL-3.0-or-later"
@@ -89,8 +89,9 @@ ears = ["_keeper/ears-keeper.sh", "_keeper/call-ears", "_keeper/input-watch.swif
89
89
  # same way. They are data, never imported by the mouth: `mouth split` runs run.py in the
90
90
  # separator venv. torch and demucs, that venv's, are in no dependency list here.
91
91
  mouth = ["_separator/*"]
92
- # talk serves its page and manifest from beside server.py
93
- talk = ["index.html", "manifest.json"]
92
+ # talk serves its page, manifest and icons from beside server.py
93
+ talk = ["index.html", "manifest.json", "icon.svg", "favicon.ico", "apple-touch-icon.png",
94
+ "icon-192.png", "icon-512.png", "icon-mask-512.png"]
94
95
 
95
96
  [tool.pytest.ini_options]
96
97
  testpaths = ["tests"]
@@ -12,8 +12,8 @@ A call already live when this starts keeps the ear it has (one restarted with th
12
12
  leaves its captures running), and one with no ear gets one. Once, every already-live
13
13
  call was left alone, so a restart mid-call left that call deaf to the end.
14
14
  Stop: touch $CALL_EARS_DIR/STOP. Log: stdout (the keeper's $EARS_LOG_DIR/ears-keeper-calls.log);
15
- each ear's in $CALL_EARS_DIR/capture-N.log. CALL_EARS_DIR defaults to
16
- ${XDG_STATE_HOME:-~/.local/state}/ears/call-ears (it was ~/.<agent>/call-ears).
15
+ each ear's in $CALL_EARS_DIR/capture-N.log. CALL_EARS_DIR defaults to the state item
16
+ call-ears (`ears home state-dir call-ears`; it was ~/.<agent>/call-ears, then an XDG folder).
17
17
 
18
18
  The room and the calls were once two launch agents; now they are one, so
19
19
  this is not a launch
@@ -30,9 +30,42 @@ if os.path.dirname(os.path.abspath(__file__)) not in sys.path:
30
30
  import phone_roots # noqa: E402 (keeper/ is not a package)
31
31
 
32
32
  HOME = Path.home()
33
- # The keeper passes its own; the default is the keeper's (keeper/ears-keeper.sh), change both.
34
- DIR = Path(os.environ.get("CALL_EARS_DIR")
35
- or Path(os.environ.get("XDG_STATE_HOME") or HOME / ".local/state") / "ears/call-ears")
33
+ HERE = Path(__file__).resolve().parent
34
+
35
+
36
+ def voice_home():
37
+ """ears/home.py, the ONE place voice names its state items.
38
+ keeper/ imports nothing from the ears package, so it loads home.py (standard library
39
+ only) by its path, once: the checkout's src/ears/home.py, else the wheel's ears/home.py
40
+ above ears/_keeper/. Not there: exit 2, naming where it looked; there is no copy of the
41
+ rule to fall back on."""
42
+ mod = getattr(voice_home, "mod", None)
43
+ if mod is None:
44
+ import importlib.util
45
+ tried = [HERE.parent / "src/ears/home.py", HERE.parent / "home.py"]
46
+ f = next((f for f in tried if f.is_file()), None)
47
+ if f is None:
48
+ print(f"call-ears: no ears/home.py at {' nor '.join(map(str, tried))}: it names "
49
+ "voice's state folders. Run it from a sessionvoice checkout, the plugin or the "
50
+ "package, or set CALL_EARS_DIR", file=sys.stderr)
51
+ sys.exit(2)
52
+ spec = importlib.util.spec_from_file_location("_voice_home", f)
53
+ mod = importlib.util.module_from_spec(spec)
54
+ spec.loader.exec_module(mod)
55
+ voice_home.mod = mod
56
+ return mod
57
+
58
+
59
+ def voice_state_path(item: str) -> Path:
60
+ """The state item ITEM's path, as the PACKAGE answers it (ears.home.asked_state_path):
61
+ its own variable wins and asks nothing; else exit 2, naming both answers, when the package
62
+ cannot be asked or disagrees with this checkout's home.py."""
63
+ return voice_home().asked_state_path(item, "call-ears")
64
+
65
+
66
+ # The keeper passes its own (CALL_EARS_DIR, always): this default is for a run by hand, and it
67
+ # is the keeper's, the state item call-ears (ears-keeper.sh asks the same).
68
+ DIR = voice_state_path("call-ears")
36
69
  STOP = DIR / "STOP"
37
70
  # The calls the probe finds live, "call:N,call:M", rewritten every loop (so its age says this
38
71
  # is alive): the keeper's room-off rule reads it. A capture is no proof of a live call - one