تخطَّ إلى المحتوى

بنية شجرة الجلسة (الحالية)

مرجع: session.md

يصف هذا المستند كيفية عمل تنقل شجرة الجلسة اليوم: نموذج الشجرة في الذاكرة، وقواعد حركة العقدة الطرفية، وسلوك التفريع، وتكامل الإضافات والأحداث.

يُخزَّن الجلسة كسجل إدخالات يُلحَق فقط، غير أن السلوك أثناء التشغيل يعتمد على الشجرة:

  • كل إدخال غير ترويسي له id وparentId.
  • الموضع النشط هو leafId في SessionManager.
  • يؤدي إلحاق إدخال دائمًا إلى إنشاء فرع من العقدة الطرفية الحالية.
  • التفريع لا يُعيد كتابة السجل التاريخي؛ بل يغير فقط المكان الذي تشير إليه العقدة الطرفية قبل الإلحاق التالي.

الملفات الرئيسية:

  • src/session/session-manager.ts — نموذج بيانات الشجرة، والاجتياز، وحركة العقدة الطرفية، واستخراج الفرع/الجلسة
  • src/session/agent-session.ts — تدفق تنقل /tree، والتلخيص، وإرسال الخطافات/الأحداث
  • src/modes/components/tree-selector.ts — سلوك واجهة الشجرة التفاعلية والتصفية
  • src/modes/controllers/selector-controller.ts — تنسيق أداة الاختيار لـ /tree و/branch
  • src/modes/controllers/input-controller.ts — توجيه الأوامر (/tree، و/branch، وسلوك الضغط المزدوج على Escape)
  • src/session/messages.ts — تحويل إدخالات branch_summary وcompaction وcustom_message إلى رسائل سياق LLM

نموذج بيانات الشجرة في SessionManager

Section titled “نموذج بيانات الشجرة في SessionManager”

الفهارس أثناء التشغيل:

  • #byId: Map<string, SessionEntry> — بحث سريع عن أي إدخال
  • #leafId: string | null — الموضع الحالي في الشجرة
  • #labelsById: Map<string, string> — التسميات المحلولة حسب معرّف الإدخال المستهدف

واجهات برمجة شجرة الشجرة:

  • getBranch(fromId?) يجتاز روابط الأصل حتى الجذر ويعيد مسار الجذر→العقدة
  • getTree() يعيد SessionTreeNode[] (entry، وchildren، وlabel)
    • تصبح روابط الأصل مصفوفات أبناء
    • تُعامَل الإدخالات التي تفتقر إلى أصول كجذور
    • يُرتَّب الأبناء من الأقدم إلى الأحدث حسب الطابع الزمني
  • getChildren(parentId) يعيد الأبناء المباشرين
  • getLabel(id) يُحلّ التسمية الحالية من labelsById

getTree() هي إسقاط أثناء التشغيل؛ يبقى الاستمرار عبر إدخالات JSONL المُلحَقة فقط.

دلالات حركة العقدة الطرفية

Section titled “دلالات حركة العقدة الطرفية”

ثمة ثلاثة أساسيات لحركة العقدة الطرفية:

  1. branch(entryId)

    • يتحقق من وجود الإدخال
    • يضبط leafId = entryId
    • لا يُكتَب أي إدخال جديد
  2. resetLeaf()

    • يضبط leafId = null
    • الإلحاق التالي ينشئ إدخال جذر جديد (parentId = null)
  3. branchWithSummary(branchFromId, summary, details?, fromExtension?)

    • يقبل branchFromId: string | null
    • يضبط leafId = branchFromId
    • يُلحق إدخال branch_summary كفرع من تلك العقدة الطرفية
    • عندما يكون branchFromId هو null، يُخزَّن fromId بصيغة "root"

سلوك تنقل /tree (ملف الجلسة ذاته)

Section titled “سلوك تنقل /tree (ملف الجلسة ذاته)”

AgentSession.navigateTree() هي تنقل، وليست تفريعًا لملف.

