Get started
تأییدهای اپراتور در چند سطح کاربری
تأییدهای اپراتور در چند سطح
این طراحی #103505 را دنبال میکند. این طراحی، مرجع تأیید محلیِ فرایند را با یک چرخهٔ حیات واحد، تحت مالکیت Gateway و مبتنی بر SQLite جایگزین میکند. هر تأیید اجرای تحت مالکیت Gateway یا تأیید Plugin/ابزار، یک شناسهٔ پایدار، یک مسیر احرازهویتشدهٔ Control UI، حل اتمی بر پایهٔ «نخستین پاسخ برنده است» و نگاشتهای مختص اپراتور به جریانهای نشست مبدأ و نشستهای نیای خود دریافت میکند.
کنشهای درونخطی و پیوندهای عمیق در کنار هم وجود دارند. هیچ کلید تغییر حالت تأییدی وجود ندارد.
اهداف
- یک شیء تأیید ماندگار برای دروازههای اجرا و Plugin/ابزار.
- مسیر پایدار
${controlUiBasePath}/approve/{approvalId}. - حل از هر Control UI، برنامهٔ بومی یا سطح کانال مجاز.
- رفتار اتمی «نخستین پاسخ برنده است» در سطحهای همزمان.
- تلاشهای مجدد یکسان و همتوان؛ پاسخهای دیرهنگام متعارض نمیتوانند پاسخ برنده را بازنویسی کنند.
- پایان مهلت، رأیهای قابلاعتمادِ بدشکل، مسیرهای مفقود، لغو و راهاندازی مجدد بهصورت بسته و امن شکست میخورند.
- رویدادهای درخواست و پایانی به نشست مبدأ و همهٔ مالکان والد/هماهنگکنندهٔ مرتبط میرسند.
- کانالها کنشهای تأیید و پیمایش نوعدار دریافت میکنند؛ دادههای بازفراخوانی انتقال، خصوصیِ کانال باقی میمانند.
- متدهای موجود اجرای Gateway و Plugin سازگار باقی میمانند، درحالیکه پیادهسازی آنها به یک سرویس واحد همگرا میشود.
خارج از اهداف
- ماندگارکردن یا ازسرگیری خودِ اجرای ابزار مسدودشده پس از راهاندازی مجدد Gateway.
- تبدیل شناسه یا URL تأیید به اعتبارنامهٔ حامل.
- افزودن اعلانهای تأیید به رونوشتهای قابلمشاهده برای مدل یا بیدارکردن عاملهای والد.
- انتقال سیاست تأیید، فرمانهای محصول یا مجوز بازبین به Pluginهای کانال.
- تکثیر وضعیت تأیید بهازای هر کانال، دستگاه یا نیا.
- بازطراحی فهرستهای مجاز اجرا، ترکیب سیاست Plugin یا ماندگاری
allow-always، جز در مواردی که برای رفع ابهام از نتایج پایانی لازم است. - دردسترسقراردادن از راه دور یک TUI تعبیهشدهٔ بدون Gateway در گام نخست. این TUI فقط محلی باقی میماند و هنگامی که هیچ بازبینی وجود ندارد، باید بهصورت بسته و امن شکست بخورد.
خط مبنا و نقشهٔ شواهد پیش از عرضه
این جدول وضعیت پیادهسازی را هنگام گشودهشدن #103505 ثبت میکند. بخشهای عرضه در ادامه، رجیستری ماندگار، کنشهای نوعدار، صفحهٔ پیوند عمیق و گامهای کلاینت بومی را که بر این خط مبنا ساخته شدهاند دنبال میکنند.
| سطح | نقطهٔ ورود و مالک در خط مبنا | رفتار و شکاف خط مبنا |
|---|---|---|
| اجرای عامل | src/agents/bash-tools.exec-approval-request.ts، src/agents/bash-tools.exec-host-shared.ts |
ثبت دومرحلهای exec.approval.* از رقابت زودهنگام /approve جلوگیری میکند، اما پایان مهلت همچنان میتواند از طریق askFallback به اجازه تبدیل شود. |
| دروازهٔ ابزار Plugin | src/agents/agent-tools.before-tool-call.ts |
plugin.approval.* را درخواست میکند؛ timeoutBehavior: "allow" میتواند یک دروازهٔ منقضیشده را تأیید کند. حالت تعبیهشده در src/infra/embedded-plugin-approval-broker.ts مرجع محلیِ فرایند جداگانهای دارد. |
| دروازهٔ Node در Plugin | src/gateway/node-invoke-plugin-policy.ts |
مستقیماً از طریق مدیر Plugin ایجاد و پخش میکند و بخشی از چرخهٔ حیات متد سرور را تکرار میکند. |
| مرجع Gateway | src/gateway/server-aux-handlers.ts، src/gateway/exec-approval-manager.ts، src/gateway/server-methods/approval-shared.ts |
مدیران جداگانهٔ اجرا و Plugin از نگاشتهای محلیِ فرایند استفاده میکنند. ورودیهای پایانی برای 15 ثانیه باقی میمانند. «نخستین پاسخ برنده است» فقط درون یک فرایند برقرار است. |
| پروتکل Gateway | packages/gateway-protocol/src/schema/exec-approvals.ts، packages/gateway-protocol/src/schema/plugin-approvals.ts، src/gateway/methods/core-descriptors.ts |
اجرا دارای get فقط برای وضعیت درانتظار است؛ Plugin فاقد get است؛ هیچ واکشی پایانیِ مستقل از نوع برای یک پیوند عمیق وجود ندارد. |
| تحویل | src/infra/exec-approval-channel-runtime.ts، src/infra/approval-native-runtime.ts، src/infra/approval-handler-runtime.ts |
از مسیریابی مبدأ، پیامهای مستقیم تأییدکننده، بازپخش موارد درانتظار، گردانندههای بومی و پاکسازی پایانی درونفرایندی پشتیبانی میکند. یک پیگیری جداگانه، تطبیق پایانی ماندگار را اضافه میکند. |
| کنشهای قابلحمل | src/interactive/payload.ts، src/plugin-sdk/interactive-runtime.ts، src/plugin-sdk/approval-reply-runtime.ts |
دکمههای تأیید، کنشهای فرمانی حاوی /approve ... هستند؛ هدفهای URL و Web App فیلدهای دکمهٔ بدون نوع هستند. |
| Telegram | extensions/telegram/src/approval-handler.runtime.ts، extensions/telegram/src/button-types.ts |
رندرکننده پیش از تولید دادههای بازفراخوانی خصوصی، متن فرمان را برای تشخیص معنای تأیید تجزیه میکند. |
| Control UI | ui/src/app/exec-approval.ts، ui/src/app/overlays.ts، ui/src/components/exec-approval.ts |
رابط تأیید یک پنجرهٔ مودال سراسری است. ui/src/app-route-paths.ts و ui/src/app-routes.ts از مسیرهای دقیق استفاده میکنند و مسیرهای ناشناخته را به Chat بازنویسی میکنند. |
| مالکیت نشست | src/agents/subagent-registry.types.ts، src/agents/subagent-registry-read.ts، src/config/sessions/types.ts |
مالکیت کنترلکننده، درخواستکننده، والد صریح و ایجاد قدیمی وجود دارد، اما رویدادهای تأیید به جریانهای آن نشستها نگاشت نمیشوند. |
| وضعیت مشترک | src/state/openclaw-state-schema.sql، src/state/openclaw-state-db.ts |
تراکنشهای فوری موجود و بهروزرسانیهای شرطی Kysely از مقایسهوتنظیم ماندگار در state/openclaw.sqlite پشتیبانی میکنند. |
آزمونهای نمایندهٔ فعلی شامل src/gateway/exec-approval-manager.test.ts، src/gateway/server-methods/approval-shared.test.ts، src/agents/bash-tools.exec-gateway-approval.e2e.test.ts، extensions/telegram/src/approval-handler.runtime.test.ts و ui/src/e2e/approval-flow.e2e.test.ts هستند.
SDK مربوط به Plugin تنها مرز کانال/Plugin باقی میماند. تغییرات زمان اجرای تأیید و ارائه باید از طریق زیرمسیرهای موجود src/plugin-sdk/approval-*.ts و src/plugin-sdk/interactive-runtime.ts صادر شوند؛ کد عملیاتی Plugin نباید اجزای داخلی Gateway را وارد کند.
نمونههای پیشین
Omnigent تجربهٔ کاربری و معناشناسی شکست سودمندی ارائه میدهد:
approval.pyوضعیت ASK را متوقف نگه میدارد، پایان مهلت را بهازای هر سیاست اعمال میکند و فقط پذیرش دقیق را تأیید تلقی میکند.sessions.pyدروازهٔ هارنس بومی سمت سرور و نگاشت درخواست/حل به نیا را در بر دارد.ApprovePage.tsxصفحهٔ مستقل تأیید موبایل را فراهم میکند.
ادعای ذخیرهسازی آن را بدون بررسی نپذیرید. وضعیت فعال و درانتظار کنونی در _elicitation_registry.py محلیِ فرایند است و جدول درانتظارِ استفادهنشده توسط e3b1f2a4c9d7_drop_pending_tool_calls_table.py حذف میشود. OpenClaw آگاهانه فراتر میرود: SQLite مرجع نهایی است و هر گذار پایانی یک مقایسهوتنظیم پایگاه داده است.
معماری و مالکیت
Gateway مالک چرخهٔ حیات است:
- یک عامل، قلاب Plugin یا سیاست Node، یک درخواست خاص نوع و اتصال اجرای محلیِ فرایند فراهم میکند.
- Gateway آن را اعتبارسنجی میکند و یک نگاشت پاکسازیشده برای بازبین میسازد.
- سرویس تأیید، مخاطبان مبدأ/مالک را محاسبه میکند، ردیف مرجع را درج میکند و سپس انتظارگر درونفرایندی را ثبت میکند.
- پس از درج ماندگار، Gateway رویدادهای تأیید موجود، نگاشتهای نشست، اعلانهای کانال و پوش بومی را منتشر میکند.
- هر سطح از طریق همان سرویس حل میشود.
- سرویس یک گذار پایانی را ثبت میکند، انتظارگر زمان اجرا را بیدار میکند و نگاشتهای پایانی را منتشر میکند.
- تحویل ناموفق رویداد هرگز تصمیم ثبتشده را برنمیگرداند؛ کلاینتها از طریق
approval.getیا بازپخش فهرست بازیابی میشوند.
مرزهای مالکیت:
src/gateway/: سرویس تأیید، مجوزدهی، سازگارکنندههای RPC، ساخت URL، چرخهٔ حیات انتظارگر و انتشار رویداد.src/state/: شِمای مشترک و نوعهای Kysely تولیدشده.src/infra/: مدلهای نمای تأیید پاکسازیشده و ساخت ارائهٔ قابلحمل.src/agents/: درخواست، انتظار و اعمال رأی بازگشتی؛ بدون ماندگاری.src/channels/وextensions/*: رندرکردن کنشهای نوعدار، مجازکردن کاربران کانال، کدگذاری بازفراخوانیهای خصوصی و بهروزرسانی کنترلهای تحویلشده.src/plugin-sdk/: فقط قراردادهای عمومی تأیید و ارائه.ui/: صفحهٔ مستقل و کلاینتهای موجود صف/مودال.
انتظارگر درونفرایندی یک سازوکار اعلان است، نه مرجع تصمیمگیری. ثبت، ردیف را درج میکند و پیش از انتشار درخواست، انتظارگر را بهصورت همگام نصب میکند؛ بنابراین هیچ حلکنندهای نمیتواند میان این مراحل وارد شود. هر حلکنندهٔ بعدی، پیش از پایاندادن به آن انتظارگر، نتیجه را از طریق SQLite ثبت میکند.
رکورد ماندگار
یک جدول operator_approvals به پایگاه دادهٔ وضعیت مشترک اضافه کنید.
| ستون | هدف |
|---|---|
approval_id |
شناسهٔ متعارف و یکتای سراسری. برای سازگاری پروتکل، شناسههای exec موجود و شناسههای plugin: را حفظ کنید، اما هرگز نوع را از پیشوند استنباط نکنید. |
resolution_ref |
مکانیاب یکتای کامل SHA-256 با قالب base64url برای فراخوانیهای برگشتی انتقال که نمیتوانند شناسهٔ متعارف را حمل کنند. این مجوزدهی یا شناسهٔ URL عمومی نیست. |
kind |
تفکیکگر بستهٔ exec | plugin. |
status |
وضعیت بستهٔ pending | allowed | denied | expired | cancelled. |
presentation_json |
نمای تأییدشده و برچسبخورده با نوع برای بازبین. درخواستهای خام زمان اجرا، پیوندهای فرمان و بارهای فراخوانی برگشتی در همان فرایند باقی میمانند. |
source_agent_id، source_session_key |
لنگر هویت مبدأ و نمای نشست. کلید نشست ماندگار است؛ UUID چرخشی نشست ماندگار نیست. |
audience_session_keys_json |
آرایهٔ JSON مرتب و بدون تکرار که پیمایش مالکیت سطحاول محدود آن را تولید میکند. رویدادهای درخواست و پایانی از همین اسنپشات استفاده میکنند. |
requested_by_device_id، requested_by_client_id |
فرادادهٔ ماندگار درخواستکننده/ممیزی. شناسهٔ اتصال در حافظه میماند و یک هویت اصلی میانسطحی نیست. |
reviewer_device_ids_json |
دستگاههای اختیاری بازبین که صریحاً هدفگذاری شدهاند و فقط زمان اجرای مورداعتماد تأیید آنها را فراهم میکند. |
runtime_epoch |
دورهٔ فرایندی که مالک اجرای متوقفشده است؛ برای لغو ردیفهای یتیم پس از راهاندازی مجدد استفاده میشود. |
created_at_ms، expires_at_ms، updated_at_ms |
زمانبندی مرجع. |
decision |
تصمیم صریح کاربر، در صورت وجود. |
terminal_reason |
دلیل بستهشدن، مانند user، timeout، malformed-verdict، no-route، run-aborted یا gateway-restart. |
resolved_at_ms، resolver_kind، resolver_id |
هویت برنده و ممیزی که در سمت سرور نگهداری میشوند. نماهای بازبین شناسههای خام حلکننده را حذف میکنند. |
consumed_at_ms، consumed_by |
محافظ بازپخش جداگانه برای allow-once؛ مصرفکردن نباید تصمیم ثبتشده را پاک کند. |
ایندکسهای الزامی:
| ایندکس | هدف |
|---|---|
(resolution_ref) یکتا |
رد ابهام میانستونی approval_id/resolution_ref هنگام درج. |
(status, expires_at_ms) |
یافتن تأییدهای در انتظار و تطبیق مهلتهای مرجع. |
(source_session_key, created_at_ms DESC) |
بازپخش تأییدهای اخیر برای یک نشست مبدأ. |
(resolved_at_ms) |
پاکسازی تأییدهای پایانی نگهداریشده مطابق سیاست ثابت نگهداری. |
آرایههای مخاطبان کوچک و محدود هستند. بازپخش پالایششده بر اساس نشست ابتدا ردیفهای در انتظار قابلمشاهده را از طریق Kysely انتخاب میکند، سپس آرایههای محدود مخاطبان را در کد برنامه رمزگشایی و پالایش میکند؛ از تطبیق رشته یا پرسوجوهای JSON با SQL خام استفاده نمیکند.
ردیفهای پایانی را بهمدت 30 روز، همراستا با دورهٔ نگهداری ممیزی فراداده در src/audit/audit-event-store.ts، حفظ کنید. پاکسازی یک سیاست ثابت نگهداری است، نه سطح پیکربندی جدید. پایگاه داده وضعیت خصوصی و محلی صفحهٔ کنترل است، اما APIهای بازبین هرگز نباید کل درخواست ذخیرهشده یا پیوند زمان اجرا را افشا کنند.
ماشین حالت و مقایسهوتنظیم
فقط این گذارها معتبرند:
pending -> allowed:allow-onceیاallow-alwaysصریح.pending -> denied: رد صریح، حکم پایانی ناقصِ مورداعتماد، یا نبود مسیر تحویل.pending -> expired: رسیدن مهلت مرجع.pending -> cancelled: توقف اجرا، خاموشی کنترلشده، یا بازیابی یتیم پس از راهاندازی مجدد.
حکم مؤثر هر وضعیت پایانیِ غیرمجاز، رد است.
حلوفصل از یک تراکنش فوری SQLite و بهروزرسانی شرطی Kysely معادل زیر استفاده میکند:
UPDATE operator_approvalsSET status = ?, decision = ?, terminal_reason = ?, resolved_at_ms = ?WHERE approval_id = ? AND status = 'pending' AND expires_at_ms > ?;اگر بهروزرسانی هیچ ردیفی را تحتتأثیر قرار ندهد، همان تراکنش رکورد را میخواند:
- مفقود یا غیرمجاز: یافتنشدن را برگردانید؛ وجود آن را افشا نکنید.
- هنوز در انتظار، اما مهلت رسیده است: با مقایسهوتنظیم آن را به
expiredتغییر دهید، سپس آن ردیف پایانی را برگردانید. - همان تصمیم ثبتشده: موفقیت تکرارپذیر را همراه برندهٔ ثبتشده برگردانید.
- تصمیم متفاوت: API یکپارچه
applied: falseرا همراه برندهٔ ثبتشده برمیگرداند؛ آداپتورهای قدیمی در مواردی که قرارداد منتشرشدهشان ایجاب میکند،APPROVAL_ALREADY_RESOLVEDرا حفظ میکنند. - هر وضعیت پایانی: هرگز آن را تغییر ندهید.
now == expires_at_ms منقضی شده است. زمان Gateway مرجع است.
اجرای allow-once از CAS دومی روی consumed_at_ms IS NULL استفاده میکند که به زمینهٔ دقیق موجود فرمان/اجرای سیستم مقید است. ردیف تأیید پس از مصرف همچنان بهعنوان رکورد ممیزی باقی میماند.
ورودی ناقص HTTP/RPC که امکان احراز هویت یا شناسایی یک تأیید را ندارد، بدون تغییر رد میشود و هرگز نمیتواند تأیید کند. حکم پایانی ناقصی که برای تأییدی شناختهشده از یک مهار/منتظر مورداعتماد دریافت شود، به denied گذار میکند.
API Gateway
روشهای مستقل از نوع زیر را برای بازبین اضافه کنید:
| روش | قرارداد |
|---|---|
approval.get { id } |
نمای قابلمشاهدهٔ در انتظار یا پایانیِ نگهداریشده را برمیگرداند. |
approval.resolve { id, kind, decision } |
شناسهٔ متعارف یا ارجاع انتقال با اندازهٔ ثابت را میپذیرد، سپس اعتبارسنجی مجوزدهی، نوع و تصمیم مجاز، تطبیق مهلت و CAS پایانی را اجرا میکند. پاسخ همیشه شناسهٔ متعارف را در بر دارد. |
پس از CAS موفق، نمای ثبتشده را بیدرنگ برگردانید. رویدادهای قدیمی، ارسالکنندههای کانال و پایاندهندههای push پیگیریهایی با حداکثر تلاش هستند؛ یک سطح کند یا ناموفق نباید پاسخ برنده را بهتأخیر بیندازد یا به عقب برگرداند.
اعتبارسنجی درخواست ویژهٔ هر نوع در exec.approval.request و plugin.approval.request باقی میماند. exec.approval.get/list/waitDecision/resolve و plugin.approval.list/waitDecision/resolve موجود به آداپتورهای مرز پروتکل برای سرویس متعارف تبدیل میشوند، زیرا API منتشرشدهٔ Gateway هستند. فراخوانهای داخلی در همان تغییر به سرویس مهاجرت میکنند.
نمای بازبین یک اجتماع برچسبدار است:
type OperatorApproval = { id: string; status: OperatorApprovalStatus; presentation: | { kind: "exec"; commandText: string /* پیشنمایش امن exec */ } | { kind: "plugin"; title: string; description: string /* پیشنمایش امن Plugin */ }; // فیلدهای مشترک چرخهٔ حیات};مسیر پایدار مشتق میشود و ذخیره نمیشود. approval.get، urlPath را برمیگرداند؛ سطوحی که مبدأ عمومی تأییدشده را میشناسند ممکن است یک url مطلق نیز دریافت کنند. اسنپشاتهای بازبین کلیدهای نشست مبدأ و مخاطبان را حذف میکنند. Gateway آن کلیدهای مسیریابی را برای نمای جداگانهٔ session.approval در سمت سرور نگه میدارد.
رویدادها و کنشهای قابلانتقال
PR 1 نام رویدادها، بارها و پالایشهای موجود دریافتکنندگان در سطح رکورد را حفظ میکند:
exec.approval.requestedexec.approval.resolvedplugin.approval.requestedplugin.approval.resolved
این رویدادهای قدیمی میتوانند کل درخواست زمان اجرا را در بر داشته باشند، بنابراین نباید برای هر کلاینت محدود به تأیید منتشر شوند. PR 5 فیلدهای برچسبدار چرخهٔ حیات (status، sourceSessionKey، urlPath، فرادادهٔ پایانی و یک kind در سطح ارائه) را از طریق نمای پالایششدهٔ چرخهٔ حیات اضافه میکند، نه با گسترش تحویل رویدادهای قدیمی.
یک رویداد نمای session.approval محدود به تأیید اضافه کنید. رویداد متعارف را یکبار با کلیدهای ذخیرهشدهٔ مخاطبان منتشر کنید؛ مشترکان نشست دقیق، همان رویداد را برای هر کلید منطبق دریافت میکنند:
sessionKey: جریانی که نما را دریافت میکند.sourceSessionKey: فرزند/مبدأیی که دروازه را ایجاد کرده است.phase:pending \| terminal، با تفکیک بر اساس وضعیت تأیید.- یک نمای امن
OperatorApproval.
کلاینتها با sessions.messages.subscribe { key, agentId?, includeApprovals: true } اعلام مشارکت میکنند. پاسخ موفق یک approvalReplay شامل حداکثر 1,000 تأیید در انتظار فعلی برای همان کلید دقیق جریان اضافه میکند که کلاینت مشترک نیز در سطح رکورد مجاز به بازبینی آنهاست. truncated: false بازپخش پالایششده را مرجع میکند و کلاینتهای در حال اتصال مجدد مجموعهٔ محلی در انتظار خود را با آن جایگزین میکنند؛ truncated: true نشانهٔ اضافهبار است و کلاینتها باید ورودیهای محلیِ دیدهنشده را تا زمانی که جستوجوی متعارف یا رویدادهای بعدی چرخهٔ حیات تکلیف آنها را روشن کند، نگه دارند. یک مهلت ماندگار که بعداً هنگام بازپخش کشف شود، پیش از بازگرداندن اسنپشات جدید، سنگقبرهای پایانی را فقط برای مخاطبان مشترک و مجاز در سطح رکورد منتشر میکند. operator.admin میتواند مستقیماً اعلام مشارکت کند؛ کلاینتهای محدودتر هم به هویت دستگاه جفتشده و هم operator.approvals نیاز دارند. اشتراک نشست بهتنهایی هرگز امکان مشاهدهٔ تأیید را اعطا نمیکند.
رویداد را زیر operator.approvals در src/gateway/server-broadcast.ts ثبت کنید. نما صرفاً مشاهدهای است: هرگز ردیفهای رونوشت را اضافه نمیکند، sessions.changed منتشر نمیکند یا عاملی را بیدار نمیکند.
MessagePresentationAction را در src/interactive/payload.ts گسترش دهید:
type MessagePresentationAction = | { type: "command"; command: string } | { type: "callback"; value: string } | { type: "approval"; approvalId: string; approvalKind: "exec" | "plugin"; decision: ExecApprovalDecision; } | { type: "url"; url: string } | { type: "web-app"; url: string };Core کنشهای تصمیمگیری نوعدار و، در صورت دردسترسبودن یک مبدأ مطلق تأییدشده برای Control UI، یک پیوند جداگانه برای بازبینی میسازد. کانالها یک کنش تأیید را در قالب callback خود کدگذاری میکنند و نتیجه را به سرویس متعارف میفرستند. اگر شناسه دقیق متعارف در قالب جا شود، callback از همان استفاده میکند؛ در غیر این صورت از resolution_ref منحصربهفردِ ردیف که شامل چکیده کامل است استفاده میکند. این مرجع فقط یک کلید فشرده برای جستوجو است: احراز هویت عادی Gateway، مجوزدهی رکورد، نوع صریح، اعتبارسنجی تصمیمهای مجاز، تطبیق مهلت و CAS نخستین پاسخ همچنان اعمال میشوند. کانالها نباید شناسهها را کوتاه کنند، پیشوندهای هش را تفکیک کنند، متن /approve را تجزیه کنند یا نوع را از پیشوند شناسه استنباط کنند.
button.url، button.webApp و کنترلهای تأیید مبتنی بر فرمان را بهعنوان ورودیهای سازگاری منسوخشده SDK افزونه نگه دارید. آنها را در مرز SDK نرمالسازی کنید؛ همه فراخوانندههای داخلی همراه را در همان PR مهاجرت دهید. /approve {id} {decision} همچنان یک جایگزین متنی و فرمان CLI/گفتوگو است، نه قرارداد معنایی دکمه.
Control UI
مسیر ${basePath}/approve/{approvalId} است. شناسه تنها پارامتر مسیر است؛ هویت نشست مبدأ از رکورد میآید.
ازآنجاکه مسیریاب کنونی مسیرهای ایستای دقیق دارد و مسیرهای ناشناخته را به Chat بازنویسی میکند، این پیوند عمیق را پیش از نرمالسازی عادی مسیر در ui/src/app/bootstrap.ts تشخیص دهید. از راهاندازی عادی Gateway/احراز هویت دوباره استفاده کنید، اما یک صفحه مستقل تأیید را بیرون از پوسته نوار کناری و مودال سراسری رندر کنید.
سند متعلق به Gatewayای است که URL آن را ارائه کرده است. اتصال اولیه آن، انتخاب پایدار Gateway راهدور در برنامه کامل را نادیده میگیرد، بدون آنکه تنظیمات آن انتخاب را تغییر دهد یا کپی کند؛ فقط احراز هویت در محدوده نشستِ Gateway ارائهدهنده باقی میماند. احراز هویت بومی مورداعتماد یا یک جایگزینی gatewayUrl که جداگانه تأیید شده باشد میتواند مقصد آن را تغییر دهد. هسته، فضای نام تکبخشی /approve را پیش از مسیرهای HTTP افزونه و تشخیص افزونه ایستا رزرو میکند، ازجمله شناسههایی که به .json یا .js ختم میشوند؛ وقتی ارائه Control UI غیرفعال باشد، مسیر رزروشده با 404 بهصورت بسته شکست میخورد. صفحه را در بسته اصلی Control UI نگه دارید تا خرابی یک قطعه با بارگذاری تنبل، تصمیم امنیتی را روی نشانگر بارگذاری معطل نگذارد.
حالتهای صفحه:
- در حال بارگذاری
- احراز هویت لازم است
- در انتظار
- در حال تعیین تکلیف
- در اینجا تأیید یا رد شد
- در جای دیگری تعیین تکلیف شد
- منقضی شد
- لغو شد
- ممنوع/یافت نشد
- خطای اتصال با امکان تلاش مجدد
صفحه Gateway RPC را فراخوانی میکند، نه یک REST API احراز هویتنشده دیگر. تازهسازی مرورگر حالت پایدار را دوباره میخواند. صفحه هرگز اعتبارنامههای Gateway را در URL، پرسوجو یا قطعه قرار نمیدهد.
مجوزدهی و حریم خصوصی
URL یک مکانیاب است، نه اختیار. تعیین تکلیف نیازمند موارد زیر است:
- اتصال احرازهویتشده Gateway؛
operator.approvalsیاoperator.admin؛- مجوز بازبین در سطح رکورد.
قواعد سطح رکورد:
operator.adminمیتواند بازبینی کند.- در صورت وجود،
reviewer_device_idsمرجع قطعی است. فقط یک دستگاه جفتشدهoperator.approvalsکه در فهرست آمده باشد میتواند بازبینی کند؛ دستگاه درخواستکننده دسترسی ضمنی ندارد، مگر آنکه خودش نیز در فهرست باشد. - بدون فهرست صریح بازبینان، دستگاه جفتشده درخواستکننده
operator.approvalsمیتواند رکورد خود را بازبینی کند. - رکوردهای واقعاً قدیمی که هیچ اتصال درخواستکننده یا بازبینی ندارند، قابلیت مشاهده گسترده برای دستگاههای جفتشده را حفظ میکنند تا ارتقا، کارهای از قبل در انتظار را معطل نگذارد.
- محیطهای اجرای داخلیِ بدون دستگاه میتوانند ازطریق اتصال محدود
محیط اجرای تأیید، تعیین تکلیف کنند اما نمیتوانند بخوانند. این اختیار فقط از توکن
محیط اجرا که سرور احراز هویتش کرده است میآید؛ فیلدهای عمومی
approval.resolveنمیتوانند آن را ایجاد کنند. - مالکیت اتصال زنده درخواستکننده برای آداپتورهای قدیمی همچنان معتبر است؛ این مالکیت هرگز از نام کلاینت یکسان استنباط نمیشود.
- عضویت در مخاطبان فقط نمایش را تغییر میدهد و هرگز مجوز را گسترش نمیدهد.
approval.get فقط تصویر پالایششده بازبین را ارائه میکند و کلیدهای داخلی مسیریابی مبدأ/مخاطب را حذف میکند. رویداد session.approval در PR 5، پس از آنکه Gateway تصویر لحظهای پایدار مخاطبان را در سمت سرور اعمال کرد، مقصد یگانه sessionKey خود را بههمراه sourceSessionKey حمل میکند. رویدادهای موجود exec/افزونه، بار داده تاریخی و دریافتکنندگان محدود خود را تا زمان مهاجرت مصرفکنندگان حفظ میکنند. درخواست اجرایی، اتصال فرمان و ادامه فقط در انتظارگر محلی فرایند باقی میمانند. ردیف پایدار شامل نمایش امن بههمراه فراداده چرخه حیات، مسیریابی و ممیزی است؛ این ردیف هرگز مقادیر خام محیط، اعتبارنامهها، سرآیندهای احراز هویت یا داده callback کانال را ذخیره نمیکند.
تصویرسازی مخاطبان
مخاطبان را یکبار پیش از درج محاسبه و تصویر لحظهای مرتبشده را پایدار کنید. مالکیت یک گراف است و همیشه زنجیرهای با یک والد نیست: یک فرزند ممکن است هم کنترلکننده فعلی و هم درخواستکننده اصلی داشته باشد و این مالکان میتوانند به ریشههای متفاوتی برسند.
از پیمایش سطحاول قطعی استفاده کنید:
- صف را با کلید نشست مبدأ مقداردهی اولیه کنید.
- برای هر کلید خارجشده از صف، جدیدترین ردیف رجیستری زیرعامل را بخوانید و هر دو یال مالکیت متمایز را با ترتیب ثابت به صف بیفزایید: ابتدا
controllerSessionKeyو سپسrequesterSessionKey. - وقتی یک ردیف قابلاستفاده رجیستری وجود دارد، مسیر تبار ورودی نشست را که ممکن است پس از هدایت منقضی شده باشد نیز دنبال نکنید. در غیر این صورت، یال جایگزین فعلی و یگانه
parentSessionKey ?? spawnedByرا به صف بیفزایید. - هنگام افزودن به صف نرمالسازی و رفع تکرار کنید تا نخستین و کوتاهترین مسیر برنده شود.
- در 64 کلید یکتا متوقف شوید؛ این سقف اندازه مخاطبان، عمق پیمایش را نیز محدود میکند.
منبع رجیستری src/agents/subagent-registry-read.ts است؛ فیلدهای مالکیت در src/agents/subagent-registry.types.ts تعریف شدهاند. فیلدهای جایگزین نشست در src/config/sessions/types.ts تعریف شدهاند.
تصاویر درخواستی و پایانی از همان مخاطبان پایدار استفاده میکنند، حتی اگر مالکیت تمرکز/کنترلکننده در زمان انتظار تأیید تغییر کند. این کار پاکسازی پایانی را برای جریان نشست هر مخاطبی که تصویر درخواست را دریافت کرده است تضمین میکند. تعیین تکلیف همیشه شناسه تأیید مبدأ را هدف میگیرد؛ نشستهای مخاطب هرگز حالت تأیید شبیهسازیشده دریافت نمیکنند. پاکسازی پیام کانال هدایتشده، همچنان پیگیری جداگانه مکانیاب تحویل در بخش زیر است.
صرفاً بهدلیل یک تأیید، پیام رونوشت ننویسید، اعلانهای سیستمی تزریق نکنید، نوبتهای مالک را آغاز نکنید یا sessions.changed منتشر نکنید.
همگرایی سطوح تحویلشده
کنترلکنندههای بومی تأیید از قبل ورودیهای پیام تحویلشده خود را بهاندازه کافی نگه میدارند تا کنترلهای فعال را جایگزین یا بازنشسته کنند. پیامهای عمومی تأیید هدایتشده در حال حاضر MessageReceipt را کنار میگذارند، بنابراین تصمیمگیری در سطحی دیگر ممکن است باعث شود کنترلهای قدیمی آنها همچنان در انتظار بهنظر برسند. یک پیگیری جداگانه این شکاف را با جدول فرزند operator_approval_deliveries در پایگاه داده حالت مشترک میبندد.
هر ردیف، شناسه تأیید، یک شناسه تحویل یکتا، کانال/حساب/مسیر دقیق، یک مکانیاب خصوصی پیام کانال با اندازه محدود و اعتبارسنجیشده با JSON، زمانهای تحویل و حالت نهاییسازی را ذخیره میکند. این ردیف هرگز داده callback، توکنهای تصمیم یا درخواستهای خام تأیید را ذخیره نمیکند. کانال مالک کدگذاری مکانیاب و تغییر پیام است؛ هسته مالک وضعیت متعارف، انتخاب هدف، سیاست تلاش مجدد و متن پایانی جایگزین است.
ثبت تحویل و تعیین تکلیف نهایی با ایمنی در برابر رقابت انجام میشوند:
- پس از آنکه ارسال در انتظار رسید خود را برگرداند، مکانیاب تحویل را درج و وضعیت تأیید والد را در یک تراکنش بخوانید.
- اگر والد از قبل پایانی است، بهجای رهاکردن تحویل دیرهنگام در حالت انتظار، نهاییسازی فوری را زمانبندی کنید.
- هر گذار پایانی ثبتشده، همه ردیفهای تحویل نهایینشده را جداگانه زمانبندی میکند؛ پخشهای قابلحذف محرک نیستند.
- نهاییساز کانال
replaced،retiredیاunsupportedرا گزارش میکند. جایگزینی، پیام پایانی تکراری را سرکوب میکند؛ بازنشستگی، پیگیری پایانی موجود را میفرستد؛ پشتیبانینشدن یا شکست، بدون بازگرداندن CAS تأیید به حالت قبل، به مسیر جایگزین میرود. - هنگام راهاندازی، تأییدهای پایانی دارای تحویلهای ناتمام دوباره امتحان میشوند و پاکسازی را در برابر راهاندازی مجدد Gateway مقاوم میکنند.
این چرخه حیات انتقال، یک قلاب اختیاری آداپتور تحویل است، نه رندرکننده یا کنش پیام روبهمدل. پیامهای C2C/گروهی QQ در حال حاضر API ویرایش، حذف یا پاککردن صفحهکلید ندارند؛ آن آداپتور همچنان پشتیبانینشده باقی میماند و تا زمانی که انتقال یک API تغییر دریافت کند، فقط پس از کلیکی بعدی میتواند حقیقت متعارف را نشان دهد.
معنای راهاندازی مجدد، مهلت و مسیر
پایداری SQLite بهمعنای ازسرگیری اجرا نیست. اتصالهای فرمان/ابزار در حافظه باقی میمانند، زیرا ممکن است شامل حقایق امنیتی حساس محیط اجرا باشند و قرارداد کار قابلازسرگیری نیستند.
هنگام راهاندازی Gateway:
- یک دوره جدید محیط اجرا ایجاد کنید؛
- ردیفهای در انتظار از دورههای قدیمیتر را بهصورت اتمی، با دلیل
gateway-restart، بهcancelledمنتقل کنید؛ - ردیفها را نگه دارید تا URLهایشان توضیح دهند چه اتفاقی افتاده است؛
- هرگز یک تأیید بعدی را بدون اتصال محیط اجرای موجود اجرا نکنید.
زمانسنجها بهینهسازیهایی برای بیدارسازی هستند. مرجع مهلت در expires_at_ms ذخیره میشود؛ خواندنها، انتظارها و تعیین تکلیفها همگی تطبیق انقضا را اجرا میکنند.
رفتار سختگیرانه نهایی:
- پایان مهلت ->
expired، رد؛ - بدون مسیر ->
denied، رد؛ - لغو اجرا ->
cancelled، رد؛ - حکم مورداعتماد بدساخت ->
denied، رد؛ - فقط یک تصمیم اجازه صریحِ مجاز ->
allowed.
رفتار exec منتشرشده کنونی همچنان با این قرارداد در تعارض است:
src/agents/bash-tools.exec-host-shared.tsممکن استaskFallbackرا اعمال کند.docs/tools/exec-approvals.mdوdocs/cli/approvals.mdآن سطح را مستند میکنند.
تأییدهای افزونه اکنون در پایان مهلت و حکمهای بدساخت بهصورت بسته شکست میخورند؛ فیلد قدیمی
timeoutBehavior همچنان پذیرفته اما نادیده گرفته میشود. پیگیری معناشناسی سختگیرانه
exec باید کد، نوعها، مستندات، آزمونها و گزارش تغییرات را با هم و با
بازبینی صریح مالک/امنیت بهروزرسانی کند. askFallback میتواند هنگام مهاجرت
همچنان انتخاب سیاست پیش از گیت را توصیف کند، اما نباید پایان مهلت رکورد در انتظار
ایجادشده را به تأیید تبدیل کند.
طرح سازگاری
- پروتکل Gateway بهصورت افزایشی؛ بدون افزایش نسخه پروتکل.
- روشها و رویدادهای موجود exec/افزونه را در مرز خارجی حفظ کنید.
- شناسههای موجود، ازجمله پیشوندهای
plugin:را نگه دارید، اما استفاده از پیشوندها بهعنوان اطلاعات نوع را متوقف کنید. - رفتار فرمان متنی
/approveرا نگه دارید. - فیلدهای قدیمی URL دکمه/Web App و کنشهای فرمان را بهعنوان ورودی سازگاری SDK افزونه نگه دارید؛ خروجی جدید هسته نوعدار است.
- همه کانالهای همراه و فراخوانندههای داخلی را در همان تغییر کنش نوعدار مهاجرت دهید.
- یک ورودی گزارش تغییرات برای URL/صفحه جدید و تغییر بعدی رفتار پایان مهلت اضافه کنید.
- تنظیم حالت elicitation اضافه نکنید.
عرضه
PR 1: چرخه حیات پایدار
- این یادداشت طراحی.
- شِمای SQLite مشترک، تولید Kysely، مخزن و پاکسازی 30روزه.
- سرویس تأیید Gateway، پل انتظارگر محیط اجرا و مدیریت موارد یتیم پس از راهاندازی مجدد.
approval.get/resolveیکپارچه.- آداپتورهای روش exec/افزونه.
- آزمونهای برندهشدن نخستین پاسخ، همتوانی، انقضا، مجوزدهی و مصرف.
- هنوز هیچ تغییری در رفتار UI یا کانال ایجاد نمیشود.
PR 2: کنشهای نوعدار و callbackهای کانال
- کنشهای نوعدار تأیید، URL و Web App.
- سازندههای اصلی ارائه و خروجیهای SDK افزونه.
- کدگذاری callback خصوصیِ انتقال با نوع مالک صریح.
- ارجاعهای پایدار و با اندازهٔ ثابت callback برای شناسههای متعارفی که از محدودیتهای انتقال فراتر میروند.
- مهاجرت کانالهای همراهشده برای کنارگذاشتن استنباط از متن فرمان و شناسهٔ تأیید.
- حقیقت متعارف نخستین پاسخ در سطح کلیکشده و بهروزرسانیهای پایانیِ native فعال بهصورت بهترین تلاش؛ پایانیسازی پایدار پیام کانال همچنان کاری پیرو است.
- آزمونهای SDK و کانالهای همراهشده.
PR 3: پیوند عمیق Control UI
- صفحهٔ مستقل و احراز هویتشدهٔ تأیید و مسیریابی راهاندازی آگاه از مسیر پایه.
- اتصال به Gateway سرویسدهنده بدون تغییر انتخاب راهدور ذخیرهشدهٔ اپراتور.
- فضای نام HTTP تأیید تحت مالکیت هسته، شامل شناسههای شبیه دارایی.
- بار دادهٔ URL ساختهشده توسط Gateway و نظرسنجی وضعیت در انتظار تا زمان ارائهٔ رویدادهای چرخهٔ عمر.
- اثبات عرض موبایل، اتصال مجدد، پاسخ رقیب، بارگذاری مجدد و مسیر mountشده.
PR 4: کلاینتهای native
- سطوح بازبینی iOS و Android از
approval.get/resolveآگاه از نوع استفاده میکنند؛ watchOS درخواستها و تصمیمهای امن برای بازبین را از طریق iPhone جفتشده بازپخش میکند. - Watch تصمیمهای exec پشتیبانیشده در قرارداد بازپخش فشردهٔ خود را ارائه میدهد: یکبار اجازهدادن و ردکردن.
- حقیقت پایانی متعارف نخستین پاسخ جایگزین وضعیت محلی تصمیمِ تلاششده میشود.
- تأییدهای دریافتِ گمشده یا مبهمِ resolve، کنترلها را تا بازخوانی متعارف منجمد میکنند.
- نمونههای منتشرشدهٔ قبلی Gateway v4 بازبینی exec را از طریق fallback محدودِ متد قدیمی حفظ میکنند؛ وضعیت پایانی حفظشده میان سطوح به متدهای یکپارچه نیاز دارد.
- هشدارهای بازبین و زمینهٔ مالک در iPhone، Watch و Android قابل مشاهده میمانند.
- اثبات واحد، ساخت و پلتفرم native.
PR 5: انتشار چرخهٔ عمر به نیاکان
- تحویل در انتظار/پایانیِ
session.approvalاز snapshot مخاطبان که در PR 1 پایدار شده است. - اشتراک جلسهٔ دقیق، بازپخش اتصال مجدد و سنگقبرهای پایانی بدون تغییر transcript یا بیدارکردن عامل.
- callbackهای چرخهٔ عمر پس از درج پایدار/CAS اجرا میشوند و هرگز به مرجع تأیید تبدیل نمیشوند.
- اثبات زیرعامل تودرتو و اتصال مجدد.
PR 6: رفتار بسته در هنگام شکست
- مهاجرت
node-invoke-plugin-policy.tsو واسط افزونهٔ تعبیهشده برای کنارگذاشتن مرجع تکراری. - معناشناسی سختگیرانهٔ مهلت زمانی، دادهٔ بدشکل، نبود مسیر، اتصال و مصرفِ یکبار اجازهدادن.
- منسوخکردن تنظیمات منتشرشدهٔ سهلگیرانهٔ مهلت زمانی بدون رعایت آنها پس از در انتظار قرارگرفتن یک درخواست.
- اثبات رقابت چندسطحی و تزریق شکست.
کار پیرو: پاکسازی پایدار پیام راهدور
- پایدارسازی مکانیابهای تحویل فورواردشده و پایانیسازی همهٔ پیامهای کانال تحویلشده پس از راهاندازی مجدد.
- جدا نگهداشتن این چرخهٔ عمر انتقال از مرجع متعارف تأیید و کنشهای نوعدار ارائه.
آزمونها
پوشش متمرکز الزامی:
- بازگشایی SQLite، تصویرهای در انتظار و پایانی را حفظ میکند.
- دو resolveکنندهٔ همزمان دقیقاً یک برندهٔ CAS تولید میکنند.
- تلاش مجدد با همان تصمیم بهشکل idempotent موفق میشود؛ تلاش مجدد متعارض، برندهٔ ثبتشده را برمیگرداند.
- resolve در مهلت نهایی یا پس از آن نمیتواند تأیید کند.
allow-onceدقیقاً یکبار قابل مصرف است، بدون پاککردن وضعیت ممیزی پایانی.- راهاندازی، epochهای قدیمیتر runtime را لغو میکند.
- جستوجو و resolve غیرمجاز وجود رکورد را افشا نمیکنند.
- رفتار فهرست مجاز صریح بازبین و
operator.approvalsجفتشدهٔ عمومی. - متدهای قدیمی exec و افزونه از یک مخزن مشترک استفاده میکنند.
- schemaهای درخواست/list/get/resolve در Gateway و بارهای دادهٔ افزایشی رویداد.
- عادیسازی کنش نوعدار، رندر fallback، خروجیهای SDK و تغییر وضعیت کانالهای همراهشده.
- کدگذاری callback در Telegram شامل دادههای خصوصیِ انتقال است و هیچ استنباطی از رشتهٔ فرمان ندارد.
- مالکهای فرزند مستقیم، کنترلکننده/درخواستکنندهٔ منشعب، مالکهای تودرتو، تخصیص مجدد، fallback فیلد جلسه، چرخه و سقف اندازهٔ مخاطبان.
- آرایههای مخاطبانِ درخواستشده و پایانی یکساناند.
- تصویرهای مالک باعث هیچ تغییر transcript یا بیدارشدن عامل نمیشوند.
- مسیر Control UI در
/و یک مسیر پایهٔ پیکربندیشده کار میکند؛ تازهسازی، حقیقت در انتظار یا پایانی را نشان میدهد. - پاسخهای همزمان Control UI و Telegram یک برنده و «در جای دیگری resolve شد» را برای بازنده نشان میدهند.
- شناسههای تأیید native و شناسههای مالک Gateway، بایتهای دقیق UTF-8 را در سراسر مسیریابی و تطبیق حفظ میکنند.
- مذاکرهٔ خانوادهٔ RPC در native برای هر مسیر پذیرفتهشدهٔ Gateway یک خانوادهٔ متعارف یا قدیمی را تثبیت میکند و پس از استفاده هرگز بیسروصدا تنزل نمییابد.
- تأییدهای دریافتِ گمشدهٔ resolve در native کنشها را تا بازخوانی متعارف منجمد میکنند؛ بازخوانی ناموفق نمیتواند برندهای جعل کند یا تازهسازی Watch را تأیید کند.
- همبستگی درخواست snapshot در Watch فقط برای مالک دقیق Gateway جفتشده و یک بازخوانی متعارف تکمیلشده در iPhone پذیرفته میشود.
- اثبات مسیر کاربر از طریق Testbox/Crabbox، شامل صفحهٔ تأیید با عرض موبایل، پاکسازی کنش Telegram و یک رفتوبرگشت در انتظار/resolve/بازندهٔ دیرهنگام میان Android، iPhone و Watch.
مشاهدهپذیری
لاگهای ساختیافته و بدون محتوا برای گذارها با شناسهٔ تأیید، نوع، کلید جلسهٔ مبدأ، وضعیت، دلیل و تأخیر منتشر کنید. هرگز پیشنمایش یا اتصال خام را لاگ نکنید.
موارد زیر را ردیابی کنید:
- تعداد درخواستشده بر اساس نوع؛
- تعداد پایانی بر اساس نوع/وضعیت/دلیل؛
- سنجۀ در انتظار؛
- تأخیر درخواست تا پایان؛
- نتایج رقابت resolve: برنده، تلاش مجدد idempotent، تعارض، منقضیشده؛
- تعداد مسیر تحویل و ردکردنهای بدون مسیر؛
- لغو یتیمهای راهاندازی؛
- اندازهٔ مخاطبان.
یک گذار commitشده موفقیت محسوب میشود، حتی اگر تحویل بعدی رویداد شکست بخورد. مشترکان چرخهٔ عمر از طریق بازپخش PR 5 و جستوجوی متعارف بازیابی میشوند. پایانیسازی پایدار پیام کانال همان کار پیروی جداگانهٔ بالاست.
تصمیمهای باز
- مبدأ Control UI قابل دسترسی از بیرون. هر snapshot دارای
urlPathنسبی و پایدار است. URL مطلق فقط پس از موفقیت نمایش Gateway میتواند از یک مکان cacheشدهٔ Tailscale Serve/Funnel اعلام شود؛allowedOrigins، سرآیندهای Host درخواست،gateway.remote.urlو گزینههای loopback/LAN صرفاً نمایشی، مبدأ متعارف نیستند. Telegram میتواند از پوستهٔ Mini App احراز هویتشدهٔ خود برای حفظ مسیر تأیید طی bootstrap استفاده کند. پراکسیهای معکوس دلخواه تا زمان وجود یک قرارداد صریح URL عمومی که جداگانه بازبینی شده باشد، فقط نسبی میمانند. هرگز اجازه ندهید کانال مبدأ را حدس بزند. - گذار سازگاری مهلت زمانی سختگیرانهٔ exec. مهلتهای زمانی تأیید افزونه اکنون بهشکل بسته شکست میخورند و
timeoutBehaviorمنسوخ شده است. قرارداد منتشرشدهٔ باقیماندهٔaskFallbackپیش از آنکه پس از پایان مهلت یک درخواست در انتظار، مجوز اجرای عملیات را متوقف کند، به بازبینی صریح مالک/امنیت، changelog، مستندات و تصمیم مهاجرت/منسوخسازی نیاز دارد. - حالت تعبیهشدهٔ بدون Gateway. توصیه: در ابتدا آن را فقط محلی نگه دارید، سپس هنگامی که Gateway وجود دارد آن را به کلاینت سرویس متعارف تبدیل کنید. پیوند عمیقی را که هیچ سروری نمیتواند resolve کند، اعلام نکنید.