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

عمليات الجلسة: export، dump، share، fork، resume/continue

يصف هذا المستند السلوك المرئي للمشغّل لعمليات تصدير/مشاركة/تفرع/استئناف الجلسات كما هي مُنفَّذة حالياً.

العمليةمسار الدخولتعديل الجلسةإنشاء/تبديل ملف الجلسةالمخرج الناتج
/dumpأمر شرطة مائلة تفاعليلالانص في الحافظة
/export [path]أمر شرطة مائلة تفاعليلالاملف HTML
--export <session.jsonl> [outputPath]مسار سريع عند بدء CLIلا يوجد تعديل جلسة أثناء التشغيللا توجد جلسة نشطة؛ يقرأ الملف المستهدفملف HTML
/shareأمر شرطة مائلة تفاعليلالاملف HTML مؤقت + رابط مشاركة/gist
/forkأمر شرطة مائلة تفاعلينعم (تتغير هوية الجلسة النشطة)ينشئ ملف جلسة جديد ويُبدّل الجلسة الحالية إليه (وضع الاستمرارية فقط)ينسخ مجلد المخرجات إلى مساحة اسم الجلسة الجديدة عند وجوده
/resumeأمر شرطة مائلة تفاعلينعم (يُستبدل الحالة النشطة في الذاكرة)يتبدّل إلى ملف جلسة موجود محددلا شيء
--resumeبدء CLI (منتقي)نعم بعد إنشاء الجلسةيفتح ملف جلسة موجود محددلا شيء
--resume <id|path>بدء CLIنعم بعد إنشاء الجلسةيفتح جلسة موجودة؛ حالة المشروع المختلف يمكن أن تُنشئ تفرعاً في المشروع الحاليلا شيء
--continueبدء CLIنعم بعد إنشاء الجلسةيفتح مسار تتبع الطرفية أو أحدث جلسة؛ ينشئ جلسة جديدة إذا لم توجد أي جلسةلا شيء

المسار:

  1. يوجّه InputController أمر /export... إلى CommandController.handleExportCommand.
  2. يقسم الأمر على المسافات البيضاء ويستخدم فقط الوسيط الأول بعد /export كـ outputPath.
  3. يستدعي AgentSession.exportToHtml() الدالة exportSessionToHtml(sessionManager, state, { outputPath, themeName }).
  4. عند النجاح، تعرض واجهة المستخدم المسار وتفتح الملف في المتصفح.

تفاصيل السلوك:

  • يتم رفض الوسائط --copy و clipboard و copy صراحةً مع تحذير باستخدام /dump.
  • يُضمّن التصدير رأس/إدخالات/طرف الجلسة بالإضافة إلى systemPrompt الحالي وأوصاف الأدوات من حالة الوكيل.
  • لا تُضاف أي إدخالات جلسة أثناء التصدير.

تحذير:

  • تحليل الوسائط مبني على المسافات البيضاء (text.split(/\s+/))، لذا لا يتم الحفاظ على المسارات المقتبسة التي تحتوي على مسافات كمسار واحد عبر مسار الأمر هذا.

--export <inputSessionFile> [outputPath] (CLI)

Section titled “--export <inputSessionFile> [outputPath] (CLI)”

المسار في main.ts:

  1. يُعالَج مبكراً (قبل بدء الجلسة التفاعلية).
  2. يستدعي exportFromFile(inputPath, outputPath?).
  3. يحمّل SessionManager.open(inputPath) الإدخالات، ثم يُولَّد HTML ويُكتب.
  4. تطبع العملية Exported to: ... وتنتهي.

تفاصيل السلوك:

  • ملف الإدخال المفقود يظهر كخطأ File not found: <path>.
  • لا ينشئ هذا المسار AgentSession ولا يُعدّل أي جلسة قيد التشغيل.

/dump (تصدير تفاعلي إلى الحافظة)

Section titled “/dump (تصدير تفاعلي إلى الحافظة)”

المسار:

  1. يستدعي CommandController.handleDumpCommand() الدالة session.formatSessionAsText().
  2. إذا كانت سلسلة فارغة، يُبلَّغ No messages to dump yet.
  3. وإلا يُنسخ إلى الحافظة عبر copyToClipboard الأصلي.