التدفق:

  1. التحقق من الهدف وحساب المسار المتروك (collectEntriesForBranchSummary)
  2. إرسال session_before_tree مع TreePreparation
  3. تلخيص الإدخالات المتروكة اختياريًا (ملخص مقدَّم من الخطاف أو ملخص مدمج)
  4. حساب هدف العقدة الطرفية الجديدة:
    • اختيار رسالة مستخدم: تنتقل العقدة الطرفية إلى أصلها، وتُعاد نص الرسالة لتعبئة المحرر مسبقًا
    • اختيار custom_message: نفس قاعدة رسالة المستخدم (العقدة الطرفية = الأصل، والنص يُعبّئ المحرر مسبقًا)
    • اختيار أي إدخال آخر: العقدة الطرفية = معرف الإدخال المختار
  5. تطبيق حركة العقدة الطرفية:
    • مع ملخص: branchWithSummary(newLeafId, ...)
    • بدون ملخص وكان newLeafId === null: resetLeaf()
    • وإلا: branch(newLeafId)
  6. إعادة بناء سياق الوكيل من العقدة الطرفية الجديدة وإرسال session_tree

مهم: تُربط إدخالات الملخص بـ موضع التنقل الجديد، لا بنهاية الفرع المتروك.

سلوك /branch (ملف جلسة جديد)

Section titled “سلوك /branch (ملف جلسة جديد)”

/branch و/tree مختلفان عمدًا:

  • /tree ينقل داخل ملف الجلسة الحالي.
  • /branch ينشئ ملف فرع جلسة جديدًا (أو استبدالًا في الذاكرة لوضع عدم الاستمرار).

تدفق /branch الموجه للمستخدم (SelectorController.showUserMessageSelectorAgentSession.branch):

  • يجب أن يكون مصدر الفرع رسالة مستخدم.
  • يُستخرج نص المستخدم المختار لتعبئة المحرر مسبقًا.
  • إذا كانت رسالة المستخدم المختارة جذرًا (parentId === null): يبدأ جلسة جديدة عبر newSession({ parentSession: previousSessionFile }).
  • وإلا: createBranchedSession(selectedEntry.parentId) لتفريع السجل حتى حد المطالبة المختارة.

تفاصيل SessionManager.createBranchedSession(leafId):

  • يبني مسار الجذر→العقدة الطرفية عبر getBranch(leafId)؛ يُلقي خطأ إذا كان مفقودًا.
  • يستبعد إدخالات label الموجودة من المسار المنسوخ.
  • يُعيد بناء إدخالات تسمية جديدة من labelsById المحلولة للإدخالات التي تبقى في المسار.
  • الوضع المستمر: يكتب ملف JSONL جديدًا ويُبدّل المدير إليه؛ يعيد مسار الملف الجديد.
  • الوضع في الذاكرة: يستبدل الإدخالات في الذاكرة؛ يعيد undefined.

إعادة بناء السياق وتكامل الملخص/المخصص

Section titled “إعادة بناء السياق وتكامل الملخص/المخصص”

buildSessionContext() (في session-manager.ts) يُحلّ مسار الجذر→العقدة الطرفية النشطة ويبني حالة سياق LLM الفعلية:

  • يتتبع أحدث حالة تفكير/نموذج/وضع/ttsr على المسار.
  • يتعامل مع أحدث ضغط على المسار:
    • يُرسل ملخص الضغط أولًا
    • يُعيد تشغيل الرسائل المحتفظ بها من firstKeptEntryId حتى نقطة الضغط
    • ثم يُعيد تشغيل الرسائل بعد الضغط
  • يشمل إدخالات branch_summary وcustom_message كأجسام AgentMessage.

ثم يُعيّن session/messages.ts هذه الأنواع من الرسائل لمدخلات النموذج:

  • يصبح branchSummary وcompactionSummary رسائل سياق ذات قالب بدور المستخدم
  • يصبح custom/hookMessage رسائل محتوى بدور المستخدم

وبالتالي تغيّر حركة الشجرة السياق بتغيير مسار العقدة الطرفية النشطة، لا بتعديل الإدخالات القديمة.

