Skip to main content
ضع على الأداة العلامة needsApproval فيتوقف التشغيل مؤقتًا قبل استدعائها، إلى أن يوافق إنسان (أو شيفرتك) على الاستدعاء أو يرفضه. يُحفظ التوقف المؤقت في ApprovalStore، فيمكن أن يأتي القرار بعد دقائق أو أيام، من طلب آخر أو من عملية أخرى، ثم يتابع التشغيل من حيث توقف.

أي الاستدعاءات تتوقف مؤقتًا

قيمة needsApproval هي true، أو دالة شرطية تستقبل الوسائط بعد التحقق منها، وأنواعها مستمدة من input المعرَّف بـ zod للأداة:
يمكن للدالة الشرطية أيضًا أن تمنع الاستدعاء أو أن تطلب الموافقة عليه مرة واحدة فقط في كل جلسة؛ انظر الموافقة أو المنع أو السؤال. حين يطلب النموذج عدة أدوات في دورة واحدة، يوقف أول استدعاء يحتاج إلى موافقة الدفعةَ كلها: الاستدعاءات التي قبله تُنفَّذ، ويتوقف التشغيل عنده، ثم تُنفَّذ الاستدعاءات التي بعده بعد البتّ فيه (انظر الموافقات في منتصف دفعة أدوات). أدوات MCP تأخذ قيمة needsApproval من تعليقات الأداة التوصيفية (annotations) على الخادم: readOnlyHint: true تُنفَّذ مباشرة، أما destructiveHint بقيمة true أو غير المحدَّدة (وهو افتراضي MCP) فتطلب الموافقة. اختر السلوك لكل خادم عبر approval: 'annotations' | 'always' | 'never' أو دالة؛ انظر موافقة أدوات MCP.

سياسات الصلاحيات

يضع permissions قواعد للوكيل كله بدل تحديدها أداةً أداة: قائمة من قواعد { tool, when?, action, reason? } تُفحص بالترتيب عند كل استدعاء أداة قبل needsApproval الخاص بالأداة. وأول قاعدة تنطبق هي التي تحسم:
  • allow تنفّذ الاستدعاء، دون موافقة حتى لو كان needsApproval سيطلبها. أما needsApproval الذي يمنع الاستدعاء فيبقى مانعًا له.
  • deny لا تنفّذه. يتلقى النموذج خطأ أداة فيه kind: 'denied' وreason الخاص بالقاعدة (انظر أخطاء الأدوات)، ويظهر في البث الحدث tool.error.
  • ask توقف التشغيل مؤقتًا بانتظار الموافقة، تمامًا مثل needsApproval (أو تسأل دالة approve).
وحين لا تنطبق أي قاعدة، يحسم needsApproval الخاص بالأداة، كما كان من قبل. tool اسم، أو قائمة أسماء، أو RegExp يُختبر على الاسم، أو '*' لكل الأدوات. وwhen يحصر القاعدة في بعض الاستدعاءات: يستقبل الوسائط بعد التحقق منها (بعد خطّافات preToolCall) و{ toolName, toolCallId, sessionId }، ويجوز أن يكون غير متزامن؛ وإذا رمى خطأً فشل الاستدعاء بذلك الخطأ. الدوال allow(tools) وdeny(tools, reason?) وask(tools) تبني القواعد الشائعة.
onPermissionDecision هو سجل التدقيق: يُستدعى مرة لكل استدعاء أداة (عدا الاستدعاءات التي رُفضت أصلًا لعدم صلاحية وسائطها) مع { toolName, toolCallId, decision, rule?, args?, at }. وdecision هو إجراء القاعدة المنطبقة أو 'default' حين لا تنطبق أي قاعدة، وrule هو { index, reason? } لتلك القاعدة، وat طابع زمني بصيغة ISO، ويُحذف args حين يضبط التشغيل الخيار redactContent. ويتلقى البث المُدخَل نفسه في حدث permission.decision (انظر البث). ولا يُنتَج أيٌّ منهما إلا حين يضبط الوكيل permissions أو onPermissionDecision. الخياران متاحان أيضًا في AgentExecutor.execute() / stream() وفي resumeAfterApproval(). يرث الوكلاء الفرعيون قواعد الوكيل الرئيسي، وتُفحص قبل قواعد الوكيل الفرعي نفسه، ويبلّغون قراراتهم إلى onPermissionDecision الخاص بالوكيل الرئيسي.

