@agent-native/core 0.98.6 → 0.98.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +6 -0
- package/corpus/core/docs/content/external-agents.mdx +15 -11
- package/corpus/core/docs/content/locales/ar-SA/external-agents.mdx +3 -1
- package/corpus/core/docs/content/locales/de-DE/external-agents.mdx +3 -1
- package/corpus/core/docs/content/locales/es-ES/external-agents.mdx +3 -1
- package/corpus/core/docs/content/locales/fr-FR/external-agents.mdx +3 -1
- package/corpus/core/docs/content/locales/hi-IN/external-agents.mdx +3 -1
- package/corpus/core/docs/content/locales/ja-JP/external-agents.mdx +3 -1
- package/corpus/core/docs/content/locales/ko-KR/external-agents.mdx +3 -1
- package/corpus/core/docs/content/locales/pt-BR/external-agents.mdx +3 -1
- package/corpus/core/docs/content/locales/zh-CN/external-agents.mdx +3 -1
- package/corpus/core/docs/content/locales/zh-TW/external-agents.mdx +3 -1
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/handlers.ts +6 -5
- package/corpus/core/src/a2a/task-store.ts +63 -0
- package/corpus/core/src/client/session-replay.ts +67 -5
- package/corpus/core/src/integrations/adapters/slack.ts +99 -19
- package/corpus/core/src/integrations/identity.ts +103 -39
- package/corpus/core/src/integrations/plugin.ts +81 -9
- package/corpus/core/src/integrations/types.ts +30 -0
- package/corpus/core/src/integrations/webhook-handler.ts +1 -0
- package/corpus/core/src/mcp/builtin-tools.ts +5 -2
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +4 -1
- package/corpus/templates/analytics/AGENTS.md +9 -9
- package/corpus/templates/analytics/changelog/2026-07-12-analytics-uses-the-full-in-app-agent-for-multi-step-incident.md +6 -0
- package/corpus/templates/analytics/server/handlers/session-replay.ts +15 -9
- package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +23 -5
- package/corpus/templates/analytics/server/lib/session-replay.ts +26 -5
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +4 -1
- package/corpus/templates/analytics/server/routes/api/session-replay/agent-diagnostics.json.get.ts +8 -5
- package/corpus/templates/analytics/server/routes/api/session-replay/agent-events.json.get.ts +8 -3
- package/corpus/templates/calendar/app/components/calendar/DeleteEventDialog.tsx +12 -2
- package/dist/a2a/handlers.d.ts.map +1 -1
- package/dist/a2a/handlers.js +6 -6
- package/dist/a2a/handlers.js.map +1 -1
- package/dist/a2a/task-store.d.ts +12 -0
- package/dist/a2a/task-store.d.ts.map +1 -1
- package/dist/a2a/task-store.js +54 -0
- package/dist/a2a/task-store.js.map +1 -1
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +53 -8
- package/dist/client/session-replay.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/integrations/adapters/slack.d.ts.map +1 -1
- package/dist/integrations/adapters/slack.js +85 -11
- package/dist/integrations/adapters/slack.js.map +1 -1
- package/dist/integrations/identity.d.ts +19 -2
- package/dist/integrations/identity.d.ts.map +1 -1
- package/dist/integrations/identity.js +76 -34
- package/dist/integrations/identity.js.map +1 -1
- package/dist/integrations/plugin.d.ts.map +1 -1
- package/dist/integrations/plugin.js +56 -6
- package/dist/integrations/plugin.js.map +1 -1
- package/dist/integrations/types.d.ts +25 -0
- package/dist/integrations/types.d.ts.map +1 -1
- package/dist/integrations/types.js.map +1 -1
- package/dist/integrations/webhook-handler.js +1 -0
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/mcp/builtin-tools.d.ts.map +1 -1
- package/dist/mcp/builtin-tools.js +5 -2
- package/dist/mcp/builtin-tools.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/docs/content/external-agents.mdx +15 -11
- package/docs/content/locales/ar-SA/external-agents.mdx +3 -1
- package/docs/content/locales/de-DE/external-agents.mdx +3 -1
- package/docs/content/locales/es-ES/external-agents.mdx +3 -1
- package/docs/content/locales/fr-FR/external-agents.mdx +3 -1
- package/docs/content/locales/hi-IN/external-agents.mdx +3 -1
- package/docs/content/locales/ja-JP/external-agents.mdx +3 -1
- package/docs/content/locales/ko-KR/external-agents.mdx +3 -1
- package/docs/content/locales/pt-BR/external-agents.mdx +3 -1
- package/docs/content/locales/zh-CN/external-agents.mdx +3 -1
- package/docs/content/locales/zh-TW/external-agents.mdx +3 -1
- package/package.json +1 -1
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.98.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- b94940e: Keep replay recording recoverable across transient upload failures and tab races, preserve object resources, and prevent late A2A workers from overwriting terminal task states.
|
|
8
|
+
|
|
3
9
|
## 0.98.6
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
|
@@ -417,17 +417,21 @@ rejected.
|
|
|
417
417
|
|
|
418
418
|
The default Slack integration follows the same rule. A verified Slack DM is
|
|
419
419
|
matched to an existing Agent Native organization member and persisted as a
|
|
420
|
-
workspace/user identity link before the agent runs
|
|
421
|
-
context loads that user's resources, instructions, and skills.
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
420
|
+
workspace/user identity link before the agent runs, and the resulting user/org
|
|
421
|
+
context loads that user's resources, instructions, and skills. Hydrated
|
|
422
|
+
workspace members whose email is missing or not yet an organization member run
|
|
423
|
+
as an anonymous org-scoped service principal instead — the same
|
|
424
|
+
org-wide-visibility tier shared channels get — with an agent-visible note and
|
|
425
|
+
a one-time Slack heads-up explaining how to gain personal access. Hydration
|
|
426
|
+
failures, guests and external (Slack Connect) members, and workspaces not
|
|
427
|
+
connected to an organization receive a polite decline reply instead of a
|
|
428
|
+
silent drop. Shared Slack channels use a service principal instead of
|
|
429
|
+
borrowing one participant's private permissions. Managed Slack OAuth requests
|
|
430
|
+
the `users:read.email` bot scope, and the generated Slack app manifest
|
|
431
|
+
requests it too. Existing Slack installs must be reconnected/reinstalled to
|
|
432
|
+
grant a newly added scope; legacy bot-token installs must add the scope in
|
|
433
|
+
Slack manually. Without it, DMs run in the anonymous org-scoped tier rather
|
|
434
|
+
than with personal access.
|
|
431
435
|
|
|
432
436
|
### Full tier (explicit opt-in only) {#full-tier}
|
|
433
437
|
|
|
@@ -402,7 +402,9 @@ export default createAgentChatPlugin({
|
|
|
402
402
|
|
|
403
403
|
لا يتم تضمين `db-schema` و`db-query` الأساسيين تلقائيًا في القراءات الخارجية. يظلان متاحين لوكيل التطبيق داخل التطبيق عبر مسار SQL المقيّد المعتاد، لكن الوصول العام إلى المخطط وSQL واسع جدًا لاستنتاجه من بيانات القراءة فقط. إذا احتاج التطبيق إلى استعلام خارجي مباشر، فعليه إضافة action مملوك للتطبيق بحدود وصول واضحة أو قائمة سماح صريحة للجداول والأعمدة مع حدود للصفوف والبايت والمهلة والتدقيق. يبقى `db-exec` و`db-patch` خارج السطح التلقائي.
|
|
404
404
|
|
|
405
|
-
|
|
405
|
+
هذا استبعاد صارم يعتمد على الاسم، وليس مجرد إغفال في البيانات الوصفية: أدوات قاعدة البيانات وseed وجلسة المتصفح والامتدادات وسياق الأشعة السينية العامة لا يُكشف عنها تلقائيًا أبدًا، حتى لو وسم تغيير مستقبلي إحداها عن طريق الخطأ بمجموعة أعلام القراءة المصادق عليها الكاملة — فهي تتطلب دائمًا إدخالًا صريحًا في `connectorCatalog`.
|
|
406
|
+
|
|
407
|
+
تطابق رسائل Slack الخاصة التي تم التحقق منها عضوًا موجودًا في مؤسسة Agent Native، وتحفظ رابط هوية مساحة العمل/المستخدم قبل تشغيل الوكيل؛ ثم يحمّل سياق المستخدم/المؤسسة الناتج موارد ذلك المستخدم وتعليماته ومهاراته. أما أعضاء مساحة العمل الذين نجحت عملية جلب بياناتهم لكن بريدهم الإلكتروني مفقود أو لم ينضموا بعد إلى المؤسسة، فيعملون بهوية خدمة مجهولة ضمن نطاق المؤسسة — وهي طبقة الرؤية نفسها على مستوى المؤسسة التي تستخدمها القنوات المشتركة — مع ملاحظة يراها الوكيل وتنبيه لمرة واحدة في Slack يشرح كيفية الحصول على وصول شخصي. تتلقى حالات فشل جلب البيانات والضيوف والأعضاء الخارجيون (Slack Connect) ومساحات العمل غير المرتبطة بمؤسسة ردًا مهذبًا بالرفض بدل إسقاط الرسالة بصمت. تستخدم قنوات Slack المشتركة هوية خدمة ولا تستعير الأذونات الخاصة لأي مشارك. يطلب OAuth المُدار والنموذج المُنشأ لتطبيق Slack نطاق البوت `users:read.email`. يجب إعادة ربط/تثبيت عمليات التثبيت الحالية لمنح نطاق أُضيف حديثًا، بينما يجب أن تضيف عمليات التثبيت القديمة التي تستخدم رمز بوت النطاق يدويًا في Slack. من دونه، تعمل الرسائل الخاصة ضمن الطبقة المجهولة ذات نطاق المؤسسة بدل الوصول الشخصي.
|
|
406
408
|
|
|
407
409
|
### الطبقة الكاملة (الاشتراك الصريح فقط) {#full-tier}
|
|
408
410
|
|
|
@@ -404,7 +404,9 @@ Die Richtlinie ist authentifiziert, nicht anonym. Die MCP-OAuth-/Connect-Identit
|
|
|
404
404
|
|
|
405
405
|
Core-`db-schema` und `db-query` werden nicht automatisch als externe Lesezugriffe veröffentlicht. Sie bleiben für den internen App-Agenten über den normalen, abgegrenzten SQL-Pfad verfügbar, aber ein breiter Schema-/SQL-Zugriff ist zu mächtig, um ihn allein aus Read-only-Metadaten abzuleiten. Benötigt eine App direkte externe Abfragen, muss sie eine eigene GET-Action mit Zugriffsprüfungen und festen Grenzen oder eine explizite Tabellen-/Spalten-Allowlist mit Zeilen-, Byte-, Zeit- und Audit-Limits bereitstellen. `db-exec` und `db-patch` bleiben außerhalb der automatischen Oberfläche.
|
|
406
406
|
|
|
407
|
-
|
|
407
|
+
Dies ist ein harter, namensbasierter Ausschluss und keine bloße Auslassung in den Metadaten: generische Datenbank-, Seed-, Browser-Sitzungs-, Erweiterungs- und Kontext-Röntgen-Tools werden niemals automatisch freigegeben, selbst wenn eine zukünftige Änderung eines davon versehentlich mit dem vollständigen Satz an Flags für authentifizierte Lesezugriffe versieht — sie benötigen immer einen expliziten `connectorCatalog`-Eintrag.
|
|
408
|
+
|
|
409
|
+
Eine verifizierte Slack-DM wird vor dem Start des Agenten einem bestehenden Mitglied der Agent-Native-Organisation zugeordnet und als Identitätsverknüpfung zwischen Workspace und Benutzer gespeichert. Der daraus entstehende Benutzer-/Organisationskontext lädt die Ressourcen, Anweisungen und Skills dieses Benutzers. Erfolgreich geladene Workspace-Mitglieder, deren E-Mail-Adresse fehlt oder die noch kein Organisationsmitglied sind, werden stattdessen als anonymer, auf die Organisation begrenzter Service-Principal ausgeführt — mit derselben organisationsweiten Sichtbarkeit wie gemeinsame Kanäle. Der Agent erhält dazu einen sichtbaren Hinweis, und Slack erklärt einmalig, wie persönlicher Zugriff erlangt werden kann. Bei fehlgeschlagener Profilladung, Gästen und externen Mitgliedern (Slack Connect) sowie Workspaces ohne Organisationsverknüpfung wird höflich abgelehnt, statt die Nachricht stillschweigend zu verwerfen. Gemeinsame Slack-Kanäle verwenden einen Service-Principal und übernehmen keine privaten Berechtigungen eines Teilnehmers. Managed OAuth und das generierte Slack-App-Manifest fordern den Bot-Scope `users:read.email`. Bestehende Installationen müssen erneut verbunden/installiert werden, um einen neu hinzugefügten Scope zu gewähren; ältere Bot-Token-Installationen müssen ihn in Slack manuell hinzufügen. Ohne diesen Scope werden DMs in der anonymen, organisationsbegrenzten Stufe statt mit persönlichem Zugriff ausgeführt.
|
|
408
410
|
|
|
409
411
|
### Vollständige Stufe (nur explizites Opt-in) {#full-tier}
|
|
410
412
|
|
|
@@ -404,7 +404,9 @@ La política es autenticada, no anónima. La identidad de MCP OAuth/connect se c
|
|
|
404
404
|
|
|
405
405
|
`db-schema` y `db-query` de Core no se exponen automáticamente como lecturas externas. Siguen disponibles para el agente interno de la aplicación mediante el flujo SQL con ámbito habitual, pero el acceso amplio al esquema/SQL es demasiado potente para inferirlo solo de metadatos de lectura. Si una aplicación necesita consultas externas directas, debe exponer una action GET propia con controles y límites de acceso, o una lista explícita de tablas/columnas con límites de filas, bytes, tiempo y auditoría. `db-exec` y `db-patch` quedan fuera de la superficie automática.
|
|
406
406
|
|
|
407
|
-
|
|
407
|
+
Se trata de una exclusión estricta basada en el nombre, no solo de una omisión de metadatos: las herramientas genéricas de base de datos, seed, sesión del navegador, extensiones y rayos X de contexto nunca se exponen automáticamente, aunque un cambio futuro anote alguna de ellas por error con el conjunto completo de indicadores de lectura autenticada — siempre requieren una entrada explícita en `connectorCatalog`.
|
|
408
|
+
|
|
409
|
+
Antes de ejecutar el agente, un mensaje directo verificado de Slack se asocia a un miembro existente de la organización de Agent Native y se guarda el vínculo de identidad entre el espacio de trabajo y el usuario. El contexto resultante del usuario y la organización carga los recursos, las instrucciones y las habilidades de ese usuario. Los miembros del espacio de trabajo cuyo perfil se ha obtenido correctamente, pero que no tienen correo electrónico o aún no pertenecen a la organización, se ejecutan como un principal de servicio anónimo limitado a la organización: el mismo nivel de visibilidad para toda la organización que usan los canales compartidos. El agente recibe una nota visible y Slack muestra una sola vez cómo obtener acceso personal. Si falla la obtención del perfil, si se trata de invitados o miembros externos (Slack Connect), o si el espacio de trabajo no está conectado a una organización, se envía una respuesta cortés de rechazo en vez de descartar el mensaje en silencio. Los canales compartidos de Slack usan un principal de servicio y no heredan los permisos privados de ningún participante. OAuth administrado y el manifiesto generado de la aplicación de Slack solicitan el ámbito de bot `users:read.email`. Las instalaciones existentes deben volver a conectarse o instalarse para conceder un ámbito recién añadido; las instalaciones antiguas con token de bot deben añadirlo manualmente en Slack. Sin él, los mensajes directos se ejecutan en el nivel anónimo limitado a la organización, no con acceso personal.
|
|
408
410
|
|
|
409
411
|
### Nivel completo (solo suscripción explícita) {#full-tier}
|
|
410
412
|
|
|
@@ -404,7 +404,9 @@ La politique est authentifiée, et non anonyme. L'identité MCP OAuth/connect es
|
|
|
404
404
|
|
|
405
405
|
Les outils Core `db-schema` et `db-query` ne sont pas automatiquement exposés comme lectures externes. Ils restent disponibles pour l'agent interne de l'application via le chemin SQL habituellement délimité, mais un accès large au schéma/SQL est trop puissant pour être déduit de simples métadonnées de lecture. Si une application a besoin de requêtes externes directes, elle doit exposer une action GET propre avec des contrôles d'accès et des limites, ou une liste explicite de tables/colonnes avec des limites de lignes, d'octets, de durée et d'audit. `db-exec` et `db-patch` restent hors de la surface automatique.
|
|
406
406
|
|
|
407
|
-
|
|
407
|
+
Il s'agit d'une exclusion stricte basée sur le nom, et non d'une simple omission de métadonnées : les outils génériques de base de données, de seed, de session de navigateur, d'extensions et de radiographie contextuelle ne sont jamais exposés automatiquement, même si une modification future annote accidentellement l'un d'eux avec l'ensemble complet des indicateurs de lecture authentifiée — ils nécessitent toujours une entrée explicite dans `connectorCatalog`.
|
|
408
|
+
|
|
409
|
+
Avant l'exécution de l'agent, un message direct Slack vérifié est associé à un membre existant de l'organisation Agent Native et le lien d'identité entre l'espace de travail et l'utilisateur est enregistré. Le contexte utilisateur/organisation qui en résulte charge les ressources, les instructions et les compétences de cet utilisateur. Les membres de l'espace de travail dont le profil a bien été chargé, mais dont l'adresse e-mail est absente ou qui ne sont pas encore membres de l'organisation, sont exécutés comme principal de service anonyme limité à l'organisation : le même niveau de visibilité à l'échelle de l'organisation que celui des canaux partagés. L'agent reçoit une note visible et Slack explique une fois comment obtenir un accès personnel. En cas d'échec du chargement du profil, pour les invités et membres externes (Slack Connect), ou pour les espaces de travail non connectés à une organisation, une réponse de refus polie est envoyée au lieu d'ignorer silencieusement le message. Les canaux Slack partagés utilisent un principal de service et n'empruntent pas les autorisations privées d'un participant. OAuth géré et le manifeste d'application Slack généré demandent la portée de bot `users:read.email`. Les installations existantes doivent être reconnectées ou réinstallées pour accorder une portée nouvellement ajoutée ; les anciennes installations par jeton de bot doivent l'ajouter manuellement dans Slack. Sans cette portée, les messages directs s'exécutent dans le niveau anonyme limité à l'organisation plutôt qu'avec un accès personnel.
|
|
408
410
|
|
|
409
411
|
### Niveau complet (adhésion explicite uniquement) {#full-tier}
|
|
410
412
|
|
|
@@ -402,7 +402,9 @@ export default createAgentChatPlugin({
|
|
|
402
402
|
|
|
403
403
|
Core के `db-schema` और `db-query` को स्वचालित बाहरी रीड के रूप में प्रकाशित नहीं किया जाता। वे सामान्य स्कोप किए गए SQL पथ के माध्यम से ऐप एजेंट के लिए उपलब्ध रहते हैं, लेकिन व्यापक स्कीमा/SQL पहुँच केवल read-only मेटाडेटा से अनुमान लगाने के लिए बहुत शक्तिशाली है। सीधे बाहरी क्वेरी की आवश्यकता होने पर ऐप को अपने access checks और सीमाओं वाला GET action, या rows, bytes, timeout और audit limits वाली स्पष्ट table/column allow-list देनी चाहिए। `db-exec` और `db-patch` स्वचालित सतह से बाहर रहते हैं।
|
|
404
404
|
|
|
405
|
-
|
|
405
|
+
यह नाम-आधारित सख्त बहिष्करण है, केवल मेटाडेटा की चूक नहीं: सामान्य डेटाबेस/seed/ब्राउज़र-सत्र/एक्सटेंशन/संदर्भ-एक्सरे टूल कभी भी स्वचालित रूप से उजागर नहीं होते, भले ही भविष्य का कोई बदलाव गलती से इनमें से किसी एक को पूर्ण authenticated-read फ़्लैग सेट के साथ चिह्नित कर दे — उन्हें हमेशा एक स्पष्ट `connectorCatalog` प्रविष्टि की आवश्यकता होती है।
|
|
406
|
+
|
|
407
|
+
एजेंट चलने से पहले सत्यापित Slack DM को Agent Native संगठन के किसी मौजूदा सदस्य से मिलाया जाता है और workspace/user पहचान लिंक सहेजा जाता है। इससे बने user/org संदर्भ में उस उपयोगकर्ता के संसाधन, निर्देश और skills लोड होते हैं। जिन workspace सदस्यों का प्रोफ़ाइल सफलतापूर्वक मिला है लेकिन ईमेल उपलब्ध नहीं है या वे अभी संगठन के सदस्य नहीं हैं, वे इसके बजाय संगठन-स्कोप वाले अनाम service principal के रूप में चलते हैं — साझा चैनलों वाली संगठन-व्यापी दृश्यता के उसी स्तर पर — साथ में एजेंट को दिखाई देने वाला नोट और व्यक्तिगत पहुँच पाने का तरीका बताने वाली एक बार की Slack सूचना। प्रोफ़ाइल लोड न होने, मेहमान या बाहरी (Slack Connect) सदस्य होने, या workspace के किसी संगठन से न जुड़े होने पर संदेश को चुपचाप छोड़ने के बजाय विनम्र अस्वीकृति भेजी जाती है। साझा Slack चैनल service principal का उपयोग करते हैं और किसी प्रतिभागी की निजी permissions नहीं लेते। Managed OAuth और generated Slack app manifest दोनों bot scope `users:read.email` मांगते हैं। नया scope देने के लिए मौजूदा installations को reconnect/reinstall करना होगा; पुराने bot-token installations को इसे Slack में manually जोड़ना होगा। इसके बिना DM व्यक्तिगत पहुँच के बजाय संगठन-स्कोप वाले अनाम स्तर में चलते हैं।
|
|
406
408
|
|
|
407
409
|
### पूर्ण स्तरीय (केवल स्पष्ट ऑप्ट-इन) {#full-tier}
|
|
408
410
|
|
|
@@ -402,7 +402,9 @@ export default createAgentChatPlugin({
|
|
|
402
402
|
|
|
403
403
|
Core の `db-schema` と `db-query` は外部の自動読み取りとして公開されません。通常のスコープ付き SQL パスを通じてアプリ内エージェントからは利用できますが、広範なスキーマ/SQL アクセスは read-only メタデータだけから推測するには強力すぎます。外部クエリを直接許可する場合は、アクセスチェックと上限を持つアプリ所有の GET action、または行数・バイト数・タイムアウト・監査の制限を備えた明示的なテーブル/カラム許可リストを追加してください。`db-exec` と `db-patch` は自動公開の対象外です。
|
|
404
404
|
|
|
405
|
-
|
|
405
|
+
これは名前ベースの厳格な除外であり、単なるメタデータの記載漏れではありません。汎用のデータベース、seed、ブラウザ セッション、拡張、コンテキスト Xray のツールは、将来の変更で誤って完全な認証済み読み取りフラグセットが付与された場合でも、自動的に公開されることは決してありません — これらには常に明示的な `connectorCatalog` エントリが必要です。
|
|
406
|
+
|
|
407
|
+
検証済みの Slack DM は、エージェントを実行する前に既存の Agent Native 組織メンバーと照合され、ワークスペース/ユーザーの ID リンクとして保存されます。生成されたユーザー/組織コンテキストには、そのユーザーのリソース、指示、スキルが読み込まれます。プロフィールの取得には成功したものの、メールアドレスがない、またはまだ組織メンバーではないワークスペース メンバーは、代わりに組織スコープの匿名サービスプリンシパルとして実行されます。これは共有チャンネルと同じ組織全体の可視性レベルであり、エージェントに表示される注記と、個人アクセスの取得方法を説明する 1 回限りの Slack 通知が付きます。プロフィール取得の失敗、ゲストや外部 (Slack Connect) メンバー、組織に接続されていないワークスペースには、メッセージを黙って破棄せず、丁寧な拒否応答を返します。Slack の共有チャンネルはサービスプリンシパルを使い、参加者の個人的な権限を借用しません。管理対象 OAuth と生成される Slack アプリ マニフェストは、bot スコープ `users:read.email` を要求します。新たに追加されたスコープを付与するには、既存のインストールを再接続/再インストールする必要があります。従来の bot トークンによるインストールでは、Slack でスコープを手動追加してください。このスコープがない場合、DM は個人アクセスではなく組織スコープの匿名レベルで実行されます。
|
|
406
408
|
|
|
407
409
|
### フルティア (明示的なオプトインのみ) {#full-tier}
|
|
408
410
|
|
|
@@ -402,7 +402,9 @@ export default createAgentChatPlugin({
|
|
|
402
402
|
|
|
403
403
|
Core `db-schema`와 `db-query`는 자동 외부 읽기로 공개되지 않습니다. 일반적인 범위 지정 SQL 경로를 통해 앱 내부 에이전트에서는 사용할 수 있지만, 광범위한 스키마/SQL 접근은 읽기 전용 메타데이터만으로 추론하기에는 너무 강력합니다. 외부 쿼리가 필요하면 접근 검사와 제한을 갖춘 앱 소유 GET action 또는 행·바이트·시간 제한과 감사를 포함한 명시적 테이블/열 허용 목록을 제공하세요. `db-exec`와 `db-patch`는 자동 표면에 포함되지 않습니다.
|
|
404
404
|
|
|
405
|
-
|
|
405
|
+
이는 이름 기반의 강력한 제외이며 단순한 메타데이터 누락이 아닙니다. 일반 데이터베이스, seed, 브라우저 세션, 확장, context-xray 도구는 향후 변경으로 인해 실수로 전체 인증된 읽기 플래그 집합이 지정되더라도 절대 자동으로 노출되지 않습니다 — 이러한 도구는 항상 명시적인 `connectorCatalog` 항목이 필요합니다.
|
|
406
|
+
|
|
407
|
+
검증된 Slack DM은 에이전트가 실행되기 전에 기존 Agent Native 조직 구성원과 연결되고 workspace/user ID 링크로 저장됩니다. 그 결과 생성된 사용자/조직 컨텍스트에는 해당 사용자의 리소스, 지침 및 skills가 로드됩니다. 프로필을 정상적으로 가져왔지만 이메일이 없거나 아직 조직 구성원이 아닌 workspace 구성원은 조직 범위의 익명 service principal로 실행됩니다. 이는 공유 채널과 동일한 조직 전체 공개 수준이며, 에이전트에 표시되는 메모와 개인 액세스를 얻는 방법을 설명하는 일회성 Slack 안내가 함께 제공됩니다. 프로필 가져오기 실패, 게스트 및 외부(Slack Connect) 구성원, 조직에 연결되지 않은 workspace에는 메시지를 조용히 버리는 대신 정중한 거절 응답을 보냅니다. Slack 공유 채널은 service principal을 사용하며 참여자의 개인 권한을 빌리지 않습니다. 관리형 OAuth와 생성된 Slack 앱 매니페스트는 모두 bot scope `users:read.email`을 요청합니다. 새로 추가된 scope를 부여하려면 기존 설치를 다시 연결/설치해야 하며, 이전 bot-token 설치는 Slack에서 scope를 수동으로 추가해야 합니다. 이 scope가 없으면 DM은 개인 액세스 대신 조직 범위의 익명 수준에서 실행됩니다.
|
|
406
408
|
|
|
407
409
|
### 전체 등급(명시적 선택만 해당) {#full-tier}
|
|
408
410
|
|
|
@@ -404,7 +404,9 @@ A política é autenticada, não anônima. A identidade MCP OAuth/connect é tra
|
|
|
404
404
|
|
|
405
405
|
Os `db-schema` e `db-query` do Core não são expostos automaticamente como leituras externas. Eles continuam disponíveis ao agente interno do aplicativo pelo caminho SQL com escopo normal, mas o acesso amplo ao esquema/SQL é poderoso demais para ser inferido apenas de metadados de leitura. Se o aplicativo precisar de consultas externas diretas, exponha uma action GET própria com controles e limites de acesso, ou uma lista explícita de tabelas/colunas com limites de linhas, bytes, tempo e auditoria. `db-exec` e `db-patch` permanecem fora da superfície automática.
|
|
406
406
|
|
|
407
|
-
|
|
407
|
+
Trata-se de uma exclusão rígida baseada em nome, não apenas uma omissão de metadados: ferramentas genéricas de banco de dados, seed, sessão de navegador, extensões e raio X de contexto nunca são expostas automaticamente, mesmo que uma mudança futura marque acidentalmente uma delas com o conjunto completo de flags de leitura autenticada — elas sempre exigem uma entrada explícita em `connectorCatalog`.
|
|
408
|
+
|
|
409
|
+
Antes de o agente ser executado, uma DM verificada do Slack é associada a um membro existente da organização Agent Native e o vínculo de identidade entre workspace e usuário é salvo. O contexto resultante de usuário/organização carrega os recursos, as instruções e as skills desse usuário. Membros do workspace cujo perfil foi carregado, mas cujo e-mail está ausente ou que ainda não pertencem à organização, são executados como um principal de serviço anônimo com escopo da organização — o mesmo nível de visibilidade para toda a organização usado pelos canais compartilhados — com uma observação visível ao agente e um aviso único no Slack explicando como obter acesso pessoal. Falhas no carregamento do perfil, convidados e membros externos (Slack Connect), e workspaces não conectados a uma organização recebem uma resposta educada de recusa em vez de a mensagem ser descartada silenciosamente. Canais compartilhados do Slack usam um principal de serviço e não herdam as permissões privadas de um participante. O OAuth gerenciado e o manifesto gerado do aplicativo Slack solicitam o escopo de bot `users:read.email`. Instalações existentes precisam ser reconectadas/reinstaladas para conceder um escopo recém-adicionado; instalações antigas com token de bot precisam adicioná-lo manualmente no Slack. Sem ele, as DMs são executadas no nível anônimo com escopo da organização, e não com acesso pessoal.
|
|
408
410
|
|
|
409
411
|
### Nível completo (somente aceitação explícita) {#full-tier}
|
|
410
412
|
|
|
@@ -401,7 +401,9 @@ export default createAgentChatPlugin({
|
|
|
401
401
|
|
|
402
402
|
Core `db-schema` 和 `db-query` 不会自动作为外部读取公开。它们仍可通过正常的作用域 SQL 路径供应用内代理使用,但广泛的架构/SQL 访问过于强大,不能仅凭只读元数据推断。如果应用需要直接外部查询,应提供带访问检查和边界的应用自有 GET action,或明确的表/列允许列表,并限制行数、字节数、超时和审计。`db-exec` 和 `db-patch` 仍不属于自动公开范围。
|
|
403
403
|
|
|
404
|
-
|
|
404
|
+
这是一种基于名称的硬性排除,而不仅仅是元数据遗漏:通用的数据库、seed、浏览器会话、扩展和上下文 X 射线工具永远不会被自动公开,即使未来的更改意外地为其中某个工具标注了完整的已认证读取标志组合也是如此 — 它们始终需要在 `connectorCatalog` 中显式声明。
|
|
405
|
+
|
|
406
|
+
经过验证的 Slack 私信会在代理运行前匹配现有的 Agent Native 组织成员,并保存工作区/用户身份关联;由此生成的用户/组织上下文会加载该用户的资源、指令和技能。对于已成功获取资料但缺少电子邮件或尚未成为组织成员的工作区成员,系统会改用组织范围的匿名服务主体运行——其组织级可见性与共享频道相同——同时向代理显示说明,并通过 Slack 一次性提示如何获得个人访问权限。如果资料获取失败、用户是访客或外部(Slack Connect)成员,或者工作区尚未连接组织,系统会礼貌回复拒绝,而不是静默丢弃消息。Slack 共享频道使用服务主体,不会借用任何参与者的私人权限。托管 OAuth 和生成的 Slack 应用 manifest 都会请求 bot scope `users:read.email`。已有安装必须重新连接/安装才能授予新增的 scope;旧版 bot token 安装则必须在 Slack 中手动添加该 scope。没有此 scope 时,私信会在组织范围的匿名层级运行,而不会获得个人访问权限。
|
|
405
407
|
|
|
406
408
|
### 完整层(仅限明确选择加入) {#full-tier}
|
|
407
409
|
|
|
@@ -401,7 +401,9 @@ export default createAgentChatPlugin({
|
|
|
401
401
|
|
|
402
402
|
Core `db-schema` 與 `db-query` 不會自動公開為外部讀取。它們仍可透過一般的範圍 SQL 路徑供應用程式內代理使用,但廣泛的結構描述/SQL 存取過於強大,不能只從唯讀中繼資料推斷。若應用程式需要直接外部查詢,請提供具有存取檢查與限制的應用程式自有 GET action,或明確的資料表/欄位允許清單,並限制資料列、位元組、逾時與稽核。`db-exec` 與 `db-patch` 仍不屬於自動公開範圍。
|
|
403
403
|
|
|
404
|
-
|
|
404
|
+
這是一種以名稱為依據的硬性排除,而不只是中繼資料的遺漏:通用的資料庫、seed、瀏覽器工作階段、擴充功能與 context-xray 工具永遠不會自動公開,即使未來的變更意外為其中某個工具標註了完整的已驗證讀取旗標組合也一樣 — 它們一律需要在 `connectorCatalog` 中明確宣告。
|
|
405
|
+
|
|
406
|
+
經過驗證的 Slack 私訊會在代理執行前配對現有的 Agent Native 組織成員,並儲存工作區/使用者身分連結;由此產生的使用者/組織內容會載入該使用者的資源、指示和技能。對於已成功取得資料但缺少電子郵件或尚未成為組織成員的工作區成員,系統會改以組織範圍的匿名服務主體執行——其組織級可見性與共用頻道相同——同時向代理顯示說明,並透過 Slack 一次性提示如何取得個人存取權。如果資料取得失敗、使用者是訪客或外部(Slack Connect)成員,或工作區尚未連接組織,系統會禮貌回覆拒絕,而不是靜默捨棄訊息。Slack 共用頻道使用服務主體,不會借用任何參與者的私人權限。託管 OAuth 與產生的 Slack 應用程式 manifest 都會要求 bot scope `users:read.email`。現有安裝必須重新連接/安裝,才能授予新增的 scope;舊版 bot token 安裝則必須在 Slack 中手動加入該 scope。若沒有此 scope,私訊會在組織範圍的匿名層級執行,而不會取得個人存取權。
|
|
405
407
|
|
|
406
408
|
### 完整層(僅限明確選取加入) {#full-tier}
|
|
407
409
|
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.98.
|
|
3
|
+
"version": "0.98.7",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
getA2ATaskDispatchState,
|
|
24
24
|
failStuckA2ATask,
|
|
25
25
|
failStuckQueuedA2ATask,
|
|
26
|
+
settleProcessingA2ATask,
|
|
26
27
|
touchQueuedA2ATaskDispatch,
|
|
27
28
|
touchProcessingA2ATask,
|
|
28
29
|
} from "./task-store.js";
|
|
@@ -132,7 +133,7 @@ export async function processA2ATaskFromQueue(
|
|
|
132
133
|
|
|
133
134
|
const message = claimed.history?.[0];
|
|
134
135
|
if (!message) {
|
|
135
|
-
await
|
|
136
|
+
await settleProcessingA2ATask(taskId, {
|
|
136
137
|
state: "failed",
|
|
137
138
|
message: {
|
|
138
139
|
role: "agent",
|
|
@@ -184,7 +185,7 @@ export async function processA2ATaskFromQueue(
|
|
|
184
185
|
);
|
|
185
186
|
} catch (err: any) {
|
|
186
187
|
try {
|
|
187
|
-
await
|
|
188
|
+
await settleProcessingA2ATask(taskId, {
|
|
188
189
|
state: "failed",
|
|
189
190
|
message: {
|
|
190
191
|
role: "agent",
|
|
@@ -404,7 +405,7 @@ async function runHandlerAndPersist(
|
|
|
404
405
|
for await (const msg of result as AsyncGenerator<Message>) {
|
|
405
406
|
lastMessage = msg;
|
|
406
407
|
}
|
|
407
|
-
await
|
|
408
|
+
await settleProcessingA2ATask(taskId, {
|
|
408
409
|
state: "completed",
|
|
409
410
|
message: lastMessage,
|
|
410
411
|
artifacts: artifacts.length > 0 ? artifacts : undefined,
|
|
@@ -414,13 +415,13 @@ async function runHandlerAndPersist(
|
|
|
414
415
|
|
|
415
416
|
const handlerResult = await (result as Promise<A2AHandlerResult>);
|
|
416
417
|
const allArtifacts = [...artifacts, ...(handlerResult.artifacts ?? [])];
|
|
417
|
-
await
|
|
418
|
+
await settleProcessingA2ATask(taskId, {
|
|
418
419
|
state: "completed",
|
|
419
420
|
message: handlerResult.message,
|
|
420
421
|
artifacts: allArtifacts.length > 0 ? allArtifacts : undefined,
|
|
421
422
|
});
|
|
422
423
|
} catch (err: any) {
|
|
423
|
-
await
|
|
424
|
+
await settleProcessingA2ATask(taskId, {
|
|
424
425
|
state: "failed",
|
|
425
426
|
message: {
|
|
426
427
|
role: "agent",
|
|
@@ -423,6 +423,69 @@ export async function updateTask(
|
|
|
423
423
|
return task;
|
|
424
424
|
}
|
|
425
425
|
|
|
426
|
+
/**
|
|
427
|
+
* Persist the terminal result produced by the async processor, but only while
|
|
428
|
+
* that processor still owns a task in `processing`. A tasks/get request may
|
|
429
|
+
* fail an over-lifetime processor while its handler is still running; the
|
|
430
|
+
* handler cannot be canceled reliably, so this compare-and-set is what keeps
|
|
431
|
+
* its eventual completion (or error) from overwriting the timeout result.
|
|
432
|
+
*/
|
|
433
|
+
export async function settleProcessingA2ATask(
|
|
434
|
+
id: string,
|
|
435
|
+
update: {
|
|
436
|
+
state: "completed" | "failed";
|
|
437
|
+
message?: Message;
|
|
438
|
+
artifacts?: Artifact[];
|
|
439
|
+
},
|
|
440
|
+
): Promise<Task | null> {
|
|
441
|
+
await ensureTable();
|
|
442
|
+
const client = getDbExec();
|
|
443
|
+
|
|
444
|
+
const { rows } = await client.execute({
|
|
445
|
+
sql: `SELECT * FROM a2a_tasks WHERE id = ?`,
|
|
446
|
+
args: [id],
|
|
447
|
+
});
|
|
448
|
+
if (rows.length === 0) return null;
|
|
449
|
+
|
|
450
|
+
const task = taskFromRow(rows[0]);
|
|
451
|
+
const now = Date.now();
|
|
452
|
+
task.status = {
|
|
453
|
+
state: update.state,
|
|
454
|
+
message: update.message ?? task.status.message,
|
|
455
|
+
timestamp: new Date().toISOString(),
|
|
456
|
+
};
|
|
457
|
+
if (update.message && task.history) {
|
|
458
|
+
task.history.push(update.message);
|
|
459
|
+
}
|
|
460
|
+
if (update.artifacts) {
|
|
461
|
+
task.artifacts = [...(task.artifacts ?? []), ...update.artifacts];
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const result = await client.execute({
|
|
465
|
+
sql: `UPDATE a2a_tasks
|
|
466
|
+
SET status_state = ?,
|
|
467
|
+
status_message = ?,
|
|
468
|
+
status_timestamp = ?,
|
|
469
|
+
history = ?,
|
|
470
|
+
artifacts = ?,
|
|
471
|
+
updated_at = ?
|
|
472
|
+
WHERE id = ?
|
|
473
|
+
AND status_state = 'processing'`,
|
|
474
|
+
args: [
|
|
475
|
+
task.status.state,
|
|
476
|
+
task.status.message ? JSON.stringify(task.status.message) : null,
|
|
477
|
+
task.status.timestamp,
|
|
478
|
+
JSON.stringify(task.history),
|
|
479
|
+
JSON.stringify(task.artifacts),
|
|
480
|
+
now,
|
|
481
|
+
id,
|
|
482
|
+
],
|
|
483
|
+
});
|
|
484
|
+
const affected = getAffectedRowCount(result);
|
|
485
|
+
if (affected === 0) return null;
|
|
486
|
+
return task;
|
|
487
|
+
}
|
|
488
|
+
|
|
426
489
|
export async function updateTaskStatusMessage(
|
|
427
490
|
id: string,
|
|
428
491
|
message: Message,
|
|
@@ -65,6 +65,8 @@ interface RrwebRecordModule {
|
|
|
65
65
|
interface SessionReplayState {
|
|
66
66
|
active: boolean;
|
|
67
67
|
startPromise: Promise<SessionReplayStartResult> | null;
|
|
68
|
+
/** Invalidates deferred recorder startup when stop is requested mid-start. */
|
|
69
|
+
startGeneration: number;
|
|
68
70
|
replayId: string | null;
|
|
69
71
|
startedAtMs: number | null;
|
|
70
72
|
sequence: number;
|
|
@@ -432,6 +434,7 @@ function getState(): SessionReplayState {
|
|
|
432
434
|
g[SESSION_REPLAY_STATE_KEY] = {
|
|
433
435
|
active: false,
|
|
434
436
|
startPromise: null,
|
|
437
|
+
startGeneration: 0,
|
|
435
438
|
replayId: null,
|
|
436
439
|
startedAtMs: null,
|
|
437
440
|
sequence: 0,
|
|
@@ -464,6 +467,7 @@ function getState(): SessionReplayState {
|
|
|
464
467
|
state.pendingFlushReason ??= null;
|
|
465
468
|
state.pendingFlushWaiters ??= [];
|
|
466
469
|
state.awaitingFullSnapshot ??= false;
|
|
470
|
+
state.startGeneration ??= 0;
|
|
467
471
|
return state;
|
|
468
472
|
}
|
|
469
473
|
|
|
@@ -1054,12 +1058,14 @@ const REPLAY_RESOURCE_TAGS = new Set([
|
|
|
1054
1058
|
"audio",
|
|
1055
1059
|
"track",
|
|
1056
1060
|
"input",
|
|
1061
|
+
"object",
|
|
1057
1062
|
"link",
|
|
1058
1063
|
]);
|
|
1059
1064
|
const NO_REPLAY_RESOURCE_ATTRIBUTES = new Set<string>();
|
|
1060
1065
|
const REPLAY_SRC_ATTRIBUTES = new Set(["src"]);
|
|
1061
1066
|
const REPLAY_SRCSET_ATTRIBUTES = new Set(["src", "srcset"]);
|
|
1062
1067
|
const REPLAY_VIDEO_ATTRIBUTES = new Set(["src", "poster"]);
|
|
1068
|
+
const REPLAY_OBJECT_ATTRIBUTES = new Set(["data"]);
|
|
1063
1069
|
const REPLAY_HREF_ATTRIBUTES = new Set(["href"]);
|
|
1064
1070
|
|
|
1065
1071
|
function replayAttributeString(
|
|
@@ -1101,6 +1107,8 @@ function replayPreservedResourceAttributes(
|
|
|
1101
1107
|
case "audio":
|
|
1102
1108
|
case "track":
|
|
1103
1109
|
return REPLAY_SRC_ATTRIBUTES;
|
|
1110
|
+
case "object":
|
|
1111
|
+
return REPLAY_OBJECT_ATTRIBUTES;
|
|
1104
1112
|
case "input":
|
|
1105
1113
|
return node.type === "image"
|
|
1106
1114
|
? REPLAY_SRC_ATTRIBUTES
|
|
@@ -1452,12 +1460,12 @@ class ReplayUploadHttpError extends Error {
|
|
|
1452
1460
|
}
|
|
1453
1461
|
}
|
|
1454
1462
|
|
|
1455
|
-
/** 4xx statuses where retrying the exact same batch can never succeed
|
|
1456
|
-
*
|
|
1457
|
-
*
|
|
1458
|
-
*
|
|
1463
|
+
/** 4xx statuses where retrying the exact same batch can never succeed.
|
|
1464
|
+
* Keep this deliberately narrow: 401/403/404 can be temporary during auth or
|
|
1465
|
+
* deploy transitions, and stopping forever on one of those would silently
|
|
1466
|
+
* black out the rest of a long-lived SPA session. */
|
|
1459
1467
|
function isDefinitiveReplayUploadClientError(status: number): boolean {
|
|
1460
|
-
return status
|
|
1468
|
+
return status === 400 || status === 409 || status === 413 || status === 422;
|
|
1461
1469
|
}
|
|
1462
1470
|
|
|
1463
1471
|
async function sendReplayUpload(
|
|
@@ -1903,6 +1911,31 @@ async function restartSessionReplayAfterConflict(
|
|
|
1903
1911
|
state: SessionReplayState,
|
|
1904
1912
|
options: NormalizedSessionReplayOptions,
|
|
1905
1913
|
sessionId: string,
|
|
1914
|
+
): Promise<SessionReplayStartResult> {
|
|
1915
|
+
// Share the public-start mutex. A consumer may call start in the await gap
|
|
1916
|
+
// after the rejected recorder stops; both paths must converge on one rrweb
|
|
1917
|
+
// instance rather than racing two fresh identities.
|
|
1918
|
+
if (state.startPromise) return state.startPromise;
|
|
1919
|
+
|
|
1920
|
+
const startGeneration = ++state.startGeneration;
|
|
1921
|
+
let startPromise: Promise<SessionReplayStartResult>;
|
|
1922
|
+
startPromise = restartSessionReplayAfterConflictInternal(
|
|
1923
|
+
state,
|
|
1924
|
+
options,
|
|
1925
|
+
sessionId,
|
|
1926
|
+
startGeneration,
|
|
1927
|
+
).finally(() => {
|
|
1928
|
+
if (state.startPromise === startPromise) state.startPromise = null;
|
|
1929
|
+
});
|
|
1930
|
+
state.startPromise = startPromise;
|
|
1931
|
+
return startPromise;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
async function restartSessionReplayAfterConflictInternal(
|
|
1935
|
+
state: SessionReplayState,
|
|
1936
|
+
options: NormalizedSessionReplayOptions,
|
|
1937
|
+
sessionId: string,
|
|
1938
|
+
startGeneration: number,
|
|
1906
1939
|
): Promise<SessionReplayStartResult> {
|
|
1907
1940
|
if (options.shouldStart && !options.shouldStart()) {
|
|
1908
1941
|
return { started: false, reason: "disabled", sessionId, sampled: true };
|
|
@@ -1932,6 +1965,7 @@ async function restartSessionReplayAfterConflict(
|
|
|
1932
1965
|
sessionId,
|
|
1933
1966
|
true,
|
|
1934
1967
|
initialProperties,
|
|
1968
|
+
startGeneration,
|
|
1935
1969
|
);
|
|
1936
1970
|
}
|
|
1937
1971
|
|
|
@@ -2879,6 +2913,11 @@ export async function startSessionReplay(
|
|
|
2879
2913
|
}
|
|
2880
2914
|
if (state.startPromise) return state.startPromise;
|
|
2881
2915
|
|
|
2916
|
+
// This is a new caller-initiated recording episode. A prior episode's
|
|
2917
|
+
// conflict-loop guard must not prevent this one from recovering once.
|
|
2918
|
+
state.automaticConflictRestartAttempted = false;
|
|
2919
|
+
const startGeneration = ++state.startGeneration;
|
|
2920
|
+
|
|
2882
2921
|
let startPromise: Promise<SessionReplayStartResult>;
|
|
2883
2922
|
startPromise = startSessionReplayRecorder(
|
|
2884
2923
|
state,
|
|
@@ -2886,6 +2925,7 @@ export async function startSessionReplay(
|
|
|
2886
2925
|
sessionId,
|
|
2887
2926
|
sampled,
|
|
2888
2927
|
initialProperties,
|
|
2928
|
+
startGeneration,
|
|
2889
2929
|
).finally(() => {
|
|
2890
2930
|
if (state.startPromise === startPromise) {
|
|
2891
2931
|
state.startPromise = null;
|
|
@@ -2901,6 +2941,7 @@ async function startSessionReplayRecorder(
|
|
|
2901
2941
|
sessionId: string,
|
|
2902
2942
|
sampled: boolean,
|
|
2903
2943
|
initialProperties: Record<string, unknown> | undefined,
|
|
2944
|
+
startGeneration: number,
|
|
2904
2945
|
): Promise<SessionReplayStartResult> {
|
|
2905
2946
|
if (state.active && state.replayId) {
|
|
2906
2947
|
return {
|
|
@@ -2918,6 +2959,9 @@ async function startSessionReplayRecorder(
|
|
|
2918
2959
|
} catch {
|
|
2919
2960
|
return { started: false, reason: "import-failed", sessionId, sampled };
|
|
2920
2961
|
}
|
|
2962
|
+
if (state.startGeneration !== startGeneration) {
|
|
2963
|
+
return { started: false, reason: "disabled", sessionId, sampled };
|
|
2964
|
+
}
|
|
2921
2965
|
if (normalized.shouldStart && !normalized.shouldStart()) {
|
|
2922
2966
|
return { started: false, reason: "disabled", sessionId, sampled };
|
|
2923
2967
|
}
|
|
@@ -2950,6 +2994,20 @@ async function startSessionReplayRecorder(
|
|
|
2950
2994
|
};
|
|
2951
2995
|
}
|
|
2952
2996
|
}
|
|
2997
|
+
// stopSessionReplay may be called while the duplicate-tab probe is waiting.
|
|
2998
|
+
// Recheck both cancellation and the caller's live eligibility before rrweb
|
|
2999
|
+
// is activated so a deferred start cannot escape a route/auth teardown.
|
|
3000
|
+
if (
|
|
3001
|
+
state.startGeneration !== startGeneration ||
|
|
3002
|
+
(normalized.shouldStart && !normalized.shouldStart())
|
|
3003
|
+
) {
|
|
3004
|
+
try {
|
|
3005
|
+
replayChannel?.close();
|
|
3006
|
+
} catch {
|
|
3007
|
+
// best-effort cleanup
|
|
3008
|
+
}
|
|
3009
|
+
return { started: false, reason: "disabled", sessionId, sampled };
|
|
3010
|
+
}
|
|
2953
3011
|
state.options = normalized;
|
|
2954
3012
|
state.replayId = replaySession.replayId;
|
|
2955
3013
|
state.startedAtMs = replaySession.startedAtMs;
|
|
@@ -3049,6 +3107,10 @@ async function startSessionReplayRecorder(
|
|
|
3049
3107
|
|
|
3050
3108
|
export async function stopSessionReplay(reason = "manual"): Promise<void> {
|
|
3051
3109
|
const state = getState();
|
|
3110
|
+
// Invalidate an import/probe that has not set `active` yet. Without this,
|
|
3111
|
+
// stop during the duplicated-tab claim window was a no-op and rrweb started
|
|
3112
|
+
// after the caller believed recording had been disabled.
|
|
3113
|
+
state.startGeneration += 1;
|
|
3052
3114
|
if (!state.active) return;
|
|
3053
3115
|
// Restore console/fetch/XHR before tearing down the recorder: the restore
|
|
3054
3116
|
// flushes any pending collapsed console duplicate, which must still be able
|