محتوى التفريغ يتضمن:

  • موجّه النظام
  • النموذج النشط/مستوى التفكير
  • تعريفات الأدوات + المعلمات
  • رسائل المستخدم/المساعد
  • كتل التفكير واستدعاءات الأدوات
  • نتائج الأدوات وكتل التنفيذ (باستثناء إدخالات bash/python المميزة بـ excludeFromContext)
  • إدخالات مخصصة/hook/إشارات ملفات/ملخص الفروع/ملخص الضغط

لا تُجرى أي تغييرات على استمرارية الجلسة عند التفريغ.

/share تفاعلي فقط ويبدأ دائماً بتصدير الجلسة الحالية إلى ملف HTML مؤقت.

المرحلة 1: التصدير المؤقت

Section titled “المرحلة 1: التصدير المؤقت”
  • مسار الملف المؤقت: ${os.tmpdir()}/${Snowflake.next()}.html
  • يستخدم session.exportToHtml(tmpFile)
  • إذا فشل التصدير (خاصةً الجلسات في الذاكرة)، تنتهي المشاركة بخطأ.

المرحلة 2: معالج المشاركة المخصص (إن وُجد)

Section titled “المرحلة 2: معالج المشاركة المخصص (إن وُجد)”

يتحقق loadCustomShare() من ~/.xcsh/agent بحثاً عن أول مرشح موجود:

  • share.ts
  • share.js
  • share.mjs

المتطلبات:

  • يجب أن يُصدّر الوحدة افتراضياً دالة (htmlPath) => Promise<CustomShareResult | string | undefined>.

إذا كان موجوداً وصالحاً:

  • تدخل واجهة المستخدم حالة تحميل Sharing....
  • تفسير نتيجة المعالج:
    • سلسلة نصية => تُعامل كرابط URL، يُعرض ويُفتح
    • كائن => يُعرض url و/أو message؛ يُفتح url
    • undefined/قيمة زائفة => Session shared عام
  • يُحذف الملف المؤقت بعد الاكتمال.

سلوك الاحتياط الحرج:

  • إذا كان المعالج المخصص موجوداً لكن فشل تحميله، يُبلَّغ خطأ الأمر ويعود.
  • إذا نُفذ المعالج المخصص ورمى استثناءً، يُبلَّغ خطأ الأمر ويعود.
  • في كلتا حالتي الفشل، لا يتراجع إلى GitHub gist.
  • يحدث الاحتياط بـ gist فقط عندما لا يوجد سكريبت مشاركة مخصص.

المرحلة 3: الاحتياط الافتراضي بـ gist

Section titled “المرحلة 3: الاحتياط الافتراضي بـ gist”

فقط عندما لا يُعثر على معالج مشاركة مخصص:

  1. يتحقق من gh auth status.
  2. يعرض حالة تحميل Creating gist....
  3. ينفّذ gh gist create --public=false <tmpFile>.
  4. يحلّل رابط gist، يستخرج معرّف gist، يبني رابط المعاينة https://gistpreview.github.io/?<id>.
  5. يعرض كلاً من رابط المعاينة ورابط gist؛ يفتح المعاينة.

دلالات الإلغاء/الإحباط في المشاركة:

  • يحتوي حالة التحميل على خطاف onAbort الذي يستعيد واجهة المحرر ويُبلّغ Share cancelled.
  • لا يُمرّر لأمر gh gist create الأساسي إشارة إحباط في مسار الشيفرة هذا؛ الإلغاء على مستوى واجهة المستخدم ويُتحقق منه بعد عودة الأمر.

/fork ينشئ جلسة جديدة من الجلسة الحالية ويُبدّل هوية الجلسة النشطة.

الشروط المسبقة والحراسات الفورية

Section titled “الشروط المسبقة والحراسات الفورية”
  • إذا كان الوكيل يبث حالياً، يُرفض /fork مع تحذير.
  • تُمسح مؤشرات الحالة/التحميل في واجهة المستخدم قبل العملية.