التسميات وسلوك واجهة الشجرة

Section titled “التسميات وسلوك واجهة الشجرة”

استمرار التسميات:

  • appendLabelChange(targetId, label?) يكتب إدخالات label على سلسلة العقدة الطرفية الحالية.
  • يُحدَّث labelsById فورًا (ضبط أو حذف).
  • يُحلّ getTree() التسمية الحالية على كل عقدة مُعادة.

سلوك أداة اختيار الشجرة (tree-selector.ts):

  • يُسطّح الشجرة للتنقل، ويحتفظ بإبراز المسار النشط، ويُولي الأولوية لعرض الفرع النشط أولًا.
  • يدعم أوضاع التصفية: default، وno-tools، وuser-only، وlabeled-only، وall.
  • يدعم البحث بالنص الحر في المحتوى الدلالي المُقيَّم.
  • يفتح Shift+L تحرير التسمية المضمّن ويكتب عبر appendLabelChange.

توجيه الأوامر:

  • /tree يفتح دائمًا أداة اختيار الشجرة.
  • /branch يفتح أداة اختيار رسالة المستخدم إلا إذا كان doubleEscapeAction=tree، وفي هذه الحالة يستخدم أيضًا واجهة أداة اختيار الشجرة.

نقاط تواصل الإضافات والخطافات لعمليات الشجرة

Section titled “نقاط تواصل الإضافات والخطافات لعمليات الشجرة”

واجهة برمجة الإضافات وقت الأمر (ExtensionCommandContext):

  • branch(entryId) — إنشاء ملف جلسة متفرّع
  • navigateTree(targetId, { summarize? }) — التنقل داخل الشجرة/الملف الحالي

الأحداث حول تنقل الشجرة:

  • session_before_tree
    • يستقبل TreePreparation:
      • targetId
      • oldLeafId
      • commonAncestorId
      • entriesToSummarize
      • userWantsSummary
    • قد يُلغي التنقل
    • قد يُقدّم حمولة ملخص تُستخدم بدلًا من الملخص المدمج
    • يستقبل إشارة إلغاء signal (مسار الإلغاء بـ Escape)
  • session_tree
    • يُرسل newLeafId، وoldLeafId
    • يتضمن summaryEntry عند إنشاء ملخص
    • fromExtension يُشير إلى مصدر الملخص

خطافات دورة الحياة المجاورة ذات الصلة:

  • session_before_branch / session_branch لتدفق /branch
  • session_before_compact، وsession.compacting، وsession_compact لإدخالات الضغط التي تؤثر لاحقًا على إعادة بناء سياق الشجرة

القيود الحقيقية وحالات الحواف

Section titled “القيود الحقيقية وحالات الحواف”
  • لا يمكن لـ branch() استهداف null؛ استخدم resetLeaf() لحالة الجذر قبل الإدخال الأول.
  • تدعم branchWithSummary() الهدف null وتُسجّل fromId: "root".
  • اختيار العقدة الطرفية الحالية في أداة اختيار الشجرة لا يُحدث أي تأثير.
  • يتطلب التلخيص نموذجًا نشطًا؛ في حال غيابه، يفشل التنقل مع التلخيص فورًا.
  • إذا أُلغي التلخيص، يُلغى التنقل وتبقى العقدة الطرفية دون تغيير.
  • لا تُعيد الجلسات في الذاكرة أبدًا مسار ملف فرع من createBranchedSession.

التوافق مع الإصدارات القديمة المازال موجودًا

Section titled “التوافق مع الإصدارات القديمة المازال موجودًا”

لا تزال ترحيلات الجلسة تعمل عند التحميل:

  • v1→v2 يضيف id/parentId ويحوّل مرساة فهرس الضغط القديمة إلى مرساة معرّف
  • v2→v3 يُرحّل دور hookMessage القديم إلى custom

السلوك الحالي أثناء التشغيل هو دلالات شجرة الإصدار الثالث بعد الترحيل.