macOS companion app

تراكب صوتي

دورة حياة تراكب الصوت (macOS)

الجمهور: مساهمو تطبيق macOS. الهدف: الحفاظ على قابلية توقع تراكب الصوت عندما تتداخل كلمة التنبيه مع اضغط للتحدث.

النية الحالية

  • إذا كان التراكب ظاهرًا بالفعل بسبب كلمة التنبيه وضغط المستخدم مفتاح الاختصار، فإن جلسة مفتاح الاختصار تتبنى النص الموجود بدلًا من إعادة تعيينه. يبقى التراكب ظاهرًا أثناء الضغط على مفتاح الاختصار. عند إفلات المستخدم: يتم الإرسال إذا وُجد نص بعد إزالة المسافات الطرفية، وإلا يتم الإخفاء.
  • كلمة التنبيه وحدها لا تزال ترسل تلقائيًا عند الصمت؛ أما اضغط للتحدث فيرسل فورًا عند الإفلات.

تم التنفيذ (9 ديسمبر 2025)

  • تحمل جلسات التراكب الآن رمزًا مميزًا لكل التقاط (كلمة تنبيه أو اضغط للتحدث). يتم إسقاط تحديثات الجزئي/النهائي/الإرسال/الإخفاء/المستوى عندما لا يطابق الرمز المميز، مما يتجنب ردود النداء القديمة.
  • يتبنى اضغط للتحدث أي نص تراكب ظاهر كبادئة (لذلك فإن الضغط على مفتاح الاختصار أثناء ظهور تراكب التنبيه يُبقي النص ويضيف الكلام الجديد). ينتظر حتى 1.5 ثانية للحصول على نسخة نهائية قبل الرجوع إلى النص الحالي.
  • يتم إصدار سجلات الرنين/التراكب عند info في الفئات voicewake.overlay وvoicewake.ptt وvoicewake.chime (بدء الجلسة، جزئي، نهائي، إرسال، إخفاء، سبب الرنين).

الخطوات التالية

  1. VoiceSessionCoordinator (ممثل)
    • يمتلك VoiceSession واحدة بالضبط في كل مرة.
    • API (مستند إلى الرمز المميز): beginWakeCapture وbeginPushToTalk وupdatePartial وendCapture وcancel وapplyCooldown.
    • يسقط ردود النداء التي تحمل رموزًا مميزة قديمة (يمنع أدوات التعرف القديمة من إعادة فتح التراكب).
  2. VoiceSession (نموذج)
    • الحقول: token وsource (wakeWord|pushToTalk) والنص المثبت/المتقلب، وأعلام الرنين، والمؤقتات (الإرسال التلقائي، الخمول)، وoverlayMode (display|editing|sending) وموعد انتهاء فترة التهدئة.
  3. ربط التراكب
    • يعكس VoiceSessionPublisher (ObservableObject) الجلسة النشطة إلى SwiftUI.
    • يعرض VoiceWakeOverlayView عبر الناشر فقط؛ ولا يغيّر الكائنات المفردة العامة مباشرة أبدًا.
    • تستدعي إجراءات مستخدم التراكب (sendNow وdismiss وedit) المنسق مع الرمز المميز للجلسة.
  4. مسار إرسال موحد
    • عند endCapture: إذا كان النص بعد إزالة المسافات الطرفية فارغًا ← إخفاء؛ وإلا performSend(session:) (يشغل رنين الإرسال مرة واحدة، ويمرر، ثم يخفي).
    • اضغط للتحدث: بلا تأخير؛ كلمة التنبيه: تأخير اختياري للإرسال التلقائي.
    • طبّق فترة تهدئة قصيرة على وقت تشغيل التنبيه بعد انتهاء اضغط للتحدث حتى لا تعيد كلمة التنبيه التشغيل فورًا.
  5. التسجيل
    • يصدر المنسق سجلات .info في النظام الفرعي ai.openclaw، ضمن الفئتين voicewake.overlay وvoicewake.chime.
    • الأحداث الرئيسية: session_started وadopted_by_push_to_talk وpartial وfinalized وsend وdismiss وcancel وcooldown.

قائمة التحقق للتصحيح

  • دفق السجلات أثناء إعادة إنتاج تراكب عالق:

    sudo log stream --predicate 'subsystem == "ai.openclaw" AND category CONTAINS "voicewake"' --level info --style compact
    
  • تحقق من وجود رمز مميز واحد فقط لجلسة نشطة؛ يجب أن يسقط المنسق ردود النداء القديمة.

  • تأكد من أن إفلات اضغط للتحدث يستدعي دائمًا endCapture مع الرمز المميز النشط؛ إذا كان النص فارغًا، فتوقع dismiss بدون رنين أو إرسال.

خطوات الترحيل (مقترحة)

  1. أضف VoiceSessionCoordinator وVoiceSession وVoiceSessionPublisher.
  2. أعد هيكلة VoiceWakeRuntime لإنشاء الجلسات/تحديثها/إنهائها بدلًا من لمس VoiceWakeOverlayController مباشرة.
  3. أعد هيكلة VoicePushToTalk لتبني الجلسات الموجودة واستدعاء endCapture عند الإفلات؛ وطبّق فترة تهدئة وقت التشغيل.
  4. اربط VoiceWakeOverlayController بالناشر؛ وأزل الاستدعاءات المباشرة من وقت التشغيل/اضغط للتحدث.
  5. أضف اختبارات تكامل لتبني الجلسات، وفترة التهدئة، وإخفاء النص الفارغ.

ذو صلة