AgentSession.fork():

  1. يُطلق session_before_switch مع reason: "fork" (قابل للإلغاء).
  2. يُفرّغ عمليات الكتابة المعلقة.
  3. يستدعي SessionManager.fork().
  4. ينسخ مجلد المخرجات من مساحة اسم الجلسة القديمة إلى الجديدة (بأفضل جهد؛ أخطاء النسخ غير ENOENT تُسجَّل ولا تكون قاتلة).
  5. يُحدّث agent.sessionId.
  6. يُطلق session_switch مع reason: "fork".

سلوك SessionManager.fork():

  • يتطلب وضع الاستمرارية وملف جلسة موجود.
  • ينشئ معرّف جلسة جديد ومسار ملف JSONL جديد.
  • يعيد كتابة الرأس مع:
    • id جديد
    • طابع زمني جديد
    • cwd بدون تغيير
    • parentSession يُعيَّن إلى معرّف الجلسة السابقة
  • يحتفظ بجميع الإدخالات غير الرأسية بدون تغيير في الملف الجديد.
  • يُعيد مدير الجلسة في الذاكرة undefined من fork().
  • يُعيد AgentSession.fork() القيمة false.
  • تُبلّغ واجهة المستخدم Fork failed (session not persisted or cancelled).

المسار:

  1. يفتح منتقي الجلسات المملوء عبر SessionManager.list(currentCwd, currentSessionDir).
  2. عند الاختيار، يستدعي SelectorController.handleResumeSession(sessionPath) الدالة session.switchSession(sessionPath).
  3. تمسح واجهة المستخدم وتعيد بناء المحادثة والمهام، ثم تُبلّغ Resumed session.

ملاحظات:

  • يسرد هذا المنتقي فقط الجلسات في نطاق مجلد الجلسة الحالي.
  • لا يستخدم البحث العالمي عبر المشاريع.
  • يسرد main.ts الجلسات لمجلد العمل/مجلد الجلسات الحالي ويفتح المنتقي.
  • يُفتح المسار المحدد بـ SessionManager.open(selectedPath) قبل إنشاء الجلسة.

ترتيب حل createSessionManager():

  1. إذا بدت القيمة كمسار (/ أو \ أو .jsonl)، يُفتح مباشرة.
  2. وإلا تُعامل كبادئة معرّف:
    • البحث في النطاق الحالي (SessionManager.list(cwd, sessionDir))
    • إذا لم يُعثر عليه ولا يوجد sessionDir صريح، البحث العالمي (SessionManager.listAll())

سلوك تطابق المعرّف عبر المشاريع:

  • إذا اختلف مجلد العمل للجلسة المطابقة عن مجلد العمل الحالي، يسأل CLI:
    • Session found in different project ... Fork into current directory? [y/N]
  • عند الموافقة: SessionManager.forkFrom(match.path, cwd, sessionDir) ينشئ ملف تفرع محلي جديد.
  • عند الرفض/الافتراضي لغير TTY: ينتهي الأمر بخطأ.

SessionManager.continueRecent(cwd, sessionDir):

  1. يحلّ مجلد الجلسة لمجلد العمل الحالي.
  2. يقرأ مسار تتبع الطرفية أولاً.
  3. يتراجع إلى أحدث ملف جلسة مُعدَّل.
  4. يفتح الجلسة المُعثر عليها؛ إذا لم توجد أي جلسة، ينشئ جلسة جديدة.

هذا سلوك عند البدء فقط؛ لا يوجد أمر شرطة مائلة تفاعلي /continue.

كيف يُغيّر تبديل الجلسة حالة التشغيل فعلياً

Section titled “كيف يُغيّر تبديل الجلسة حالة التشغيل فعلياً”