الموافقة أو المنع أو السؤال

يمكن لدالة needsApproval أن تُرجع أكثر من قيمة منطقية. تستقبل الوسائط بعد التحقق منها و{ toolName, toolCallId, sessionId, messages }، وتُرجع (أو تُنجَز بـ):
  • 'ask' أو true: التوقف المؤقت بانتظار الموافقة، كما كان من قبل.
  • 'approve' أو false: تنفيذ الاستدعاء.
  • 'deny' أو { deny: reason }: لا يُنفَّذ الاستدعاء ولا يتوقف التشغيل. يتلقى النموذج خطأ أداة فيه kind: 'denied' وreason، فيمكنه أن يجرّب شيئًا آخر؛ ويظهر في البث الحدث tool.error، ويسجّل onPermissionDecision القيمة decision: 'deny' مع reason (ومن دون rule).
ثلاث دوال مساعدة تغطي السياسات الشائعة: always() (مثل true)، وnever() (false) وonce(). تسأل once() في أول مرة تُستدعى فيها الأداة في الجلسة؛ وبعد أن يوافق إنسان على استدعاء، تُنفَّذ الاستدعاءات اللاحقة لتلك الأداة في الجلسة نفسها دون سؤال. الرفض لا يُحفظ، والجلسة الجديدة تسأل من جديد. أما once({ per: 'args' }) فتحفظ الموافقات لكل أداة مع وسائطها، فالاستدعاء بوسائط مختلفة يسأل من جديد. هذه الذاكرة محفوظة في سجل المحادثة (رسالة tool للاستدعاء الموافَق عليه تحمل metadata.approval)، فتُحفظ حيثما حُفظت الجلسة أو نقطة الحفظ أو لقطة الموافقة، وتبقى سارية بعد الاستئناف في عملية أخرى. ومن دون جلسة، كل send() سجل محادثة قائم بذاته. وإذا أزال ضغط السياق تلك الرسالة عادت الأداة إلى السؤال.
مع permissions، قاعدة deny تغلب دائمًا، ولا يُستدعى needsApproval الخاص بالأداة. وفيما عدا ذلك، 'deny' (أو { deny }) الصادر عن الأداة نفسها يمنع دائمًا، أيًّا كانت القاعدة المنطبقة. قاعدة allow تحل محل سؤال الأداة (فتتجاوز 'ask' وtrue وonce())، وقاعدة ask توقف التشغيل مؤقتًا حتى لو كانت الأداة ستوافق. وإن لم تنطبق أي قاعدة، سرت نتيجة needsApproval الخاص بالأداة. ويعمل خطّاف preToolCall قبل هذا كله، ويمكنه أن يمنع الاستدعاء أولًا (انظر نتائج الخطّافات). أدوات MCP تحتفظ بقيمة needsApproval المستمدة من تعليقاتها التوصيفية (قيمة منطقية). ويقيّم الوكلاء الفرعيون needsApproval لأدواتهم بالطريقة نفسها، وموافقة once() الممنوحة عبر الوكيل الرئيسي تُحفظ لبقية مهمة ذلك الوكيل الفرعي.

