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

بيئة تشغيل أداة Bash

يصف هذا المستند مسار بيئة تشغيل أداة bash المستخدمة في استدعاءات أدوات الوكيل، بدءًا من تطبيع الأوامر إلى التنفيذ، والاقتطاع/القطع الأثرية، والعرض.

كما يُوضّح أين يختلف السلوك في واجهة المستخدم النصية التفاعلية (TUI)، ووضع الطباعة، ووضع RPC، وتنفيذ الصدفة المُبادَر من المستخدم بعلامة التعجب (!).

النطاق وأسطح بيئة التشغيل

Section titled “النطاق وأسطح بيئة التشغيل”

هناك سطحان مختلفان لتنفيذ bash في coding-agent:

  1. سطح استدعاء الأداة (toolName: "bash"): يُستخدم عندما يستدعي النموذج أداة bash.
    • نقطة الدخول: BashTool.execute().
  2. سطح أوامر علامة التعجب للمستخدم (!cmd من الإدخال التفاعلي أو أمر RPC bash): مسار مساعد على مستوى الجلسة.
    • نقطة الدخول: AgentSession.executeBash().

كلاهما يستخدم في النهاية executeBash() في src/exec/bash-executor.ts للتنفيذ بدون PTY، لكن مسار استدعاء الأداة فقط هو الذي يُشغّل منطق التطبيع/الاعتراض وعارض الأداة.

خط أنابيب استدعاء الأداة من البداية إلى النهاية

Section titled “خط أنابيب استدعاء الأداة من البداية إلى النهاية”

1) تطبيع المدخلات ودمج المعاملات

Section titled “1) تطبيع المدخلات ودمج المعاملات”

يقوم BashTool.execute() أولاً بتطبيع الأمر الخام عبر normalizeBashCommand():

  • يستخرج | head -n N، | head -N، | tail -n N، | tail -N اللاحقة إلى حدود مُهيكَلة،
  • يقص المسافات البيضاء اللاحقة/السابقة،
  • يحافظ على المسافات البيضاء الداخلية كما هي.

ثم يدمج الحدود المُستخرَجة مع وسائط الأداة الصريحة:

  • وسائط head/tail الصريحة تتجاوز القيم المُستخرَجة،
  • القيم المُستخرَجة هي احتياطية فقط.

تشير تعليقات bash-normalize.ts إلى إزالة 2>&1، لكن التنفيذ الحالي لا يزيلها. سلوك وقت التشغيل لا يزال صحيحًا (stdout/stderr مدمجان بالفعل)، لكن سلوك التطبيع أضيق مما تُشير إليه التعليقات.

2) الاعتراض الاختياري (مسار الأوامر المحظورة)

Section titled “2) الاعتراض الاختياري (مسار الأوامر المحظورة)”

إذا كان bashInterceptor.enabled مفعّلاً، يقوم BashTool بتحميل القواعد من الإعدادات وتشغيل checkBashInterception() على الأمر المُطبَّع.

سلوك الاعتراض:

  • يُحظر الأمر فقط عندما:
    • تتطابق قاعدة التعبير النمطي، و
    • الأداة المُقترَحة موجودة في ctx.toolNames.
  • يتم تخطي قواعد التعبير النمطي غير الصالحة بصمت.
  • عند الحظر، يرمي BashTool خطأ ToolError برسالة:
    • Blocked: ...
    • الأمر الأصلي مضمّن.

أنماط القواعد الافتراضية (المُعرَّفة في الكود) تستهدف الاستخدامات الخاطئة الشائعة:

  • قارئات الملفات (cat، head، tail، …)
  • أدوات البحث (grep، rg، …)
  • أدوات البحث عن الملفات (find، fd، …)
  • المحررات الموضعية (sed -i، perl -i، awk -i inplace)
  • كتابة إعادة توجيه الصدفة (echo ... > file، إعادة توجيه heredoc)

يتضمن InterceptionResult حقل suggestedTool، لكن BashTool حاليًا يعرض فقط نص الرسالة (لا يوجد حقل أداة مُقترَحة مُهيكَل في details).

3) التحقق من CWD وتقييد المهلة الزمنية

Section titled “3) التحقق من CWD وتقييد المهلة الزمنية”

يتم حل cwd نسبةً إلى cwd الجلسة (resolveToCwd)، ثم التحقق منه عبر stat:

  • المسار المفقود -> ToolError("Working directory does not exist: ...")
  • ليس مجلداً -> ToolError("Working directory is not a directory: ...")

