جلسات عن بعد
خذ URL و تيم كومر محدد من مكونات شخصية أخرى من AirCode Ø ، ثم اختر هذه الاتصال في جلسة جديدة → أسرع في. يتم استدعاء الوثيقة عن بعد Claude Code، Codex أو أحد المورد الأصلي المتوفر. يظهر المستخدم الحالي المحادثة والنتائج مع عمل المساعد عن بعد.
اختيار مكان يعيش المشروع. مع موقع المشروع → هذه الوثيقة، A يملك المشروع P1 و B يقوم بتشغيل المساعد باستخدام حساب المورد B. B يطلب الملفات التي يحتاجها من خلال أدوات مساحة العمل؛ A يقرأ، وتعديل وتشغيل الإرشادات في مكتبة P1 الفعلية. لا توجد كوكب أو المنسق النهائي. يتوفر الكود والانتقال الدعوي المطلوب إلى B ومديرها AI . إعدادات الموردين وبيانات الاستخدام في B. التخطيط والتنقيب والتأكد من أن يكون A.
A يفتح اتصال WSS معتمد إلى B ، والذي يسلط الضوء على أدوات في كل الاتجاهات. يمكن أن تكون الآلات في شبكات مختلفة أو مراكز البيانات. A لا يحتاج إلى محطة إرسال أو عنوان عام؛ B يحتاج إلى مادة HTTPS قابلة للتوصل إلى URL، ويجب أن يدعم محرر تحديثات WebSocket. يجب أن تدعم كلا الحالتين مساحات العمل المرتبطة. إغلاق المتصفح أو مشاهدة الهواتف المحمولة يترك هذه الاتصال إلى الموقع. يجب أن يبقى المضيف في العمل لإستخدام المشروع.
موقع المشروع → الوثيقة عن بعد يحافظ على الوضع الآخر: كل من الممثل والمشروع المسموح به بـ B. هذه الأنواع اثنتان صريحتان؛ اختيار مشروع يطلق عليه اسم مماثل على B لا يعني أبدا P1 على A.
اتصال اثنين من الوثائق الخاصة بك
- في الوثيقة عن بعد، قم بتواصل حساب المورد الأصلي في AI استخدام.
- افتح الإعدادات → بوابة AI، وعلى الهاتف المزيد → بوابة AI. ضمن إنشاء وصول، فعّل السماح بالاتصالات الخاصة. في إنشاء رمز، اترك نسخة أخرى محددة، وسمّ الاتصال ثم اختر إنشاء رمز اتصال. افتراضيًا تبقى الملفات والأوامر على A بينما يستخدم العامل حساب الذكاء الاصطناعي على B. للعمل على ملفات B، افتح أين تُعالج الملفات وتُنفَّذ الأوامر؟، واختر هذه النسخة ومشروعًا على B. يتيح الاختيار لكل جلسة كلا الموقعين ضمن حدود مشروع B نفسه.
- قم بتسجيل الورقة من إخراجها مرة واحدة و Base URL.
- على A، افتح الإعدادات → بوابة AI → إضافة خادم آخر. أدخل اسم الاتصال وعنوان URL للنسخة B أو Base URL والرمز، ثم اختر حفظ الاتصال. يمكنك أيضًا الإضافة من جلسة جديدة → التشغيل على → إضافة نسخة بعيدة….
- في جلسة جديدة، قم بتحديد الاتصال المخزون في Run on. تحقق من موقع المشروع يعتمد على هذا الوضع عندما يسمح بتطبيق التراكيب. حفظ أو اختيار P1 تحت مشروع في هذه الحالة، اختر الصلب → نموذج → الإنجاز → الحقوق، أدخل الإشارة ، ثم اختر بدء جلسة. المكونات العملية الحالية تدعم Claude Code و Codex. خيارات تأتي من كاتالونات B في الحي. يظل المحركات الأخرى في وضع المشروع عن بعد.
يتم إرسال الورق مرة واحدة إلى خادمك الحالي لتخزين مجهول. يستخدم عملاء الويب والعملاء الأصليين بعد ذلك مؤشر الاتصال. لا يلقى موقع الويب أي من URL أو القائمة. استخدم HTTPS للاتصال بالإنترنت أو شبكة خاصة موثوقة. تكنولوجيات Gateway هي إشارات شخصية، وليس مشاركة روابط.
يبدأ جسر المناطق العملية وأدوات MCP فقط في موقع مشروع → هذه الوثيقة مع عامل عن بعد. لا تبدأ جلسات عادية أي من هذه الخدمات. إغلاق آخر جلسة توزيع يوقف القفز الخاص، إغلاق فقط متصفح أو مشاهدة الهاتف المحمول يترك جلسة تشغيل.
Codex يحتاج حالياً إلى تسجيل الدخول المملوكة بالفيديو (auth.json) على B لهذا النظام؛ يتم رفض تسجيل الدخول فقط بالضبط. يستخدم تصميمها الخاص الروابط إلى ملف الحساب الأصلي والملفات المحادثة، دون نسخ القيمة OAuth. CLI التوافق والسياسات التي يتم إدارةها من جهاز الكمبيوتر لا تزال موجودة.
العمل مع المساعدة عن بعد
سحب إلى التطبيق وطلب أي شيء
فتح أي باب على A ، فتح نموذج (الشريط الإعدادية في المؤلف) ، ثم اختيار الاتصال B المختبر في Run on. اختر الحزام → نموذج → التفكير → الحقوق من كاتالون B. يمكنك أيضًا حفظ اتصال هناك مع Add remote instance…. كلا الحالتين تحتاج إلى إعلانات النسخة conversationWorkspace: 1، أو إطلاق بلاك B غير متوفر.
بالنسبة لـ سحابة التطبيق، وصف التطبيق واحصل على أسماء التطبيق / المشروع كما هو الحال في المعتاد. المشروع، وتخزين التطبيق، والمدير التنفيذي والتعليق يتكون من A. يتم إرسال الرسائل التالية، والتعديلات الصوتية، والقطاعات المختارة من صور الشاشة إلى عامل B. الخطة أولاً → الخادم واحد يمكن إرسال نقطة التحقق من الخطة على A؛ التفاوض تحتاج حاليا إلى هذا الوضع.
ل سؤال أي شيء، أدخل سؤالك وأي ملفات. ملف المحادثات الخاصة بها ومجموعة الملفات **** تتكون من A. ويمكن قراءة الصور والموسيقى PDF المرفقة من خلال أدوات المجالات العملية. تحويل إلى مشروع نسخ هذا ملف A إلى مشروع A عادي.
هذه الأبواب تظل عادة من خلال حوار من الهوية والموضوعية. إن فتح محادثة قد انتهت وإختيار Resume يتواصل بشكل واضح مع نفس السياق الأصلية على B؛ إن إعادة توزيع الشبكة فقط يتم إعادة توزيع القراء. لا تزال المرفقات، والإنتاج اللوحي، والترجمة المرتفعة الخاصة متاحة على A. يمكن أن تغير إعدادات المقاول إمكانيات مساحة العمل لـ A و نموذج / الجهد لـ B. يظهر المطابق B ويظهر فقط في القراءة، وتشغيل الأوامر على A ويظهر في الصوت. يتم عرض المتصفح، والتخطيط والمعدات Knowledge على الطلب من خلال القدرات A الحالية للاتصال. لا يتم إضافة مساعد أو جسر إلى محادثة محددة محددة من خلال هذه الميزة.
الصوت يظهر أحداث الترجمة الجديدة ومقاطع الحسابات المسموح بها / الأسئلة الحالية. إرسال الرسائل التالية، وإجابة على الحوار، أو استخدام Stop لتوقف الاستجابة الحالية. في وضع المرافق العملية المتصلة، يتم تطبيق الحقوق من قبل A: تشريعات القراءة فقط تمنع الكتابة والرسائل؛ تشريعات عادية تمنع في هذه المحادثة؛ تشريعات التعديل والتأكيد تسمح بتعديل الملفات ولكن تمنع عن إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال. يحدد شكل “مرافق العمل المزدوجة” الجديد المرافق المتاحة بشكل منفصل وضع الإمكانيات عند البدء، ويظهر اثنين من أبواب المحادثة أعلاه التحكم في الإمكانيات العادية لـ A. يظهر المدير المكون (مجاني فقط) إنتاج المورد الحقيقي لـ B ؛ يتم تشغيل أوامر المشروع على A من خلال أدوات منطقة العمل وتسويق إنتاجها في الصوت. في وضع المشروع عن بعد، Terminal أيضًا يوافق على الدخول الأصلية والتعديل.
يتم نقل النسخة الحية عبر WebSocket. يستخدم الشاشة استجابة SSE مستمرة وتتحدث عن الأحداث أثناء كتابة الملف؛ بعض الموردين يقومون بتسجيل رسالة متوفرة بدلا من كل تشكيل. هذا لا يفرض تحديثات الصوت على مستوى التكريم لكل CLI. /chat/completions API لا تزال تستخدم **** SSE.
يتم قراءة إعادة الاتصال على الشبكة من آخر معالج الترجمة. لا يبدأ أيًا من أي جهاز آخر أو يترك رسالة تلقائية. بعد إطلاق غير متوقع ، قم بإعادة فتح جلسة جديدة، قم بتحديد الاتصال و اقرأ في قسم محادثات عن بعد. إعادة تقييم واضح لهذه الطلبة يحافظ على هويته. لا تغيّر ID فقط لتجنب نتيجة غير متوقعة.
إغلاق النظرة يفرض فقط على القارئ. إزالة الاتصال المزودة يغلق الوصول إلى مساحة العمل ويوقف إجراءاتها الخاصة على A، ويحتفظ بـ “الإجراء”. **** يوقف المساعد ومهامه الخاصة به، ويظل الترجمة على الوجه المباشر ويمكن فتحه مرة أخرى للكتابة. بدء جلسة جديدة لتشغيل عامل مرة أخرى بعد إغلاقها. يتم تقييم المحادثات عن بعد التي تم تخزينها تحت اتصالها في جلسة جديدة. يصف هذا الفصل الشكل المزدوج من العقد الجديد بعناية. إرسال إرسال إلى التطبيق وإرسال أي شيء للحفاظ على تاريخهم المعتاد والعمل المفاجئ على A.
مقياس، الإلغاء والحدود
الرمز الذي يُنشأ من أجل تطبيق يتيح استخدام AI API للنصوص والصور فقط. عند اختيار نسخة أخرى، يمنح الإعداد الافتراضي الوصول إلى مساحة عمل النسخة التي تبدأ الجلسة. أما اختيار هذه النسخة ومشروع عند إنشاء الرمز فيسمح ببدء الجلسات في ذلك المشروع وحده وبالوصول إلى المحادثات المنشأة بهذا الرمز فقط. ولا يمنح ذلك تسجيل دخول المالك أو الإعدادات أو المحادثات الأخرى أو واجهات API العشوائية لملفات المالك أو إدارة الرموز. تستخدم أدوات الوكيل الأذونات المختارة عند البدء؛ واختيار مشروع ليس عزلاً على مستوى نظام التشغيل.
تُمنح إمكانية الوصول إلى مساحات العمل المتصلة افتراضيًا للرموز الجديدة من نوع نسخة أخرى؛ ولا تُضاف تلقائيًا إلى الرموز القديمة. يرتبط كل إطلاق على A بحدوث شخصية مشروع واحد، والاتصال، ووسائل التمييز، والتطلع. B ليس لديه API لتغيير هذا الجذع. مسارات الملفات متصلة ، يتم رفض الترددات الخارجية ، وتتطلب الكتابات SHA-256 الملاحظة عند قراءة الملف. يتم رفض الإصدارات المتكررة أو غير واضحة. لديها إمكانيات المستخدم A، و Project cwd ليس حزمة صغيرة من نظام التشغيل.
تأثيرات المناخ العمل لديها رسوم مستمرة. يتم إعادة الاتصال مرة أخرى فقط على نفس هوية العمل وتعود النتيجة المكتوبة. إصابة المورد أثناء تأثير تقول عدم اليقين بدلا من إعادة تشغيلها. يوقف الأوامر المملوكة عند خروج خادم A ، عندما يتم إلغاء الوصول ، أو عندما تنتهي العقد. تحقق من تأثيرات إجراءات غير آمنة قبل طلب إجراءات جديدة. إعادة تشغيل أي من الوثائق لا تنشئ أي من الموردين الآخرين أو إعادة تشغيل إرسال.
تكرار تكرار تكرار تكرار بعد 90 يوما. إن إلغاء التقييم أو إلغاء الوسائط الجانبية يلغي سلطة الجلسة وتوقف العاملين فيها. يتم التحقق من إجازة في كل عملية وبحسب التحقق من الخلفية المحددة ، بما في ذلك بعد إعادة تشغيل المورد. إزالة اتصال مُخزٍ لا يُنقذ الكترونات الأصلية: إزالةها في الحالة المرسلة إذا كنت تريد إزالة السلطة.
هناك ما يصل إلى 20 جلسات مسدودة نشطة لكل حالة إرسال وربما أربعة قراء متواصلة لكل نقطة، مع 20 قراء في جميع أنحاء مسدودة. يستخدم كل حفل يفتتح قراءة واحد، ويمكن إضافة آخر من قراءته في قناة Open Terminal. هذه هي حدود الموارد المحلية، وليس ميزات التسجيل. لا تزال متطلبات الموردين والمتوافقين متوافقة، ولا يتم تثبيت أو تحديث أي من الموردين CLI عن طريق الاتصال.
تصل مساحة العمل المرتبطة إلى ما يصل إلى 20 قناة أدوات مستمرة على B وربما أربعة لكل نقطة، مع أربعة دعوات في الرحلة وربما أربعة أوامر المشروع المستمرة لكل مساحة العمل. A يتيح 20 مساحة عمل فعالة و 20 إجراءات في مجموعها. يتم الحد من الأقراص إلى 2 ميغابايت، الملفات المعتادة / نتائج التعديلات إلى 8 ميغابايت، الاتصال إلى 20،000 لكل مساحة العمل، والرسائل إلى عشرة دقائق. يجيب الأداة على محتوى ملف الصفحات وتحديدًا رصيفات الإرشادات المقطوعة. القنوات القراءة هي في حد أقصى 128 كوباغرام؛ الأدلة النصية القصيرة / القصيرة لديها حدود التخطيط الخاصة بها ويجب أن تتوافق في إطار واحد. هذه هي حدود الموارد المحلية، ولا يتم إنشاء أي مصدر عام إضافي.
جلسة API لـ backend الشخصي أو أداة AI
جلسة OpenAPI التخطيط يصف هذا البروتوكول. استخدم Authorization: Bearer TOKEN في كل مكالمة إلى /api/ai-gateway/v1 Base URL من الوثيقة المرسلة. لا تستخدم إشارات URL/query. لا تضع الورقة في المطالبات أو الكود أو تخزين المتصفح.
| نقطة انطلاق | معنى |
|---|---|
GET /session-catalog |
نسخة بروتوكول، مشروع مبرم، workspaceAccess، ومجموعة المحرك الحي / النماذج / التفكير / التمييز |
POST /sessions |
بدء مكتب أساسي؛ المطلوب UUID intention, engine, model, permissionMode, initialPrompt; المطلوب effort, name |
GET /sessions |
ما يصل إلى 100 محادثات حديثة تم إنشاؤها من خلال هذا الترتيب |
GET /sessions/{id} |
خلاصة الحدث الحالي للاتحاد الذي يحتوي على هذا الترتيب |
POST /sessions/{id}/resume |
استمرار واضح لمناقشة عملية مع UUID intention, initialPrompt، model/effort، نفس السياق الداخلي B |
POST /sessions/{id}/events |
Continuous SSE; JSON { "offset": null } في البداية ، ثم العودة الأخيرة على التفاعل مرة أخرى |
POST /sessions/{id}/transcript |
اقرأ صفحة اسمية ، إرسال offset لأحداث جديدة أو before لأخبار قديمة |
POST /sessions/{id}/input |
التتبع: UUID intention و text |
POST /sessions/{id}/interaction |
UUID intention، والاتصال الحالي id، والعديد من الخيارات المتاحة / النص / رد |
POST /sessions/{id}/command |
UUID intention و action: interrupt, close, model, effort أو permission; تغييرات الاختيار تتطلب arg من الكاتالوني |
GET /sessions/{id}/terminal |
تحديث WebSocket مصممة؛ إنتاج المقطع الخام، JSON إنتاج { "t": "i", "d": "text" }، تعديل حجم { "t": "r", "c": 100, "r": 30 } |
GET /workspaces/{id} |
استرداد جلسة B بواسطة المجال العمل الأصلي UUID؛ لا تخلق بديل على رد فعل خاسر |
GET /workspaces/{id}/channel |
A يفتح WebSocket معتمدة إلى B قبل البدء في البدء؛ B يرسله دعوات أدوات مجال العمل، A يعود إلى نتائجها |
بالنسبة إلى جسر A-workspace المدمج ، استخدم A من API الممثل من المستخدم الحالي: حفظ B مع POST /api/ai-gateway/connections، اكتشاف الخيارات مع GET /api/engines?remoteConnectionId=ID&workspaceLocation=local، ثم إرسال POST /api/ai-gateway/connections/ID/sessions مع localProject: "P1" و اختيار الكاتالون. A يوفر هوية منطقة العمل وقطع قناة. يفتح اسم agremote_… المشتري اسم A /api/ai-gateway/remote-sessions/NAME/events و /interaction، بما في ذلك التأكيدات على A. لا يمكن لـ “B” لـ “Gateway Token” أن تمنح هذه النقطة النهائية للمالك. النتيجة التالية تؤكد هذه الفرق الكاملة.
بالنسبة لـ سحب إلى التطبيق و سؤال أي شيء، استخدمت POST /api/sessions المستحقة من المالك العادي على A مع اختيار remoteConnectionId: "SAVED_CONNECTION_ID" و B من الكاتالونات. استخدم askAnything: true لـ سؤال أو project و createMode: true لبرنامج تطبيقي موجود. إنشاء تطبيق جديد يستخدم أولاً POST /api/create-mode/projects/confirmed مع prompt، appName و projectName. الحفاظ على idempotencyKey من خلال إطلاق غير متوقع. يضم الإجابة المعتادة tmuxName، sessionId و remoteAgent الملفات. تستخدم التتبع والترجمة والمنطقة العملية والرسائل الدقيقة API الحوار العادي A ؛ لا بد من استبدال تيم كروز لقياس مالك A.
إذا تم تنفيذ خادم الاتصال منفصل ، فانظر بروتوكول منطقة العمل وملفات الأدوات. إطلاق على B مع workspace: { id: UUID, project: "P1" } فقط بعد تنفيذ القناة، والروح المحلية المرتبطة، والإمكانيات، والرسائل العملياتية المستمرة. لا يتيح العلامة نفسها الوصول إلى الملف. يتم إلغاء تغييرات الدخول المحدد لـ B و تغييرات الإمكانيات الأصلية في هذه الجلسات.
SSE update يحتوي على { id, project, session, transcript }. transcript.events هو صفحة الأحداث الجديدة ، offset هو مدفوع عدد الناتج المحلي غير المباشر ، و interaction أو terminalAttention يصف احتياجات الدخول الحالية. الحفاظ على المراكز SSE الكاملة في جميع أنحاء الشبكة. التعليقات على الحرس الثوري في حدث error ، قم بتخزين المحادثة وإعادة الاتصال للمراجعة. لا تقبل أيضًا أن العمل لم يحدث من ردة الشبكة المفقودة.
تتمكن من الحصول على الوظائف المسموح بها، والعلامات النموذجية والمحاولات من /session-catalog، وليس من هاتين الصورتين API. إنشاء الجلسة وإجراءات يرفض المجالات غير المستخدمة. يتم إدخال الدعوات الأوليّة والمتواصلة إلى 64 كابيتال. يتم توزيع التفكير في إطلاق النار إلى اليمين ، وتتراوح التفكير في العمل إلى جلسة. يتم رفض إعادة استخدام إرادة مع محتوى مختلف. بعد محاولة إرسال الوقت المحدد ، لا يزال النتيجة غير معروفة AI_GATEWAY_SESSION_UNCERTAIN ولا يمكن إعادة تشغيلها تلقائيا.
بالنسبة إلى جدران التمويل، انتظر استعادة الشركة الفعلية؛ لا يزال وقت إعادة الإعداد غير معروف. بالنسبة إلى AI_GATEWAY_SESSION_ATTENTION، إجابة على الحوار الحالي. بالنسبة إلى الوصول المزمع إلغاءه/الخسارة، قم بتشكيل تيم كوم جديد في الحالة المرسلة. بالنسبة إلى النماذج المفقودة، قم بتعليق الكاتالوني. لا يمكن قبول محادثات المالك الحالي مع تكنولوجيا جديدة.
إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال إرسال
يحافظ على نفس المالك API على الملفات والموضوعات ومقاطع الفيديو على A في حين يتم تشغيل الاتصال B المتصفح.
// Node.js 22+. Private backend only; never send these credentials to the website.
const origin = new URL(process.env.AIRCODE_LOCAL_ORIGIN);
const ownerToken = process.env.AIRCODE_LOCAL_AUTH_TOKEN;
const connectionId = process.env.AIRCODE_REMOTE_CONNECTION_ID;
const idempotencyKey = process.env.AIRCODE_REMOTE_INTENTION;
if (!['https:', 'http:'].includes(origin.protocol) || origin.username || origin.password
|| origin.pathname !== '/' || origin.search || origin.hash
|| !ownerToken || !connectionId || !idempotencyKey) throw new Error('Configure A, its saved B connection and one retained intention UUID');
async function call(path, body) {
const response = await fetch(origin.origin + '/api' + path, {
method: body === undefined ? 'GET' : 'POST', redirect: 'error',
headers: { authorization: 'Bearer ' + ownerToken, 'content-type': 'application/json' },
...(body === undefined ? {} : { body: JSON.stringify(body) }),
});
if (!response.ok) throw new Error('A refused the operation: HTTP ' + response.status);
return response.json();
}
const catalog = await call('/engines?remoteConnectionId=' + encodeURIComponent(connectionId) + '&workspaceLocation=local');
const engine = process.env.AIRCODE_REMOTE_ENGINE, model = process.env.AIRCODE_REMOTE_MODEL;
const permissionMode = process.env.AIRCODE_REMOTE_PERMISSION;
if (!engine || model === undefined || !permissionMode) {
console.log(JSON.stringify(catalog, null, 2));
throw new Error('Choose harness, model and permissions from B’s catalogue');
}
const selection = { remoteConnectionId: connectionId, engine, model, permissionMode,
...(process.env.AIRCODE_REMOTE_EFFORT ? { effort: process.env.AIRCODE_REMOTE_EFFORT } : {}) };
// Set AIRCODE_APP_PROJECT only for an existing A application project.
// To create one first, POST /create-mode/projects/confirmed with confirmed
// { projectName, appName, prompt }, then retain its returned name.
const project = process.env.AIRCODE_APP_PROJECT;
const session = await call('/sessions', { ...selection, idempotencyKey,
...(project ? { project, createMode: true } : { askAnything: true }),
initialPrompt: process.env.AIRCODE_PROMPT || 'Explain how this distributed conversation works.',
});
console.log('Conversation on A:', session.tmuxName, 'Agent:', session.remoteAgent.instanceName);
// Read-only observation; never replay the launch or prompt to reconnect.
const transcript = await call('/sessions/' + encodeURIComponent(session.tmuxName) + '/transcript');
for (const event of transcript.events) if (event.text) console.log(event.text);
// Poll the same route with ?offset=LAST_OFFSET for incremental updates.
// Send an explicit follow-up to POST /sessions/NAME/input with { text }.
// Resume explicitly with POST /sessions { project: session.project,
// resumeSessionId: session.sessionId, initialPrompt: 'Continue' }.
مشروع على A، المفوض على B
استخدم إدعاء جلسة صاحب A في backend الخاص. يتم تخزين تيم كروز B على A بالفعل ولا يمكن استبدال هذه الوثيقة للمالك. اختر قيمة الكاتدرائية واحتفظ بـ UUID إرادة إطلاق واحدة.
import { randomUUID } from 'node:crypto';
// A owns P1. B supplies the agent/account. Never send these values to the website.
// Save B once in A's Settings → AI gateway → Remote instances.
const origin = process.env.AIRCODE_LOCAL_ORIGIN;
const ownerToken = process.env.AIRCODE_LOCAL_AUTH_TOKEN;
const connectionId = process.env.AIRCODE_REMOTE_CONNECTION_ID;
if (!origin || !ownerToken || !connectionId) throw new Error('Configure A and its saved connection to B');
const url = new URL(origin);
if (!['https:', 'http:'].includes(url.protocol) || url.username || url.password
|| url.search || url.hash || url.pathname !== '/') throw new Error('Use A’s instance origin');
async function call(path, body, signal) {
const response = await fetch(url.origin + path, {
method: body === undefined ? 'GET' : 'POST', redirect: 'error', signal,
headers: { authorization: 'Bearer ' + ownerToken, 'content-type': 'application/json' },
...(body === undefined ? {} : { body: JSON.stringify(body) }),
});
if (!response.ok) throw new Error('A refused the operation: HTTP ' + response.status);
return response;
}
const id = encodeURIComponent(connectionId);
const catalog = await (await call('/api/engines?remoteConnectionId=' + id + '&workspaceLocation=local')).json();
const engine = process.env.AIRCODE_REMOTE_ENGINE;
const model = process.env.AIRCODE_REMOTE_MODEL;
const permissionMode = process.env.AIRCODE_REMOTE_PERMISSION;
if (!engine || model === undefined || !permissionMode) {
console.log(JSON.stringify(catalog, null, 2));
throw new Error('Choose harness, model and permissions from B’s catalogue');
}
// Persist the intention before launch and reuse it if its response is lost.
const intention = process.env.AIRCODE_REMOTE_INTENTION;
if (!intention) throw new Error('Set AIRCODE_REMOTE_INTENTION to a new UUID once, then retain it');
const session = await (await call('/api/ai-gateway/connections/' + id + '/sessions', {
intention, localProject: process.env.AIRCODE_LOCAL_PROJECT || 'P1',
engine, model, permissionMode, effort: process.env.AIRCODE_REMOTE_EFFORT || '',
initialPrompt: 'Read the project instructions, then explain which machine owns its files.',
})).json();
console.log('Conversation on A:', session.tmuxName);
const path = '/api/ai-gateway/remote-sessions/' + encodeURIComponent(session.tmuxName);
// Approvals from A appear as transcript.interaction with id "workspace:…".
// Only after the owner chooses an answer, send it to A:
async function answerCurrentInteraction(interactionId, option) {
return call(path + '/interaction', { intention: randomUUID(), id: interactionId, option });
}
// This bounded reader displays complete events and pending owner decisions.
// It never approves an operation automatically.
const signal = AbortSignal.timeout(30_000);
const stream = await call(path + '/events', { offset: null }, signal);
const decoder = new TextDecoder(); let buffer = '';
try {
for await (const chunk of stream.body) {
buffer += decoder.decode(chunk, { stream: true }).replace(/\r\n/g, '\n');
let boundary;
while ((boundary = buffer.indexOf('\n\n')) >= 0) {
const frame = buffer.slice(0, boundary); buffer = buffer.slice(boundary + 2);
const data = frame.split('\n').filter(line => line.startsWith('data:'))
.map(line => line.slice(5).trimStart()).join('\n');
if (!data) continue;
const update = JSON.parse(data);
console.log(JSON.stringify(update));
// Display update.transcript.interaction and let the owner choose an answer.
}
}
} catch (error) { if (!signal.aborted) throw error; }
// Disconnecting the reader leaves the agent and workspace connection running.
// An explicit POST path + '/command' with {intention:UUID,action:'close'} stops them.
جلسة حية على مشروع B
Node.js 22 أو أحدث. إعداد AIRCODE_REMOTE_BASE_URL و AIRCODE_REMOTE_TOKEN في تصميم المورد الخاص. يتم إدخال القائمة الأولى حتى تختار أيضًا AIRCODE_REMOTE_ENGINE، AIRCODE_REMOTE_MODEL و AIRCODE_REMOTE_PERMISSION. AIRCODE_REMOTE_EFFORT هو إمكاني. يتم إغلاق القارئ بعد 30 ثانية، ويظل المنشط عن بعد.
import { randomUUID } from 'node:crypto';
// Node.js 22+. Configure these privately on your application's server.
const base = process.env.AIRCODE_REMOTE_BASE_URL;
const token = process.env.AIRCODE_REMOTE_TOKEN;
if (!base || !token) throw new Error('Configure the remote Base URL and token');
const url = new URL(base);
if (!['https:', 'http:'].includes(url.protocol) || url.username || url.password
|| url.search || url.hash || url.pathname !== '/api/ai-gateway/v1') {
throw new Error('Use the instance Base URL without a trailing slash');
}
async function call(path, body, signal) {
const response = await fetch(base + path, {
method: body === undefined ? 'GET' : 'POST', redirect: 'error', signal,
headers: { authorization: 'Bearer ' + token, 'content-type': 'application/json' },
...(body === undefined ? {} : { body: JSON.stringify(body) }),
});
if (!response.ok) throw new Error('Remote request refused: HTTP ' + response.status);
return response;
}
const catalog = await (await call('/session-catalog')).json();
const engine = process.env.AIRCODE_REMOTE_ENGINE;
const model = process.env.AIRCODE_REMOTE_MODEL;
const permissionMode = process.env.AIRCODE_REMOTE_PERMISSION;
if (!engine || model === undefined || !permissionMode) {
console.log(JSON.stringify(catalog, null, 2));
throw new Error('Choose harness, model and permissions from this catalogue');
}
// Keep this intention for an explicit retry of this exact launch request.
const intention = process.env.AIRCODE_REMOTE_INTENTION || randomUUID();
const session = await (await call('/sessions', {
intention, engine, model, permissionMode,
effort: process.env.AIRCODE_REMOTE_EFFORT || '',
initialPrompt: 'Describe the project without changing its files.',
})).json();
console.log('Remote conversation:', session.id);
// Demonstrate a bounded live reader. Disconnecting leaves the agent running.
const signal = AbortSignal.timeout(30_000);
const stream = await call('/sessions/' + session.id + '/events', { offset: null }, signal);
const decoder = new TextDecoder();
try {
for await (const chunk of stream.body) process.stdout.write(decoder.decode(chunk, { stream: true }));
} catch (error) { if (!signal.aborted) throw error; }