وكلاء createAgent()

  • send() المتوقف مؤقتًا يُنجَز (ولا يرمي خطأً) بـ finishReason: 'awaiting-approval' ومعه approvalId.
  • agent.approvals.list() تُرجع الاستدعاءات المعلّقة التي توقف عندها هذا الوكيل في هذه العملية، الأقدم أولًا: { id, toolCallId, toolName, args, createdAt }، مع subagentPath حين يكون الاستدعاء تابعًا لوكيل فرعي.
  • agent.approvals.resolve({ id, approved, note? }) تنفّذ الاستدعاء (عند الموافقة) أو تعطي النموذج رفضًا مع note الذي كتبته (عند الرفض)، ثم تتابع التشغيل، وتُنجَز بنتيجة التشغيل المتابَع، وقد يتوقف مؤقتًا من جديد. وترمي خطأً حين يكون id غير معروف أو سبق البتّ فيه.
  • التشغيل الذي توقف مؤقتًا داخل agent.session() يتابع في تلك الجلسة: استدعاء الأداة ونتيجته والإجابة النهائية تنضم إلى سجل محادثة الجلسة.
  • agent.stream() وsession.stream() ينتهيان عند التوقف المؤقت بحدث approval.requested ثم run.done ('awaiting-approval')؛ ويُبتّ فيه بالطريقة نفسها.
تُحفظ التوقفات المؤقتة في InMemoryApprovalStore خاص بكل وكيل ما لم تمرّر approvalStore أو store فيه approvals. وللبتّ في توقف مؤقت بعد إعادة التشغيل، أعطِ الوكيل مخزنًا دائمًا، مثل مخزن SQLite (انظر اختيار المخزن):
لا تعرف list() إلا التوقفات التي أحدثها كائن الوكيل هذا؛ فاحتفظ بـ approvalId (أو اقرأ المخزن) للبتّ في توقف مؤقت من مكان آخر. ولا ينضم التشغيل المتابَع إلى جلسة إلا حين يُبتّ فيه عبر الوكيل الذي يملك كائن تلك الجلسة.

الاستئناف بوكيل تغيّر

تحمل لقطة الموافقة بصمة الوكيل المتوقف، والبتّ فيها بوكيل يختلف نموذجه أو أدواته أو تعليماته يُصدر تحذيرًا ('warn'، وهو الافتراضي)، أو يُرفض بالخطأ LOUSHO_AGENT_DRIFT (onAgentDrift: 'error'، وتبقى الموافقة معلّقة)، أو يمضي دون اعتراض ('ignore'). والاستدعاء الموافَق عليه الذي لم تعد أداته موجودة يُرفض دائمًا بالخطأ LOUSHO_RESUME_TOOL_MISSING. انظر الاستئناف بوكيل تغيّر. عند استئناف استدعاء موافَق عليه تعمل خطّافات ما قبل الأداة من جديد، والوسائط التي تتركها (سواء عبر { input } أو بتعديل ctx.args في موضعه) يجب أن تساوي ما وُوفق عليه، بصرف النظر عن ترتيب المفاتيح؛ وإلا رُفض الاستدعاء بخطأ أداة فيه kind: 'validation'. والخطّاف الذي يحجب أجزاءً من مدخلاته أو يوحّد صيغتها ينبغي أن يعمل في التشغيل المتوقف أيضًا، فيوافق الإنسان على المدخلات بعد الحجب.

اتخاذ القرار في الشيفرة

مرّر approve للبتّ في كل استدعاء لحظة ظهوره بدل التوقف المؤقت: true تنفّذ الأداة، وfalse ترسل رفضًا إلى النموذج. يسري ذلك على send() والجلسات وagent.approvals.resolve()؛ أما stream() فيبقى ينتهي عند التوقف المؤقت.

بث التشغيل المتابَع

agent.approvals.streamResolve(decision, { signal }) و agent.approvals.streamAnswer({ id, answer }, { signal }) تتابعان التشغيل مثل resolve() وanswer()، لكنهما تُرجعان AgentRun نفسه الذي تُرجعه agent.stream() (انظر البث): run.start، ثم tool.start وtool.done للاستدعاء الذي بُتّ فيه (وtool.error عند الرفض)، ثم أحداث المتابعة حتى run.done. وrun.result هو ما تُنجَز به resolve(). والمتابعة التي تتوقف مؤقتًا من جديد تنتهي بـ approval.requested ثم run.done ('awaiting-approval')، حتى مع وجود دالة approve، كما يفعل stream(). والتوقف المؤقت الذي حدث داخل جلسة يتابع في تلك الجلسة، ويأتي run.done بعد حفظ سجل المحادثة.