AgentSession.switchSession(sessionPath) يُنفّذ الانتقال أثناء التشغيل المُستخدم في عمليات شبيهة بالاستئناف:

  1. يُطلق session_before_switch مع reason: "resume" و targetSessionFile (قابل للإلغاء).
  2. يفصل اشتراك أحداث الوكيل ويُحبط العمل الجاري.
  3. يمسح رسائل التوجيه/المتابعة/الدور التالي المُصطفة.
  4. يُفرّغ كتابات مدير الجلسة الحالي.
  5. sessionManager.setSessionFile(sessionPath) ويُحدّث agent.sessionId.
  6. يبني سياق الجلسة من الإدخالات المحمّلة.
  7. يُطلق session_switch مع reason: "resume".
  8. يستبدل رسائل الوكيل من السياق.
  9. يستعيد النموذج (إذا كان متاحاً في السجل الحالي).
  10. يستعيد أو يُهيئ مستوى التفكير.
  11. يعيد توصيل اشتراك أحداث الوكيل.

لا يُنشئ switchSession() بحد ذاته أي ملف جلسة جديد.

إطلاق الأحداث ونقاط الإلغاء

Section titled “إطلاق الأحداث ونقاط الإلغاء”

خطافات دورة حياة التبديل/التفرع

Section titled “خطافات دورة حياة التبديل/التفرع”

لـ newSession و fork و switchSession:

  • حدث ما قبل: session_before_switch
    • الأسباب: new، fork، resume
    • قابل للإلغاء بإعادة { cancel: true }
  • حدث ما بعد: session_switch
    • نفس مجموعة الأسباب
    • يتضمن previousSessionFile

يعود ExtensionRunner.emit() مبكراً عند أول نتيجة إلغاء لحدث ما قبل.

سلوك onSession للأدوات المخصصة

Section titled “سلوك onSession للأدوات المخصصة”

يربط جسر SDK أحداث جلسة الإضافة بردود نداء onSession للأدوات المخصصة:

  • session_switch -> onSession({ reason: "switch", previousSessionFile })
  • session_branch -> reason: "branch"
  • session_start -> reason: "start"
  • session_tree -> reason: "tree"
  • session_shutdown -> reason: "shutdown"

هذه الردود رصدية؛ لا تُلغي التبديل/التفرع.

أسطح إلغاء أخرى ذات صلة بهذا المستند

Section titled “أسطح إلغاء أخرى ذات صلة بهذا المستند”
  • /fork يُحظر أثناء البث (يجب على المستخدم الانتظار/إحباط الاستجابة الحالية أولاً).
  • يمكن إلغاء منتقي /resume بإغلاق المستخدم للمنتقي.
  • يمكن إلغاء --resume <id> عبر المشاريع برفض موجه التفرع.
  • /share لديه مسار إحباط في واجهة المستخدم (Share cancelled) لمسار gist؛ لا يربط دلالات إنهاء العملية لـ gh gist create في مسار الشيفرة هذا.

سلوك الجلسة غير المستمرة (في الذاكرة)

Section titled “سلوك الجلسة غير المستمرة (في الذاكرة)”

عندما يُنشأ مدير الجلسة بـ SessionManager.inMemory() (--no-session):

  • مسار ملف الجلسة غائب.
  • /export و /share يفشلان مع Cannot export in-memory session to HTML (يُنقل إلى واجهة خطأ الأمر).
  • /fork يفشل لأن SessionManager.fork() يتطلب الاستمرارية.
  • /dump لا يزال يعمل لأنه يُسلسل حالة الوكيل في الذاكرة.
  • تُتجاوز دلالات resume/continue عبر CLI إذا تم تعيين --no-session، لأن إنشاء المدير يُعيد في الذاكرة فوراً.

تحذيرات التنفيذ المعروفة (حسب الشيفرة الحالية)

Section titled “تحذيرات التنفيذ المعروفة (حسب الشيفرة الحالية)”
  • لا يتحقق SelectorController.handleResumeSession() من النتيجة المنطقية لـ session.switchSession(...)؛ يمكن أن يستمر التبديل المُلغى بواسطة خطاف عبر مسار إعادة رسم/حالة واجهة المستخدم “Resumed session”.
  • فشل المشاركة المخصصة في /share لا يتدهور إلى احتياط gist الافتراضي؛ بل ينهي الأمر بخطأ.
  • تحليل وسائط /export بسيط ولا يحافظ على المسارات المقتبسة التي تحتوي على مسافات.