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.
- {sessionvoice-0.3.2/src/sessionvoice.egg-info → sessionvoice-0.4.0}/PKG-INFO +95 -37
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/README.md +94 -36
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/pyproject.toml +4 -3
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_keeper/call-ears +38 -5
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_keeper/ears-keeper.sh +94 -40
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_keeper/ears-mail.py +650 -42
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_keeper/ears-sounds.py +60 -35
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_keeper/mouth-sounds.py +79 -33
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_keeper/phone_roots.py +16 -2
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_settings/ears.schema.json +15 -1
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_settings/mouth.schema.json +104 -3
- sessionvoice-0.4.0/src/ears/_settings/talk.schema.json +20 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_speakers/speaker-sidecar2.py +33 -14
- sessionvoice-0.4.0/src/ears/boxes.py +166 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/cli.py +13 -0
- sessionvoice-0.4.0/src/ears/codestamp.py +84 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/heard.py +55 -14
- sessionvoice-0.4.0/src/ears/heardbox.py +105 -0
- sessionvoice-0.4.0/src/ears/home.py +1115 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/loop.py +8 -3
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/sources.py +23 -4
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/maildir.py +12 -8
- sessionvoice-0.4.0/src/ears/mic_check.py +476 -0
- sessionvoice-0.4.0/src/ears/migrate.py +1299 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/recording.py +23 -9
- sessionvoice-0.4.0/src/ears/service_files.py +145 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/services.py +544 -96
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/speakers.py +10 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/speech.py +10 -4
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/vad.py +47 -10
- sessionvoice-0.4.0/src/ears/wake.py +445 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/__init__.py +328 -29
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/__main__.py +170 -30
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/audio.py +2 -1
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/backends.py +39 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/bed.py +12 -2
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/box.py +48 -7
- sessionvoice-0.4.0/src/mouth/fetch.py +440 -0
- sessionvoice-0.4.0/src/mouth/finder.py +180 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/hold.py +85 -3
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/inbox.py +4 -2
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/level.py +50 -3
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/maillog.py +4 -2
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/notice.py +219 -19
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/output.py +6 -2
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/player.py +11 -3
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/reply.py +23 -15
- sessionvoice-0.4.0/src/mouth/sender.py +117 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/sidecar.py +86 -29
- sessionvoice-0.4.0/src/mouth/sourcekey.py +178 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/split.py +26 -12
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/voices.py +229 -14
- {sessionvoice-0.3.2 → sessionvoice-0.4.0/src/sessionvoice.egg-info}/PKG-INFO +95 -37
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/sessionvoice.egg-info/SOURCES.txt +49 -0
- sessionvoice-0.4.0/src/talk/apple-touch-icon.png +0 -0
- sessionvoice-0.4.0/src/talk/favicon.ico +0 -0
- sessionvoice-0.4.0/src/talk/icon-192.png +0 -0
- sessionvoice-0.4.0/src/talk/icon-512.png +0 -0
- sessionvoice-0.4.0/src/talk/icon-mask-512.png +0 -0
- sessionvoice-0.4.0/src/talk/icon.svg +1 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/talk/index.html +94 -33
- sessionvoice-0.4.0/src/talk/manifest.json +6 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/talk/server.py +253 -68
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_arm_finds_heard_first.py +1 -1
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_arm_help.py +3 -2
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_audio_fence.py +1 -1
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_call_sock_search.py +123 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_call_tap_help.py +3 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_call_tap_listen_off.py +3 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_call_tap_silero.py +3 -0
- sessionvoice-0.4.0/tests/test_codestamp.py +119 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_configuration_docs.py +61 -3
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_conftest_order.py +1 -1
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_ears_keys.py +11 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_ears_mail.py +525 -1
- sessionvoice-0.4.0/tests/test_ears_mail_alert.py +460 -0
- sessionvoice-0.4.0/tests/test_ears_mail_lookup.py +159 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_ears_settings_schema.py +18 -2
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_ears_sounds_call.py +37 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_ears_wake.py +96 -1
- sessionvoice-0.4.0/tests/test_echo_own_line.py +140 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_export_ignore.py +49 -2
- sessionvoice-0.4.0/tests/test_heardbox.py +139 -0
- sessionvoice-0.4.0/tests/test_home.py +1085 -0
- sessionvoice-0.4.0/tests/test_install_fetch_retry.py +265 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_install_silero_pin.py +8 -5
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_instrumental_docs.py +30 -17
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_adopt.py +151 -5
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_again.py +26 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_defaults.py +42 -13
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_listen_off.py +4 -3
- sessionvoice-0.4.0/tests/test_keeper_mail_alert.py +354 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_room.py +2 -1
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_stop.py +3 -1
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_loop.py +10 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_log_dir.py +13 -7
- sessionvoice-0.4.0/tests/test_mail_alert_twins.py +105 -0
- sessionvoice-0.4.0/tests/test_mail_fence.py +453 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_maildir_private.py +11 -3
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_message_bin.py +6 -1
- sessionvoice-0.4.0/tests/test_mic_check.py +638 -0
- sessionvoice-0.4.0/tests/test_mic_check_docs.py +157 -0
- sessionvoice-0.4.0/tests/test_migrate.py +1130 -0
- sessionvoice-0.4.0/tests/test_mouth_sounds_defaults.py +66 -0
- sessionvoice-0.4.0/tests/test_mouth_thread_settle.py +56 -0
- sessionvoice-0.4.0/tests/test_nothing_born_outside.py +237 -0
- sessionvoice-0.4.0/tests/test_plists_after_migrate.py +338 -0
- sessionvoice-0.4.0/tests/test_pypi_dist.py +374 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_recording.py +9 -1
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_say_again_e2e.py +1 -1
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_scanlist.py +3 -2
- sessionvoice-0.4.0/tests/test_seat_home.py +745 -0
- sessionvoice-0.4.0/tests/test_service_files.py +396 -0
- sessionvoice-0.4.0/tests/test_service_files_not_ours.py +260 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_adopt_cold.py +2 -2
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_install_checks.py +202 -21
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_install_load_fail.py +9 -9
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_install_preflight.py +121 -3
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_label_move.py +1 -1
- sessionvoice-0.4.0/tests/test_services_listen_flag.py +168 -0
- sessionvoice-0.4.0/tests/test_services_mic_check.py +444 -0
- sessionvoice-0.4.0/tests/test_services_no_launchd.py +105 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_one_keeper.py +1 -1
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_sounds.py +7 -5
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_speakers.py +29 -8
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_speakers_checks.py +7 -7
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_speech_cache.py +1 -1
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_stt_model.py +1 -1
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_talk.py +154 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_settings_in_wheel.py +2 -2
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_settings_schema_refresh.py +3 -1
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_settings_stranded.py +15 -1
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_speakers_enrol.py +2 -2
- sessionvoice-0.4.0/tests/test_state_items.py +543 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_talk_cli.py +22 -3
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_talk_generalise.py +53 -44
- sessionvoice-0.4.0/tests/test_talk_hangup.py +62 -0
- sessionvoice-0.4.0/tests/test_talk_hearers.py +56 -0
- sessionvoice-0.4.0/tests/test_talk_helpers.py +114 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_talk_host_check.py +23 -24
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_talk_pair.py +11 -17
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_talk_prefix.py +24 -7
- sessionvoice-0.4.0/tests/test_talk_server.py +429 -0
- sessionvoice-0.4.0/tests/test_talk_tail_race.py +270 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_talk_token_gate.py +53 -36
- sessionvoice-0.4.0/tests/test_talk_uncrowd.py +133 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_talk_unix.py +16 -22
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_waiter_shims.py +1 -1
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_waiters.py +2 -2
- sessionvoice-0.4.0/tests/test_wake_rules_live.py +352 -0
- sessionvoice-0.4.0/tests/test_wake_self.py +259 -0
- sessionvoice-0.4.0/tests/test_wake_spoken_forms.py +249 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_wheel_build.py +1 -1
- sessionvoice-0.3.2/src/ears/home.py +0 -307
- sessionvoice-0.3.2/src/ears/wake.py +0 -261
- sessionvoice-0.3.2/src/talk/manifest.json +0 -1
- sessionvoice-0.3.2/tests/test_home.py +0 -355
- sessionvoice-0.3.2/tests/test_talk_server.py +0 -198
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/LICENSE +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/setup.cfg +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/__init__.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_keeper/held_stream.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_keeper/input-watch.swift +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_settings/ears-sounds.schema.json +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_speakers/cluster-room.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_speakers/make-refs.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_speakers/requirements.lock +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_speakers/run.sh +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_speech/build-constraints.txt +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/_speech/requirements.lock +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/backends/__init__.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/backends/apple.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/backends/nemotron.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/backends/proc.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/backends/whisper.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/callrec.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/envnames.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/eval.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/eval_mlx.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/events.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/extras.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/heard_wait.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/keys.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/lag.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/__init__.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/__main__.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/aged.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/chunker.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/detector.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/missed.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/silero.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/sink.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/speakers.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/streaming.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/stt.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/turnaudio.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/listen/watchdog.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/message_bin.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/operator_config.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/previews.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/redecode.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/serve.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/smartturn.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/ears/turns.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/_separator/requirements.lock +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/_separator/run.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/autosplit.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/bargeswitch.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/canplay.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/coreaudio.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/devices.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/duckswitch.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/instrumental.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/mpv.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/onset.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/paths.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/resolver.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/separator.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/splitwait.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/stems.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/mouth/timeline.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/sessionvoice.egg-info/dependency_links.txt +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/sessionvoice.egg-info/entry_points.txt +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/sessionvoice.egg-info/requires.txt +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/sessionvoice.egg-info/top_level.txt +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/talk/__init__.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/talk/__main__.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/talk/cli.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/talk/token_store.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/src/talk/web.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_anchor.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_apple_assets_why_not.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_audio_devices_schema.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_bin_tool_fallback.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_callrec.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_capture_mislabel.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_changelog_union.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_child_pytest.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_clock_check.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_conftest_ffmpeg.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_dependencies_docs.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_docs_layout.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_ears_no_backend.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_ears_serve_cli.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_ears_sounds_stream.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_ears_stt_key.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_echo_cut.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_echo_speaker.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_emitted_neutral.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_envnames.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_eval.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_extras.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_github_links.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_heard_contract.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_heard_cursor_by.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_heard_partials_only.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_heard_wait.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_heard_writer.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_install_apple_assets.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_install_apple_optin.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_beat.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_calls.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_deaf.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_fallback.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_in_wheel.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_lag.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_lid.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_missed_reply.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_model.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_speakers_deaf.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_stt_deaf.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_swiftc_guard.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_keeper_zeros.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_launchd_no_machine_paths.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_launchd_templates.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_aged.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_audio_stamp.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_call_source.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_clean_exit.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_hygiene.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_missed_reply.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_parts.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_redecode.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_silero.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_speaker_agent.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_speaker_split.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_stack_dump.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_stream_feed.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_streaming.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_stt_follow.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_stt_waits.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_turn_speaker.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_vad_edges.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_watchdog.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_listen_wedge_scenarios.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_live_home_guard.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_lock_wheels.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_marketplace_description.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_mouth_notice_fence.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_mouth_schema_plain.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_mouth_sounds_scoop_by.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_mouth_sounds_wake.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_name_hint_bench.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_no_real_times.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_no_time_of_day_stamps.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_no_tracker_ids.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_no_work_hour_stamps.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_operator_config.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_persona_defaults.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_plugin_install_doc.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_readme_links.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_recording_retention.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_release_script.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_sendmail_tripwire.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_separator_runner.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_service_build_constraints.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_service_hash_pins.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_bootstrap.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_dry_run.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_install_golden.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_kit_labels.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_legacy_plists.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_phone_only.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_prefetch.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_speech.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_stt_row.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_services_weights.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_settings_dir.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_speakers.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_speakers_call.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_speakers_follow.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_speakers_in_package.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_speakers_name.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_speakers_parity.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_speech.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_stt_name_hint.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_system_python_scripts.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_talk_kit.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_talk_launchd.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_talk_page_pair.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_talk_token.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_talk_web_room.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_turn_audio.py +0 -0
- {sessionvoice-0.3.2 → sessionvoice-0.4.0}/tests/test_turns.py +0 -0
- {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
|
+
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
|
-
`<
|
|
96
|
-
`~/.
|
|
97
|
-
`~/.sessionvoice`
|
|
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.
|
|
132
|
-
`ears services install
|
|
133
|
-
|
|
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
|
|
233
|
-
the heard log and each part's state; by default it is
|
|
234
|
-
`~/.
|
|
235
|
-
agents are in
|
|
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:** `<
|
|
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
|
-
`<
|
|
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
|
|
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
|
|
399
|
-
|
|
400
|
-
is exactly one
|
|
401
|
-
|
|
402
|
-
|
|
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
|
|
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
|
-
`<
|
|
52
|
-
`~/.
|
|
53
|
-
`~/.sessionvoice`
|
|
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.
|
|
88
|
-
`ears services install
|
|
89
|
-
|
|
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
|
|
189
|
-
the heard log and each part's state; by default it is
|
|
190
|
-
`~/.
|
|
191
|
-
agents are in
|
|
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:** `<
|
|
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
|
-
`<
|
|
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
|
|
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
|
|
355
|
-
|
|
356
|
-
is exactly one
|
|
357
|
-
|
|
358
|
-
|
|
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
|
|
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
|
+
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
|
|
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
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|