تُقيَّد المهلة الزمنية في النطاق [1, 3600] ثانية وتُحوَّل إلى ميلي ثانية.

قبل التنفيذ، تُخصّص الأداة مسار/معرّف قطعة أثرية (بأفضل جهد) لتخزين المخرجات المقتطعة.

  • فشل تخصيص القطعة الأثرية غير مُوقِف (يستمر التنفيذ بدون ملف تفريغ القطعة الأثرية)،
  • معرّف/مسار القطعة الأثرية يُمرَّر إلى مسار التنفيذ للحفظ الكامل للمخرجات عند الاقتطاع.

5) اختيار تنفيذ PTY مقابل غير PTY

Section titled “5) اختيار تنفيذ PTY مقابل غير PTY”

يختار BashTool تنفيذ PTY فقط عندما تتحقق جميع الشروط التالية:

  • bash.virtualTerminal === "on"
  • PI_NO_PTY !== "1"
  • سياق الأداة يحتوي على واجهة مستخدم (ctx.hasUI === true وctx.ui مُعيَّن)

وإلا فإنه يستخدم executeBash() غير التفاعلي.

هذا يعني أن وضع الطباعة وسياقات RPC/الأداة بدون واجهة مستخدم تستخدم دائمًا غير PTY.

محرك التنفيذ غير التفاعلي (executeBash)

Section titled “محرك التنفيذ غير التفاعلي (executeBash)”

نموذج إعادة استخدام جلسة الصدفة

Section titled “نموذج إعادة استخدام جلسة الصدفة”

يُخزّن executeBash() مؤقتًا نُسَخ Shell الأصلية في خريطة عامة على مستوى العملية مُفهرَسة بـ:

  • مسار الصدفة،
  • بادئة الأمر المُهيأة،
  • مسار اللقطة،
  • بيئة الصدفة المُسَلسَلة،
  • مفتاح جلسة الوكيل الاختياري.

لعمليات التنفيذ على مستوى الجلسة، يُمرِّر AgentSession.executeBash() القيمة sessionKey: this.sessionId، مما يعزل إعادة الاستخدام لكل جلسة.

مسار استدعاء الأداة لا يُمرِّر sessionKey، لذا نطاق إعادة الاستخدام يعتمد على تكوين الصدفة/اللقطة/البيئة.

تكوين الصدفة وسلوك اللقطة

Section titled “تكوين الصدفة وسلوك اللقطة”

عند كل استدعاء، يُحمّل المُنفِّذ تكوين صدفة الإعدادات (shell، env، prefix اختياري).

إذا تضمنت الصدفة المُحدَّدة bash، يحاول getOrCreateSnapshot():

  • تلتقط اللقطة الأسماء المستعارة/الدوال/الخيارات من ملف rc الخاص بالمستخدم،
  • إنشاء اللقطة بأفضل جهد،
  • الفشل يعود إلى عدم استخدام لقطة.

إذا تم تكوين prefix، يصبح الأمر:

<prefix> <command>

يُدفّق Shell.run() القطع إلى دالة رد الاتصال. يُمرِّر المُنفِّذ كل قطعة إلى OutputSink ودالة رد اتصال onChunk الاختيارية.

الإلغاء:

  • إشارة الإلغاء المُفعَّلة تُطلق shellSession.abort(...)،
  • المهلة الزمنية من النتيجة الأصلية تُعيَّن إلى cancelled: true + نص توضيحي،
  • الإلغاء الصريح يُعيد بالمثل cancelled: true + توضيح.

لا يُرمى استثناء داخل المُنفِّذ عند المهلة الزمنية/الإلغاء؛ يُعيد BashResult مُهيكَل ويترك للمُستدعي تعيين دلالات الخطأ.

مسار PTY التفاعلي (runInteractiveBashPty)

Section titled “مسار PTY التفاعلي (runInteractiveBashPty)”

عند تفعيل PTY، تُشغّل الأداة runInteractiveBashPty() التي تفتح مكون تراكب وحدة التحكم وتُدير جلسة PtySession أصلية.

أبرز السلوكيات:

  • طرفية xterm-headless الافتراضية تعرض نافذة العرض في التراكب،
  • إدخال لوحة المفاتيح يُطبَّع (بما في ذلك تسلسلات Kitty ومعالجة وضع مؤشر التطبيق)،
  • esc أثناء التشغيل يُنهي جلسة PTY،
  • تغيير حجم الطرفية يُنشَر إلى PTY (session.resize(cols, rows)).