طرح سؤال على المستخدم

createAgent({ askQuestion: true }) يعطي الوكيل الأداة المضمّنة ask_question (معطّلة افتراضيًا؛ وaskQuestionTool() تُرجع الأداة نفسها لتمرّرها في tools بنفسك). مدخلاتها هي { question: string; options?: string[]; allowFreeText?: boolean }. واستدعاؤها يوقف التشغيل مؤقتًا عبر آلية الموافقات، فينتظر تمامًا كما تنتظر الموافقة: في الجلسات، وفي المخازن الدائمة، وعبر إعادة التشغيل.
  • يحمل السجل المعلّق kind: 'question' و question: { text, options?, allowFreeText? }، في agent.approvals.list() وفي دالة approve وفي الحدث approval.requested، فتستطيع واجهة المستخدم أن تعرض سؤالًا بدل زر موافقة.
  • agent.approvals.answer({ id, answer }) (وهي مثل resolve({ id, approved: true, note: answer })) تتابع التشغيل. يتلقى النموذج { answer, option? }، حيث option هو فهرس العنصر المطابق في options (دون تمييز بين حالة الأحرف). ومع allowFreeText: false، الإجابة الخارجة عن الخيارات تصل إلى النموذج خطأَ أداة.
  • resolve({ id, approved: false, note? }) تمتنع عن الإجابة: يتلقى النموذج خطأ أداة فيه kind: 'rejected' يفيد بأن المستخدم امتنع عن الإجابة.
  • يجوز لدالة approve أن تُرجع نصًا للإجابة من الشيفرة، وهذا مفيد في الاختبارات والوكلاء المُبرمَجة ردودهم سلفًا.
قاعدة الصلاحيات التي تطبّق allow على ask_question تتجاوز التوقف المؤقت، فيفشل الاستدعاء بالرسالة “No answer”؛ فاترك الأداة على سلوكها الافتراضي.

AgentExecutor وresumeAfterApproval()

عند استخدام AgentExecutor.execute() مباشرة، مرّر approvalStore. عند استدعاء يتطلب موافقة يحفظ المنفّذ ExecutionSnapshot بدل استدعاء الأداة. استأنف لاحقًا، بعد إعادة تشغيل حقيقية إن شئت، عبر resumeAfterApproval():
resumeAfterApproval(decision, store, registry, provider, options?, checkpointStore?) تقبل معظم خيارات التنفيذ في execute() (مثل signal و onAgentEvent وexporter). ومع sessionId + checkpointStore، تُسجَّل للتوقف المؤقت نقطة حفظ أيضًا، ويرمي execute() بذلك الـ sessionId الخطأ SessionAwaitingApprovalError إلى أن يُبتّ في الموافقة، فلا يمكن تجاوز موافقة معلّقة (انظر التنفيذ المتين). وstreamResumeAfterApproval() تقبل الوسائط نفسها وتبث التشغيل المتابَع في AgentRun (انظر البث بعد الموافقة).

في مواضع أخرى

  • الوكلاء الفرعيون. الأداة التي تتطلب موافقة داخل وكيل فرعي توقف تشغيل الوكيل الرئيسي مؤقتًا؛ ويُبتّ فيها عبر agent.approvals الخاص بالوكيل الرئيسي. انظر الموافقات داخل وكيل فرعي.
  • أدوات مساحة العمل. أداة الصدفة (shell) تتطلب موافقة افتراضيًا. انظر أدوات مساحة العمل.
  • MCP. الأدوات التي تتطلب موافقة لا يمكن الموافقة عليها عبر MCP؛ انظر تقديم وكيل عبر MCP.
  • Agent Forge يعرض الموافقات المعلّقة بطاقاتٍ مضمّنة في محادثته؛ انظر Agent Forge.