تُحقَن إعدادات تقوية البيئة الافتراضية للتشغيلات غير المُراقَبة:

  • تعطيل أدوات العرض المُقسَّم (PAGER=cat، GIT_PAGER=cat، إلخ.)،
  • تعطيل مطالبات المحرر (GIT_EDITOR=true، EDITOR=true، …)،
  • تقليل مطالبات الطرفية/المصادقة (GIT_TERMINAL_PROMPT=0، SSH_ASKPASS=/usr/bin/false، CI=1
  • علامات أتمتة مدير الحزم/الأدوات للسلوك غير التفاعلي.

تُطبَّع مخرجات PTY (CRLF/CR إلى LF، sanitizeText) وتُكتَب في OutputSink، بما في ذلك دعم تفريغ القطعة الأثرية.

عند خطأ بدء/تشغيل PTY، يستقبل الحوض سطر PTY error: ... ويُنهى الأمر بكود خروج غير مُحدَّد.

معالجة المخرجات: التدفق، والاقتطاع، وتفريغ القطعة الأثرية

Section titled “معالجة المخرجات: التدفق، والاقتطاع، وتفريغ القطعة الأثرية”

يستخدم كلا مسارَي PTY وغير PTY حوض OutputSink.

  • يحتفظ بمخزن ذيلي في الذاكرة آمن لـ UTF-8 (DEFAULT_MAX_BYTES، حاليًا 50 كيلوبايت)،
  • يتتبع إجمالي البايتات/الأسطر المُشاهَدة،
  • إذا كان مسار القطعة الأثرية موجودًا وتجاوزت المخرجات (أو الملف نشط بالفعل)، يكتب التدفق الكامل إلى ملف القطعة الأثرية،
  • عند تجاوز عتبة الذاكرة، يقتطع المخزن المؤقت في الذاكرة إلى الذيل (آمن لحدود UTF-8)،
  • يُعلّم truncated عند حدوث التجاوز/تفريغ الملف.

يُعيد dump():

  • output (بادئة توضيحية محتملة)،
  • truncated،
  • totalLines/totalBytes،
  • outputLines/outputBytes،
  • artifactId إذا كان ملف القطعة الأثرية نشطًا.

تنبيه حول المخرجات الطويلة

Section titled “تنبيه حول المخرجات الطويلة”

اقتطاع وقت التشغيل يعتمد على عتبة البايتات في OutputSink (50 كيلوبايت افتراضيًا). لا يفرض حدًا صارمًا بـ 2000 سطر في مسار الكود هذا.

تحديثات الأداة المباشرة

Section titled “تحديثات الأداة المباشرة”

للتنفيذ بدون PTY، يستخدم BashTool مخزن TailBuffer منفصل للتحديثات الجزئية ويُرسل لقطات onUpdate أثناء تشغيل الأمر.

لتنفيذ PTY، يُعالَج العرض المباشر بواسطة تراكب واجهة مستخدم مخصص، وليس بقطع نص onUpdate.

تشكيل النتيجة، والبيانات الوصفية، وتعيين الأخطاء

Section titled “تشكيل النتيجة، والبيانات الوصفية، وتعيين الأخطاء”

بعد التنفيذ:

  1. معالجة cancelled:
    • إذا كانت إشارة الإلغاء مُفعَّلة -> يرمي ToolAbortError (دلالات الإلغاء)،
    • وإلا -> يرمي ToolError (يُعامَل كفشل في الأداة).
  2. timedOut في PTY -> يرمي ToolError.
  3. تطبيق مرشحات head/tail على نص المخرجات النهائي (applyHeadTail، head ثم tail).
  4. المخرجات الفارغة تصبح (no output).
  5. إرفاق بيانات وصفية للاقتطاع عبر toolResult(...).truncationFromSummary(result, { direction: "tail" }).
  6. تعيين كود الخروج:
    • كود خروج مفقود -> ToolError("... missing exit status")
    • كود خروج غير صفري -> ToolError("... Command exited with code N")
    • كود خروج صفري -> نتيجة نجاح.

هيكل حمولة النجاح:

  • content: نص المخرجات،
  • details.meta.truncation عند الاقتطاع، تتضمن:
    • direction، truncatedBy، عدد الأسطر+البايتات الإجمالية/المخرجة،
    • shownRange،
    • artifactId عند توفره.

لأن الأدوات المُدمَجة مُغلَّفة بـ wrapToolWithMetaNotice()، يُلحَق نص إشعار الاقتطاع بمحتوى النص النهائي تلقائيًا (على سبيل المثال: Full: artifact://<id>).

عارض استدعاء الأداة (bashToolRenderer)

Section titled “عارض استدعاء الأداة (bashToolRenderer)”

يُستخدم bashToolRenderer لرسائل استدعاء الأداة (toolCall / toolResult):

  • الوضع المطوي يعرض معاينة مقتطعة بصريًا حسب الأسطر،
  • الوضع الموسّع يعرض كل نص المخرجات المتاح حاليًا،
  • سطر التحذير يتضمن سبب الاقتطاع وartifact://<id> عند الاقتطاع،
  • قيمة المهلة الزمنية (من الوسائط) تُعرض في سطر البيانات الوصفية بالتذييل.

تنبيه: توسيع القطعة الأثرية الكاملة

Section titled “تنبيه: توسيع القطعة الأثرية الكاملة”

يحتوي BashRenderContext على isFullOutput، لكن مُنشئ سياق العارض الحالي لا يُعيّنه لنتائج أداة bash. العرض الموسّع لا يزال يستخدم النص الموجود بالفعل في محتوى النتيجة (مخرجات الذيل/المقتطعة) ما لم يوفر مُستدعٍ آخر محتوى القطعة الأثرية الكامل.

مكون أوامر علامة التعجب للمستخدم (BashExecutionComponent)

Section titled “مكون أوامر علامة التعجب للمستخدم (BashExecutionComponent)”

مكون BashExecutionComponent مخصص لأوامر ! الخاصة بالمستخدم في الوضع التفاعلي (وليس استدعاءات أدوات النموذج):

  • يُدفّق القطع مباشرةً،
  • المعاينة المطوية تحتفظ بآخر 20 سطرًا منطقيًا،
  • تقييد الأسطر عند 4000 حرف لكل سطر،
  • يعرض تحذيرات الاقتطاع والقطعة الأثرية عند وجود بيانات وصفية،
  • يُعلّم حالة الإلغاء/الخطأ/الخروج بشكل منفصل.

يُوصَّل هذا المكون بواسطة CommandController.handleBashCommand() ويُغذّى من AgentSession.executeBash().

اختلافات السلوك حسب الوضع

Section titled “اختلافات السلوك حسب الوضع”
السطحمسار الدخولمؤهل لـ PTYتجربة مستخدم المخرجات المباشرةعرض الأخطاء
استدعاء أداة تفاعليBashTool.executeنعم، عندما bash.virtualTerminal=on وواجهة المستخدم موجودة وPI_NO_PTY!=1تراكب PTY (تفاعلي) أو تحديثات ذيلية مُدفّقةأخطاء الأداة تصبح toolResult.isError
استدعاء أداة وضع الطباعةBashTool.executeلا (لا يوجد سياق واجهة مستخدم)لا تراكب TUI؛ المخرجات تظهر في تدفق الأحداث/نص المساعد النهائينفس تعيين أخطاء الأداة
استدعاء أداة RPC (أدوات الوكيل)BashTool.executeعادةً لا توجد واجهة مستخدم -> غير PTYأحداث/نتائج أدوات مُهيكَلةنفس تعيين أخطاء الأداة
أمر علامة التعجب التفاعلي (!)AgentSession.executeBash + BashExecutionComponentلا (يستخدم المُنفِّذ مباشرةً)مكون تنفيذ bash مخصصالمتحكم يلتقط الاستثناءات ويعرض خطأ واجهة المستخدم
أمر RPC bashrpc-mode -> session.executeBashلايُعيد BashResult مباشرةًالمُستهلك يتعامل مع الحقول المُعادة
  • المُعترض يحظر الأوامر فقط عندما تكون الأداة المُقترَحة متاحة حاليًا في السياق.
  • إذا فشل تخصيص القطعة الأثرية، لا يزال الاقتطاع يحدث لكن لا يتوفر مرجع artifact:// خلفي.
  • ذاكرة تخزين جلسة الصدفة المؤقتة ليس لديها إخلاء صريح في هذه الوحدة؛ مدة الحياة على نطاق العملية.
  • أسطح مهلة PTY وغير PTY تختلف:
    • PTY يكشف حقل نتيجة timedOut صريح،
    • غير PTY يُعيّن المهلة الزمنية إلى ملخص cancelled + annotation.