@agent-native/core 0.106.1 → 0.106.3

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.
Files changed (99) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +12 -0
  3. package/corpus/core/docs/content/external-agents.mdx +8 -0
  4. package/corpus/core/docs/content/locales/ar-SA/external-agents.mdx +7 -0
  5. package/corpus/core/docs/content/locales/ar-SA/mcp-protocol.mdx +7 -0
  6. package/corpus/core/docs/content/locales/de-DE/external-agents.mdx +9 -0
  7. package/corpus/core/docs/content/locales/de-DE/mcp-protocol.mdx +9 -0
  8. package/corpus/core/docs/content/locales/es-ES/external-agents.mdx +9 -0
  9. package/corpus/core/docs/content/locales/es-ES/mcp-protocol.mdx +9 -0
  10. package/corpus/core/docs/content/locales/fr-FR/external-agents.mdx +9 -0
  11. package/corpus/core/docs/content/locales/fr-FR/mcp-protocol.mdx +9 -0
  12. package/corpus/core/docs/content/locales/hi-IN/external-agents.mdx +8 -0
  13. package/corpus/core/docs/content/locales/hi-IN/mcp-protocol.mdx +8 -0
  14. package/corpus/core/docs/content/locales/ja-JP/external-agents.mdx +8 -0
  15. package/corpus/core/docs/content/locales/ja-JP/mcp-protocol.mdx +8 -0
  16. package/corpus/core/docs/content/locales/ko-KR/external-agents.mdx +8 -0
  17. package/corpus/core/docs/content/locales/ko-KR/mcp-protocol.mdx +8 -0
  18. package/corpus/core/docs/content/locales/pt-BR/external-agents.mdx +9 -0
  19. package/corpus/core/docs/content/locales/pt-BR/mcp-protocol.mdx +9 -0
  20. package/corpus/core/docs/content/locales/zh-CN/external-agents.mdx +6 -0
  21. package/corpus/core/docs/content/locales/zh-CN/mcp-protocol.mdx +6 -0
  22. package/corpus/core/docs/content/locales/zh-TW/external-agents.mdx +6 -0
  23. package/corpus/core/docs/content/locales/zh-TW/mcp-protocol.mdx +6 -0
  24. package/corpus/core/docs/content/mcp-protocol.mdx +8 -0
  25. package/corpus/core/package.json +1 -1
  26. package/corpus/core/src/a2a/artifact-response.ts +84 -0
  27. package/corpus/core/src/agent/thread-data-builder.ts +1 -1
  28. package/corpus/core/src/integrations/webhook-handler.ts +41 -5
  29. package/corpus/core/src/mcp/builtin-tools.ts +115 -10
  30. package/corpus/core/src/server/agent-chat-plugin.ts +7 -2
  31. package/corpus/templates/clips/app/components/capture-install-options.tsx +60 -5
  32. package/corpus/templates/clips/app/components/library/library-layout.tsx +9 -1
  33. package/corpus/templates/clips/app/hooks/use-desktop-promo.ts +4 -4
  34. package/corpus/templates/clips/app/i18n/ar-SA.ts +1 -0
  35. package/corpus/templates/clips/app/i18n/de-DE.ts +1 -0
  36. package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
  37. package/corpus/templates/clips/app/i18n/es-ES.ts +1 -0
  38. package/corpus/templates/clips/app/i18n/fr-FR.ts +1 -0
  39. package/corpus/templates/clips/app/i18n/hi-IN.ts +1 -0
  40. package/corpus/templates/clips/app/i18n/ja-JP.ts +1 -0
  41. package/corpus/templates/clips/app/i18n/ko-KR.ts +1 -0
  42. package/corpus/templates/clips/app/i18n/pt-BR.ts +1 -0
  43. package/corpus/templates/clips/app/i18n/zh-CN.ts +1 -0
  44. package/corpus/templates/clips/app/i18n/zh-TW.ts +1 -0
  45. package/corpus/templates/clips/app/lib/capture-install-options.ts +89 -4
  46. package/corpus/templates/clips/app/routes/_app.dictate.tsx +10 -1
  47. package/corpus/templates/clips/app/routes/record.tsx +1 -0
  48. package/corpus/templates/clips/app/routes/share.$shareId.tsx +7 -0
  49. package/corpus/templates/clips/changelog/2026-07-16-the-desktop-app-button-now-shows-open-desktop-app-and-launch.md +6 -0
  50. package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +5 -0
  51. package/corpus/templates/clips/desktop/src-tauri/capabilities/default.json +1 -0
  52. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +41 -6
  53. package/corpus/templates/clips/desktop/src-tauri/tauri.conf.json +5 -0
  54. package/corpus/templates/content/.agents/skills/content/SKILL.md +31 -0
  55. package/corpus/templates/content/changelog/2026-07-16-slack-corrections-now-preserve-newer-content-values-unless-y.md +6 -0
  56. package/dist/a2a/artifact-response.d.ts +9 -0
  57. package/dist/a2a/artifact-response.d.ts.map +1 -1
  58. package/dist/a2a/artifact-response.js +68 -0
  59. package/dist/a2a/artifact-response.js.map +1 -1
  60. package/dist/agent/thread-data-builder.js +1 -1
  61. package/dist/agent/thread-data-builder.js.map +1 -1
  62. package/dist/collab/struct-routes.d.ts +1 -1
  63. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  64. package/dist/integrations/webhook-handler.js +24 -5
  65. package/dist/integrations/webhook-handler.js.map +1 -1
  66. package/dist/mcp/builtin-tools.d.ts.map +1 -1
  67. package/dist/mcp/builtin-tools.js +87 -6
  68. package/dist/mcp/builtin-tools.js.map +1 -1
  69. package/dist/notifications/routes.d.ts +1 -1
  70. package/dist/observability/routes.d.ts +5 -5
  71. package/dist/resources/handlers.d.ts +1 -1
  72. package/dist/secrets/routes.d.ts +9 -9
  73. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  74. package/dist/server/agent-chat-plugin.js +3 -1
  75. package/dist/server/agent-chat-plugin.js.map +1 -1
  76. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  77. package/docs/content/external-agents.mdx +8 -0
  78. package/docs/content/locales/ar-SA/external-agents.mdx +7 -0
  79. package/docs/content/locales/ar-SA/mcp-protocol.mdx +7 -0
  80. package/docs/content/locales/de-DE/external-agents.mdx +9 -0
  81. package/docs/content/locales/de-DE/mcp-protocol.mdx +9 -0
  82. package/docs/content/locales/es-ES/external-agents.mdx +9 -0
  83. package/docs/content/locales/es-ES/mcp-protocol.mdx +9 -0
  84. package/docs/content/locales/fr-FR/external-agents.mdx +9 -0
  85. package/docs/content/locales/fr-FR/mcp-protocol.mdx +9 -0
  86. package/docs/content/locales/hi-IN/external-agents.mdx +8 -0
  87. package/docs/content/locales/hi-IN/mcp-protocol.mdx +8 -0
  88. package/docs/content/locales/ja-JP/external-agents.mdx +8 -0
  89. package/docs/content/locales/ja-JP/mcp-protocol.mdx +8 -0
  90. package/docs/content/locales/ko-KR/external-agents.mdx +8 -0
  91. package/docs/content/locales/ko-KR/mcp-protocol.mdx +8 -0
  92. package/docs/content/locales/pt-BR/external-agents.mdx +9 -0
  93. package/docs/content/locales/pt-BR/mcp-protocol.mdx +9 -0
  94. package/docs/content/locales/zh-CN/external-agents.mdx +6 -0
  95. package/docs/content/locales/zh-CN/mcp-protocol.mdx +6 -0
  96. package/docs/content/locales/zh-TW/external-agents.mdx +6 -0
  97. package/docs/content/locales/zh-TW/mcp-protocol.mdx +6 -0
  98. package/docs/content/mcp-protocol.mdx +8 -0
  99. package/package.json +1 -1
package/corpus/README.md CHANGED
@@ -28,4 +28,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
28
28
  ## Generated Counts
29
29
 
30
30
  - core files: 2283
31
- - template files: 6062
31
+ - template files: 6064
@@ -1,5 +1,17 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.106.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 10fb9f4: Preserve durable `ask_app` task handles and return retryable status-read details when transient polling transport failures outlast bounded retries.
8
+
9
+ ## 0.106.2
10
+
11
+ ### Patch Changes
12
+
13
+ - 7fb8e89: Return a verified artifact receipt when an integration mutation succeeds without a final model summary.
14
+
3
15
  ## 0.106.1
4
16
 
5
17
  ### Patch Changes
@@ -560,6 +560,14 @@ and `taskId`; the result includes `pollAfterMs` as a host-friendly interval.
560
560
  The task must be created before the first response can time out, so a polling
561
561
  URL returned only after a long call is not a reliable recovery contract.
562
562
 
563
+ If a bounded transient status read is unavailable, `ask_app_status` can return
564
+ `status: "unknown"`, `statusRead: "unavailable"`, `retryable: true`, the
565
+ original `app` and `taskId`, and `pollAfterMs` / `poll`. This means the status
566
+ read is unavailable—not that the durable task failed. Retry `ask_app_status`
567
+ with that exact `app` and `taskId`; never resubmit `ask_app` to recover, because
568
+ submission can duplicate work. Permanent authentication, not-found, and
569
+ protocol failures remain errors.
570
+
563
571
  ### Per-app tour {#tour}
564
572
 
565
573
  Every allow-listed template that produces or lists a navigable resource ships a `link` builder, and the ingest-heavy ones ship a GET + `publicAgent` action so a connected agent can pull live state:
@@ -527,6 +527,13 @@ Claude Code calls: manage-draft(to: "john@example.com", subject: "Q3 Report", bo
527
527
 
528
528
  لا تشترك مضيفات وبوابات MCP في مهلة زمنية واحدة طويلة لاستدعاء الأداة؛ يفرض بعضها حدًا أقصى مدته خمس دقائق بينما يكون بعضها الآخر قابلاً للتكوين. لا تصمم تكامل الوكيل الخارجي بالاعتماد على استدعاء `tools/call` محظور لمدة عشر دقائق. يقر كل من `ask-agent` و`ask_app` بالعمل المستضاف بسرعة، ثم يُرجعان `taskId` دائمًا عند انتهاء مهلة الانتظار المضمّن. استطلع باستخدام `ask_app_status` مستخدمًا `app` و`taskId` المُرجعين؛ وتتضمن النتيجة `pollAfterMs` كفاصل زمني ملائم للمضيف. يجب إنشاء المهمة قبل أن تنتهي مهلة الاستجابة الأولى، لذا فإن رابط الاستطلاع الذي يُرجع فقط بعد استدعاء طويل ليس عقد استرداد يمكن الاعتماد عليه.
529
529
 
530
+ عند عدم إتاحة قراءة حالة عابرة ومحدودة المدة، يمكن أن تُرجع `ask_app_status` القيم
531
+ `status: "unknown"` و`statusRead: "unavailable"` و`retryable: true`، إلى جانب
532
+ `app` و`taskId` الأصليين و`pollAfterMs` / `poll`. يعني ذلك أن قراءة الحالة غير
533
+ متاحة، وليس أن المهمة الدائمة قد فشلت. أعد محاولة `ask_app_status` باستخدام `app`
534
+ و`taskId` نفسيهما تمامًا؛ ولا تعِد إرسال `ask_app` للاسترداد مطلقًا، إذ قد يكرر
535
+ الإرسال العمل. تظل أخطاء المصادقة الدائمة أو عدم العثور أو البروتوكول أخطاءً.
536
+
530
537
  ### جولة لكل تطبيق {#tour}
531
538
 
532
539
  يقوم كل قالب مدرج في القائمة المسموح بها ينتج موردًا قابلاً للملاحة أو يسرده بشحن أداة إنشاء `link`، وتقوم القوالب ذات الاستيعاب الثقيل بشحن إجراء GET + `publicAgent` حتى يتمكن الوكيل المتصل من سحب الحالة المباشرة:
@@ -231,6 +231,13 @@ Auto-mounted on every app. Speaks the standard MCP handshake (`initialize` → `
231
231
 
232
232
  استدعِ `ask_app_status` بتلك الوسيطات حتى تصل المهمة إلى حالة نهائية. يستخدم `ask_app` المملوك للتطبيق في Dispatch نفس عقد المهمة الدائمة والحالة، مع التحقق من صلاحية التطبيق المحدد عند كل من الإرسال والاستطلاع.
233
233
 
234
+ عند عدم إتاحة قراءة حالة عابرة ومحدودة المدة، يمكن أن تُرجع `ask_app_status` القيم
235
+ `status: "unknown"` و`statusRead: "unavailable"` و`retryable: true`، إلى جانب
236
+ `app` و`taskId` الأصليين و`pollAfterMs` / `poll`. يعني ذلك أن قراءة الحالة غير
237
+ متاحة، وليس أن المهمة الدائمة قد فشلت. أعد محاولة `ask_app_status` باستخدام `app`
238
+ و`taskId` نفسيهما تمامًا؛ ولا تعِد إرسال `ask_app` للاسترداد مطلقًا، إذ قد يكرر
239
+ الإرسال العمل. تظل أخطاء المصادقة الدائمة أو عدم العثور أو البروتوكول أخطاءً.
240
+
234
241
  ## المصادقة {#authentication}
235
242
 
236
243
  تدعم نقطة النهاية MCP MCP OAuth القياسي عن بعد بالإضافة إلى البديل الحالي للرمز المميز لحامله:
@@ -530,6 +530,15 @@ Für schnelle ChatGPT/Claude-Übergaben ist der ideale Pfad direkt: Rufen Sie di
530
530
 
531
531
  MCP-Hosts und -Gateways teilen sich kein einheitliches Zeitlimit für Tool-Aufrufe; manche erzwingen eine Obergrenze von fünf Minuten, andere sind konfigurierbar. Planen Sie eine externe Agentenintegration nicht um einen zehnminütigen, blockierenden `tools/call` herum. `ask-agent` und `ask_app` bestätigen gehostete Arbeit schnell und geben dann eine dauerhafte `taskId` zurück, sobald die Inline-Wartezeit abläuft. Fragen Sie mit `ask_app_status` unter Verwendung der zurückgegebenen `app` und `taskId` ab; das Ergebnis enthält `pollAfterMs` als host-freundliches Intervall. Die Aufgabe muss erstellt werden, bevor die erste Antwort in ein Timeout laufen kann – eine Polling-URL, die erst nach einem langen Aufruf zurückgegeben wird, ist kein zuverlässiger Recovery-Vertrag.
532
532
 
533
+ Ist ein begrenzter, vorübergehender Statusabruf nicht verfügbar, kann
534
+ `ask_app_status` `status: "unknown"`, `statusRead: "unavailable"` und
535
+ `retryable: true` sowie das ursprüngliche `app`, `taskId` und `pollAfterMs` /
536
+ `poll` zurückgeben. Das bedeutet, dass der Statusabruf nicht verfügbar ist –
537
+ nicht, dass die dauerhafte Aufgabe fehlgeschlagen ist. Wiederholen Sie
538
+ `ask_app_status` mit genau demselben `app` und `taskId`; reichen Sie zur
539
+ Wiederherstellung niemals erneut `ask_app` ein, da dies Arbeit duplizieren kann.
540
+ Dauerhafte Authentifizierungs-, Nicht-gefunden- und Protokollfehler bleiben Fehler.
541
+
533
542
  ### Tour pro App {#tour}
534
543
 
535
544
  Jede Vorlage auf der Zulassungsliste, die eine navigierbare Ressource erstellt oder auflistet, liefert einen `link`-Builder, und die aufnahmeintensiven Vorlagen liefern eine GET + `publicAgent`-Aktion, damit ein verbundener Agent den Live-Status abrufen kann:
@@ -231,6 +231,15 @@ Der Agent führt die gleiche Schleife aus wie der interaktive Chat – er kann m
231
231
 
232
232
  Rufen Sie `ask_app_status` mit diesen Argumenten auf, bis die Aufgabe einen Endzustand erreicht. Das app-eigene `ask_app` von Dispatch verwendet denselben Vertrag für dauerhafte Aufgaben und Status, wobei die Berechtigung für die ausgewählte App sowohl bei der Übermittlung als auch beim Polling geprüft wird.
233
233
 
234
+ Ist ein begrenzter, vorübergehender Statusabruf nicht verfügbar, kann
235
+ `ask_app_status` `status: "unknown"`, `statusRead: "unavailable"` und
236
+ `retryable: true` sowie das ursprüngliche `app`, `taskId` und `pollAfterMs` /
237
+ `poll` zurückgeben. Das bedeutet, dass der Statusabruf nicht verfügbar ist –
238
+ nicht, dass die dauerhafte Aufgabe fehlgeschlagen ist. Wiederholen Sie
239
+ `ask_app_status` mit genau demselben `app` und `taskId`; reichen Sie zur
240
+ Wiederherstellung niemals erneut `ask_app` ein, da dies Arbeit duplizieren kann.
241
+ Dauerhafte Authentifizierungs-, Nicht-gefunden- und Protokollfehler bleiben Fehler.
242
+
234
243
  ## Authentifizierung {#authentication}
235
244
 
236
245
  Der MCP-Endpunkt unterstützt den Standard-Remote-MCP OAuth sowie den vorhandenen Bearer-Token-Fallback:
@@ -539,6 +539,15 @@ para hosts. La tarea debe crearse antes de que la primera respuesta pueda
539
539
  agotar el tiempo de espera, por lo que una URL de sondeo devuelta solo después
540
540
  de una llamada larga no es un contrato de recuperación confiable.
541
541
 
542
+ Si no está disponible una lectura de estado transitoria y acotada,
543
+ `ask_app_status` puede devolver `status: "unknown"`,
544
+ `statusRead: "unavailable"`, `retryable: true`, el `app` y `taskId` originales,
545
+ y `pollAfterMs` / `poll`. Esto significa que la lectura de estado no está
546
+ disponible, no que la tarea duradera haya fallado. Vuelva a intentar
547
+ `ask_app_status` con exactamente el mismo `app` y `taskId`; nunca reenvíe
548
+ `ask_app` para recuperarse, porque el envío puede duplicar el trabajo. Los errores
549
+ permanentes de autenticación, de no encontrado y de protocolo siguen siendo errores.
550
+
542
551
  ### Recorrido por aplicación {#tour}
543
552
 
544
553
  Cada plantilla incluida en la lista permitida que produce o enumera un recurso navegable incluye un generador `link`, y las de ingesta pesada incluyen una acción GET + `publicAgent` para que un agente conectado pueda obtener el estado en vivo:
@@ -240,6 +240,15 @@ estado terminal. El `ask_app` propio de cada aplicación en Dispatch usa el
240
240
  mismo contrato de tarea y estado duraderos, con la concesión de la aplicación
241
241
  seleccionada verificada tanto en el envío como en el sondeo.
242
242
 
243
+ Si no está disponible una lectura de estado transitoria y acotada,
244
+ `ask_app_status` puede devolver `status: "unknown"`,
245
+ `statusRead: "unavailable"`, `retryable: true`, el `app` y `taskId` originales,
246
+ y `pollAfterMs` / `poll`. Esto significa que la lectura de estado no está
247
+ disponible, no que la tarea duradera haya fallado. Vuelva a intentar
248
+ `ask_app_status` con exactamente el mismo `app` y `taskId`; nunca reenvíe
249
+ `ask_app` para recuperarse, porque el envío puede duplicar el trabajo. Los errores
250
+ permanentes de autenticación, de no encontrado y de protocolo siguen siendo errores.
251
+
243
252
  ## Autenticación {#authentication}
244
253
 
245
254
  El punto final MCP admite MCP OAuth remoto estándar más el respaldo de token de portador existente:
@@ -531,6 +531,15 @@ Pour des transferts rapides ChatGPT/Claude, le chemin idéal est direct : appel
531
531
 
532
532
  Les hôtes et passerelles MCP ne partagent pas un seul et même délai d'expiration pour les appels d'outils : certains imposent un plafond de cinq minutes, d'autres sont configurables. Ne concevez pas une intégration d'agent externe autour d'un `tools/call` bloquant de dix minutes. `ask-agent` et `ask_app` accusent rapidement réception du travail hébergé, puis renvoient un `taskId` durable lorsque l'attente en ligne expire. Interrogez avec `ask_app_status` en utilisant les `app` et `taskId` renvoyés ; le résultat inclut `pollAfterMs` comme intervalle adapté à l'hôte. La tâche doit être créée avant que la première réponse ne puisse expirer, de sorte qu'une URL d'interrogation renvoyée seulement après un long appel n'est pas un contrat de récupération fiable.
533
533
 
534
+ Si une lecture d'état transitoire et limitée est indisponible, `ask_app_status`
535
+ peut renvoyer `status: "unknown"`, `statusRead: "unavailable"`,
536
+ `retryable: true`, les `app` et `taskId` d'origine, ainsi que `pollAfterMs` /
537
+ `poll`. Cela signifie que la lecture d'état est indisponible, et non que la tâche
538
+ durable a échoué. Réessayez `ask_app_status` avec exactement les mêmes `app` et
539
+ `taskId` ; ne soumettez jamais à nouveau `ask_app` pour récupérer, car la
540
+ soumission peut dupliquer le travail. Les erreurs permanentes d'authentification,
541
+ d'introuvable et de protocole restent des erreurs.
542
+
534
543
  ### Visite par application {#tour}
535
544
 
536
545
  Chaque modèle sur liste verte qui produit ou répertorie une ressource navigable est livré avec un générateur `link`, et ceux qui sont lourds à ingérer envoient une action GET + `publicAgent` afin qu'un agent connecté puisse extraire l'état en direct :
@@ -231,6 +231,15 @@ L'agent exécute la même boucle que le chat interactif : il peut appeler plusi
231
231
 
232
232
  Appelez `ask_app_status` avec ces arguments jusqu'à ce que la tâche atteigne un état terminal. Le `ask_app` propre à l'application de Dispatch utilise le même contrat de tâche durable et de statut, avec la vérification de l'autorisation de l'application sélectionnée effectuée à la fois lors de la soumission et de l'interrogation.
233
233
 
234
+ Si une lecture d'état transitoire et limitée est indisponible, `ask_app_status`
235
+ peut renvoyer `status: "unknown"`, `statusRead: "unavailable"`,
236
+ `retryable: true`, les `app` et `taskId` d'origine, ainsi que `pollAfterMs` /
237
+ `poll`. Cela signifie que la lecture d'état est indisponible, et non que la tâche
238
+ durable a échoué. Réessayez `ask_app_status` avec exactement les mêmes `app` et
239
+ `taskId` ; ne soumettez jamais à nouveau `ask_app` pour récupérer, car la
240
+ soumission peut dupliquer le travail. Les erreurs permanentes d'authentification,
241
+ d'introuvable et de protocole restent des erreurs.
242
+
234
243
  ## Authentification {#authentication}
235
244
 
236
245
  Le point de terminaison MCP prend en charge le modèle distant standard MCP OAuth, ainsi que le remplacement du jeton de support existant :
@@ -535,6 +535,14 @@ MCP होस्ट और गेटवे एक साझा लंबे ट
535
535
  टाइमआउट होने से पहले टास्क बनाया जाना चाहिए, इसलिए केवल लंबे कॉल के बाद
536
536
  लौटाया गया पोलिंग URL एक विश्वसनीय रिकवरी अनुबंध नहीं है।
537
537
 
538
+ यदि सीमित, अस्थायी स्थिति-पठन उपलब्ध नहीं है, तो `ask_app_status`
539
+ `status: "unknown"`, `statusRead: "unavailable"`, `retryable: true`, मूल `app`
540
+ और `taskId`, तथा `pollAfterMs` / `poll` लौटा सकता है। इसका अर्थ है कि स्थिति-पठन
541
+ अनुपलब्ध है, न कि स्थायी टास्क विफल हुआ है। ठीक उसी `app` और `taskId` के साथ
542
+ `ask_app_status` को फिर आज़माएँ; पुनर्प्राप्ति के लिए `ask_app` को कभी फिर से सबमिट
543
+ न करें, क्योंकि सबमिशन काम की नकल कर सकता है। स्थायी प्रमाणीकरण, न मिलने और
544
+ प्रोटोकॉल त्रुटियाँ त्रुटियाँ ही रहती हैं।
545
+
538
546
  ### प्रति-ऐप टूर {#tour}
539
547
 
540
548
  प्रत्येक अनुमति-सूचीबद्ध टेम्पलेट जो एक नेविगेशन योग्य संसाधन का उत्पादन या सूचीबद्ध करता है, एक `link` बिल्डर को शिप करता है, और अंतर्ग्रहण-भारी वाले एक GET + `publicAgent` कार्रवाई को शिप करते हैं ताकि एक कनेक्टेड एजेंट लाइव स्थिति खींच सके:
@@ -238,6 +238,14 @@ Auto-mounted on every app. Speaks the standard MCP handshake (`initialize` → `
238
238
  अनुबंध का उपयोग करता है, जिसमें चयनित-ऐप अनुदान की जांच सबमिशन और पोलिंग दोनों
239
239
  पर की जाती है।
240
240
 
241
+ यदि सीमित, अस्थायी स्थिति-पठन उपलब्ध नहीं है, तो `ask_app_status`
242
+ `status: "unknown"`, `statusRead: "unavailable"`, `retryable: true`, मूल `app`
243
+ और `taskId`, तथा `pollAfterMs` / `poll` लौटा सकता है। इसका अर्थ है कि स्थिति-पठन
244
+ अनुपलब्ध है, न कि स्थायी टास्क विफल हुआ है। ठीक उसी `app` और `taskId` के साथ
245
+ `ask_app_status` को फिर आज़माएँ; पुनर्प्राप्ति के लिए `ask_app` को कभी फिर से सबमिट
246
+ न करें, क्योंकि सबमिशन काम की नकल कर सकता है। स्थायी प्रमाणीकरण, न मिलने और
247
+ प्रोटोकॉल त्रुटियाँ त्रुटियाँ ही रहती हैं।
248
+
241
249
  ## प्रमाणीकरण {#authentication}
242
250
 
243
251
  MCP एंडपॉइंट मानक रिमोट MCP OAuth प्लस मौजूदा बियरर-टोकन फ़ॉलबैक का समर्थन करता है:
@@ -528,6 +528,14 @@ ChatGPT/Claude スタイルの OAuth アプリ ホストの場合、検出サー
528
528
 
529
529
  MCP ホストとゲートウェイは、単一の長いツール呼び出しタイムアウトを共有していません。5 分の上限を課すものもあれば、設定可能なものもあります。10 分間ブロックする `tools/call` を前提に外部エージェント連携を設計しないでください。 `ask-agent` と `ask_app` はホストされている処理をすぐに確認応答し、インライン待機が期限切れになると永続的な `taskId` を返します。返された `app` と `taskId` を使用して `ask_app_status` でポーリングしてください。結果にはホストにとって扱いやすい間隔として `pollAfterMs` が含まれます。タスクは最初のレスポンスがタイムアウトする前に作成されている必要があるため、長い呼び出しの後にのみ返されるポーリング URL は信頼できる復旧手段ではありません。
530
530
 
531
+ 制限された一時的なステータス取得を利用できない場合、`ask_app_status` は
532
+ `status: "unknown"`、`statusRead: "unavailable"`、`retryable: true`、元の
533
+ `app` と `taskId`、および `pollAfterMs` / `poll` を返すことがあります。これは
534
+ ステータス取得が利用できないことを意味し、永続タスクが失敗したことを意味しません。
535
+ まったく同じ `app` と `taskId` で `ask_app_status` を再試行してください。送信により
536
+ 作業が重複する可能性があるため、復旧のために `ask_app` を再送信してはいけません。
537
+ 永続的な認証、未検出、プロトコルのエラーは引き続きエラーです。
538
+
531
539
  ### アプリごとのツアー {#tour}
532
540
 
533
541
  ナビゲート可能なリソースを生成またはリストする許可リストに登録されたすべてのテンプレートには `link` ビルダーが同梱されており、取り込みが多いテンプレートには GET + `publicAgent` アクションが同梱されているため、接続されたエージェントはライブ状態を取得できます。
@@ -231,6 +231,14 @@ Auto-mounted on every app. Speaks the standard MCP handshake (`initialize` → `
231
231
 
232
232
  タスクが最終状態に達するまで、これらの引数を使用して `ask_app_status` を呼び出してください。Dispatch のアプリ所有の `ask_app` は同じ永続的なタスクとステータスの契約を使用し、選択されたアプリの許可は送信時とポーリング時の両方でチェックされます。
233
233
 
234
+ 制限された一時的なステータス取得を利用できない場合、`ask_app_status` は
235
+ `status: "unknown"`、`statusRead: "unavailable"`、`retryable: true`、元の
236
+ `app` と `taskId`、および `pollAfterMs` / `poll` を返すことがあります。これは
237
+ ステータス取得が利用できないことを意味し、永続タスクが失敗したことを意味しません。
238
+ まったく同じ `app` と `taskId` で `ask_app_status` を再試行してください。送信により
239
+ 作業が重複する可能性があるため、復旧のために `ask_app` を再送信してはいけません。
240
+ 永続的な認証、未検出、プロトコルのエラーは引き続きエラーです。
241
+
234
242
  ## 認証 {#authentication}
235
243
 
236
244
  MCP エンドポイントは、標準のリモート MCP OAuth に加えて、既存のベアラー トークン フォールバックをサポートします。
@@ -536,6 +536,14 @@ MCP 호스트와 게이트웨이는 하나의 긴 도구 호출 제한 시간을
536
536
  전에 작업이 생성되어야 하므로, 오랜 호출이 끝난 후에만 반환되는 폴링 URL은 신뢰할
537
537
  수 있는 복구 계약이 아닙니다.
538
538
 
539
+ 제한된 일시적 상태 읽기를 사용할 수 없으면 `ask_app_status`는
540
+ `status: "unknown"`, `statusRead: "unavailable"`, `retryable: true`, 원래의
541
+ `app`과 `taskId`, 그리고 `pollAfterMs` / `poll`을 반환할 수 있습니다. 이는 상태
542
+ 읽기를 사용할 수 없다는 뜻이지 내구성 있는 작업이 실패했다는 뜻은 아닙니다. 정확히 같은
543
+ `app`과 `taskId`로 `ask_app_status`를 재시도하세요. 제출하면 작업이 중복될 수 있으므로
544
+ 복구를 위해 `ask_app`을 다시 제출하지 마세요. 영구적인 인증, 찾을 수 없음, 프로토콜
545
+ 오류는 계속 오류로 남습니다.
546
+
539
547
  ### 앱별 둘러보기 {#tour}
540
548
 
541
549
  탐색 가능한 리소스를 생성하거나 나열하는 모든 허용 목록 템플릿은 `link` 빌더를 제공하고, 수집이 많은 템플릿은 연결된 에이전트가 라이브 상태를 가져올 수 있도록 GET + `publicAgent` 작업을 제공합니다.
@@ -238,6 +238,14 @@ Auto-mounted on every app. Speaks the standard MCP handshake (`initialize` → `
238
238
  Dispatch의 앱 소유 `ask_app`은 제출과 폴링 모두에서 선택된 앱의 권한을
239
239
  확인하는, 동일한 내구성 있는 작업 및 상태 계약을 사용합니다.
240
240
 
241
+ 제한된 일시적 상태 읽기를 사용할 수 없으면 `ask_app_status`는
242
+ `status: "unknown"`, `statusRead: "unavailable"`, `retryable: true`, 원래의
243
+ `app`과 `taskId`, 그리고 `pollAfterMs` / `poll`을 반환할 수 있습니다. 이는 상태
244
+ 읽기를 사용할 수 없다는 뜻이지 내구성 있는 작업이 실패했다는 뜻은 아닙니다. 정확히 같은
245
+ `app`과 `taskId`로 `ask_app_status`를 재시도하세요. 제출하면 작업이 중복될 수 있으므로
246
+ 복구를 위해 `ask_app`을 다시 제출하지 마세요. 영구적인 인증, 찾을 수 없음, 프로토콜
247
+ 오류는 계속 오류로 남습니다.
248
+
241
249
  ## 인증 {#authentication}
242
250
 
243
251
  MCP 엔드포인트는 표준 원격 MCP OAuth와 기존 베어러 토큰 폴백을 지원합니다.
@@ -539,6 +539,15 @@ amigável para hosts. A tarefa precisa ser criada antes que a primeira
539
539
  resposta possa expirar, então um URL de consulta retornado apenas após uma
540
540
  chamada longa não é um contrato de recuperação confiável.
541
541
 
542
+ Se uma leitura de status transitória e limitada estiver indisponível,
543
+ `ask_app_status` poderá retornar `status: "unknown"`,
544
+ `statusRead: "unavailable"`, `retryable: true`, o `app` e o `taskId` originais,
545
+ e `pollAfterMs` / `poll`. Isso significa que a leitura de status está
546
+ indisponível, não que a tarefa durável falhou. Tente novamente `ask_app_status`
547
+ com exatamente o mesmo `app` e `taskId`; nunca reenvie `ask_app` para recuperar,
548
+ pois o envio pode duplicar o trabalho. Erros permanentes de autenticação, de não
549
+ encontrado e de protocolo continuam sendo erros.
550
+
542
551
  ### Tour por aplicativo {#tour}
543
552
 
544
553
  Cada modelo listado como permitido que produz ou lista um recurso navegável envia um construtor `link`, e os modelos pesados enviam uma ação GET + `publicAgent` para que um agente conectado possa extrair o estado ativo:
@@ -234,6 +234,15 @@ terminal. O `ask_app` de propriedade do aplicativo no Dispatch usa o mesmo
234
234
  contrato de tarefa durável e status, com a concessão do aplicativo selecionado
235
235
  verificada tanto no envio quanto na consulta.
236
236
 
237
+ Se uma leitura de status transitória e limitada estiver indisponível,
238
+ `ask_app_status` poderá retornar `status: "unknown"`,
239
+ `statusRead: "unavailable"`, `retryable: true`, o `app` e o `taskId` originais,
240
+ e `pollAfterMs` / `poll`. Isso significa que a leitura de status está
241
+ indisponível, não que a tarefa durável falhou. Tente novamente `ask_app_status`
242
+ com exatamente o mesmo `app` e `taskId`; nunca reenvie `ask_app` para recuperar,
243
+ pois o envio pode duplicar o trabalho. Erros permanentes de autenticação, de não
244
+ encontrado e de protocolo continuam sendo erros.
245
+
237
246
  ## Autenticação {#authentication}
238
247
 
239
248
  O endpoint MCP suporta MCP remoto padrão OAuth mais o substituto de token de portador existente:
@@ -525,6 +525,12 @@ CSP 和沙箱权限等安全元数据存在于资源中
525
525
 
526
526
  MCP 主机和网关并不共享同一个较长的工具调用超时时间;有些强制执行五分钟的上限,有些则可配置。不要围绕一个阻塞十分钟的 `tools/call` 来设计外部代理集成。`ask-agent` 和 `ask_app` 会快速确认托管工作已开始,然后在内联等待到期时返回一个持久的 `taskId`。使用返回的 `app` 和 `taskId` 通过 `ask_app_status` 进行轮询;结果中包含 `pollAfterMs`,作为对主机友好的轮询间隔。任务必须在第一次响应超时之前就已创建,因此仅在长时间调用之后才返回的轮询 URL 并不是可靠的恢复约定。
527
527
 
528
+ 当有界的暂态状态读取不可用时,`ask_app_status` 可以返回
529
+ `status: "unknown"`、`statusRead: "unavailable"`、`retryable: true`、原始的
530
+ `app` 和 `taskId`,以及 `pollAfterMs` / `poll`。这表示状态读取不可用,并不表示
531
+ 持久任务失败。请使用完全相同的 `app` 和 `taskId` 重试 `ask_app_status`;切勿为恢复而
532
+ 重新提交 `ask_app`,因为提交可能会重复工作。永久性的身份验证、未找到和协议错误仍然是错误。
533
+
528
534
  ### 每应用游览 {#tour}
529
535
 
530
536
  每个生成或列出可导航资源的允许列表模板都会附带一个 `link` 构建器,而摄取量大的模板会附带一个 GET + `publicAgent` 操作,以便连接的代理可以提取实时状态:
@@ -231,6 +231,12 @@ Auto-mounted on every app. Speaks the standard MCP handshake (`initialize` → `
231
231
 
232
232
  使用这些参数调用 `ask_app_status` 进行轮询,直到任务达到终态。Dispatch 中应用自持的 `ask_app` 使用相同的持久任务和状态约定,在提交和轮询时都会检查所选应用的授权。
233
233
 
234
+ 当有界的暂态状态读取不可用时,`ask_app_status` 可以返回
235
+ `status: "unknown"`、`statusRead: "unavailable"`、`retryable: true`、原始的
236
+ `app` 和 `taskId`,以及 `pollAfterMs` / `poll`。这表示状态读取不可用,并不表示
237
+ 持久任务失败。请使用完全相同的 `app` 和 `taskId` 重试 `ask_app_status`;切勿为恢复而
238
+ 重新提交 `ask_app`,因为提交可能会重复工作。永久性的身份验证、未找到和协议错误仍然是错误。
239
+
234
240
  ## 身份验证 {#authentication}
235
241
 
236
242
  MCP 端点支持标准远程 MCP OAuth 以及现有的不记名令牌后备:
@@ -525,6 +525,12 @@ CSP 和沙箱權限等安全中繼資料存在於資源中
525
525
 
526
526
  MCP 主機與閘道器並未共用單一的長工具呼叫逾時限制;有些強制五分鐘上限,有些則可設定。請勿將外部代理整合設計成依賴十分鐘的封鎖式 `tools/call`。`ask-agent` 與 `ask_app` 會快速確認託管工作,並在行內等待逾期時傳回持久的 `taskId`。請使用傳回的 `app` 與 `taskId` 呼叫 `ask_app_status` 進行輪詢;結果包含 `pollAfterMs` 作為對主機友善的輪詢間隔。任務必須在第一次回應逾時之前就已建立,因此僅在長時間呼叫後才傳回輪詢網址並非可靠的復原機制。
527
527
 
528
+ 若有界的暫時狀態讀取無法使用,`ask_app_status` 可傳回
529
+ `status: "unknown"`、`statusRead: "unavailable"`、`retryable: true`、原始的
530
+ `app` 與 `taskId`,以及 `pollAfterMs` / `poll`。這表示狀態讀取無法使用,並不表示
531
+ 持久任務失敗。請以完全相同的 `app` 與 `taskId` 重試 `ask_app_status`;切勿為了復原而
532
+ 重新提交 `ask_app`,因為提交可能重複工作。永久性的驗證、找不到與通訊協定錯誤仍然是錯誤。
533
+
528
534
  ### 每應用游覽 {#tour}
529
535
 
530
536
  每個生成或列出可導覽資源的允許清單範本都會附帶一個 `link` 建置器,而攝取量大的範本會附帶一個 GET + `publicAgent` 操作,以便連線的代理可以擷取即時狀態:
@@ -232,6 +232,12 @@ Auto-mounted on every app. Speaks the standard MCP handshake (`initialize` → `
232
232
  請使用這些引數呼叫 `ask_app_status`,直到任務進入終止狀態。Dispatch 應用程式自有的
233
233
  `ask_app` 使用相同的持久任務與狀態合約,並在提交與輪詢時都會檢查所選應用程式的授權。
234
234
 
235
+ 若有界的暫時狀態讀取無法使用,`ask_app_status` 可傳回
236
+ `status: "unknown"`、`statusRead: "unavailable"`、`retryable: true`、原始的
237
+ `app` 與 `taskId`,以及 `pollAfterMs` / `poll`。這表示狀態讀取無法使用,並不表示
238
+ 持久任務失敗。請以完全相同的 `app` 與 `taskId` 重試 `ask_app_status`;切勿為了復原而
239
+ 重新提交 `ask_app`,因為提交可能重複工作。永久性的驗證、找不到與通訊協定錯誤仍然是錯誤。
240
+
235
241
  ## 驗證 {#authentication}
236
242
 
237
243
  MCP 端點支援標準遠端 MCP OAuth 以及現有的不記名權杖後備:
@@ -244,6 +244,14 @@ Call `ask_app_status` with those arguments until the task reaches a terminal
244
244
  state. Dispatch's app-owned `ask_app` uses the same durable task and status
245
245
  contract, with the selected-app grant checked on both submission and polling.
246
246
 
247
+ If a bounded transient status read is unavailable, `ask_app_status` can return
248
+ `status: "unknown"`, `statusRead: "unavailable"`, `retryable: true`, the
249
+ original `app` and `taskId`, and `pollAfterMs` / `poll`. This means the status
250
+ read is unavailable—not that the durable task failed. Retry `ask_app_status`
251
+ with that exact `app` and `taskId`; never resubmit `ask_app` to recover, because
252
+ submission can duplicate work. Permanent authentication, not-found, and
253
+ protocol failures remain errors.
254
+
247
255
  ## Authentication {#authentication}
248
256
 
249
257
  The MCP endpoint supports standard remote MCP OAuth plus the existing bearer-token fallback:
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.106.1",
3
+ "version": "0.106.3",
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": {
@@ -3,6 +3,8 @@ import { createHmac, timingSafeEqual } from "node:crypto";
3
3
  export interface A2AToolResultSummary {
4
4
  tool: string;
5
5
  result: string;
6
+ isError?: boolean;
7
+ completedSideEffect?: boolean;
6
8
  }
7
9
 
8
10
  export interface A2AArtifactResponseOptions {
@@ -34,6 +36,7 @@ const ARTIFACT_IDENTITY_WRITE_TOOLS = new Set([
34
36
  "add-database-item",
35
37
  "create-document",
36
38
  "update-document",
39
+ "set-document-property",
37
40
  "create-deck",
38
41
  "duplicate-deck",
39
42
  "add-slide",
@@ -545,6 +548,8 @@ function collectArtifacts(results: A2AToolResultSummary[]): {
545
548
  const forms = new Map<string, CreatedFormArtifact>();
546
549
 
547
550
  for (const toolResult of results) {
551
+ if (toolResult.isError === true || toolResult.completedSideEffect === false)
552
+ continue;
548
553
  if (toolResult.tool === "call-agent") {
549
554
  for (const artifact of parseDownstreamArtifactBlock(toolResult.result)) {
550
555
  if (artifact.kind === "deck") {
@@ -632,6 +637,7 @@ function collectArtifacts(results: A2AToolResultSummary[]): {
632
637
  toolResult.tool === "create-document" ||
633
638
  toolResult.tool === "update-document"
634
639
  ) {
640
+ if (parsed.conflict === true) continue;
635
641
  const id = stringValue(parsed.id);
636
642
  if (id) {
637
643
  documents.set(id, {
@@ -643,6 +649,17 @@ function collectArtifacts(results: A2AToolResultSummary[]): {
643
649
  continue;
644
650
  }
645
651
 
652
+ if (toolResult.tool === "set-document-property") {
653
+ const id = stringValue(parsed.documentId);
654
+ if (id) {
655
+ documents.set(id, {
656
+ id,
657
+ url: stringValue(parsed.url) ?? stringValue(parsed.urlPath),
658
+ });
659
+ }
660
+ continue;
661
+ }
662
+
646
663
  if (
647
664
  toolResult.tool === "get-document" ||
648
665
  toolResult.tool === "get-content-document"
@@ -872,6 +889,8 @@ export function extractA2AArtifactIdentities(
872
889
  };
873
890
 
874
891
  for (const result of results) {
892
+ if (result.isError === true || result.completedSideEffect === false)
893
+ continue;
875
894
  if (result.tool === "call-agent") {
876
895
  for (const identity of persistedArtifactIdentitiesFromMarker(
877
896
  result.result,
@@ -1540,3 +1559,68 @@ export function buildA2ARecoverableArtifactMessage(
1540
1559
  ...lines,
1541
1560
  ].join("\n");
1542
1561
  }
1562
+
1563
+ function mutationReceiptUrl(
1564
+ identity: A2AArtifactIdentity,
1565
+ baseUrl: string | undefined,
1566
+ ): string | undefined {
1567
+ if (
1568
+ identity.sourceAction === "call-agent" &&
1569
+ (!identity.url || identity.url.startsWith("/"))
1570
+ ) {
1571
+ return undefined;
1572
+ }
1573
+ if (identity.url) {
1574
+ return identity.url.startsWith("/")
1575
+ ? artifactUrl(baseUrl, identity.url)
1576
+ : identity.url;
1577
+ }
1578
+
1579
+ const path =
1580
+ identity.resourceType === "document"
1581
+ ? `/page/${identity.id}`
1582
+ : identity.resourceType === "deck"
1583
+ ? `/deck/${identity.id}`
1584
+ : identity.resourceType === "dashboard"
1585
+ ? `/adhoc/${identity.id}`
1586
+ : identity.resourceType === "analysis"
1587
+ ? `/analyses/${identity.id}`
1588
+ : identity.resourceType === "image"
1589
+ ? `/image/${identity.id}`
1590
+ : identity.resourceType === "design"
1591
+ ? `/design/${identity.id}`
1592
+ : undefined;
1593
+ return path ? artifactUrl(baseUrl, path) : undefined;
1594
+ }
1595
+
1596
+ /**
1597
+ * Build a bounded participant-facing receipt from authenticated artifact writes.
1598
+ * Unlike generic artifact recovery, this only trusts identities extracted from
1599
+ * successful write actions (or a signed downstream write ledger), so a read or
1600
+ * an unverified URL cannot be rounded up to a successful mutation.
1601
+ */
1602
+ export function buildA2AVerifiedMutationReceipt(
1603
+ toolResults: A2AToolResultSummary[],
1604
+ options: A2AArtifactResponseOptions = {},
1605
+ ): string | null {
1606
+ const baseUrl = normalizeBaseUrl(options.baseUrl);
1607
+ const identities = extractA2AArtifactIdentities(toolResults);
1608
+ if (identities.length === 0) return null;
1609
+
1610
+ const lines = identities.map((identity) => {
1611
+ const label =
1612
+ identity.resourceType.charAt(0).toUpperCase() +
1613
+ identity.resourceType.slice(1);
1614
+ const url = mutationReceiptUrl(identity, baseUrl);
1615
+ return url
1616
+ ? `- ${label}: ${url} (ID: ${identity.id})`
1617
+ : `- ${label} ID: ${identity.id}`;
1618
+ });
1619
+
1620
+ return [
1621
+ "A verified change was saved, but I couldn't generate the detailed summary.",
1622
+ "",
1623
+ "Saved artifacts:",
1624
+ ...lines,
1625
+ ].join("\n");
1626
+ }
@@ -719,7 +719,7 @@ export function threadMessageTextForEngine(message: any): string {
719
719
 
720
720
  const context = [
721
721
  "<integration_artifact_context>",
722
- "Trusted action history for this conversation. Resource IDs remain stable if participants rename the resource. Use these IDs when a follow-up refers to an earlier artifact, while still deciding from the request whether to update, add, supersede, or create.",
722
+ "Trusted action history for this conversation. Resource IDs remain stable if participants rename the resource. Fields such as titleAtAction are historical aliases from the time of that action, not current resource state. Use stable IDs to locate an earlier artifact, read its current state before changing it, and omit fields the user did not explicitly ask to change while still deciding whether to update, add, supersede, or create.",
723
723
  promptSafeJson(artifacts),
724
724
  "</integration_artifact_context>",
725
725
  ].join("\n");
@@ -2,6 +2,7 @@ import type { H3Event } from "h3";
2
2
 
3
3
  import {
4
4
  appendA2AArtifactLinks,
5
+ buildA2AVerifiedMutationReceipt,
5
6
  extractA2AArtifactIdentities,
6
7
  type A2AToolResultSummary,
7
8
  } from "../a2a/artifact-response.js";
@@ -83,7 +84,13 @@ const DEFERRED_RESPONSE_MAX_HANDLER_MS = 2_500;
83
84
  const EMPTY_INTEGRATION_RESPONSE_MESSAGE =
84
85
  "The model finished without a visible answer. Try again, or open the thread in Dispatch to inspect the run.";
85
86
 
86
- type ToolDoneEvent = { type: "tool_done"; tool: string; result: string };
87
+ type ToolDoneEvent = {
88
+ type: "tool_done";
89
+ tool: string;
90
+ result: string;
91
+ isError?: boolean;
92
+ completedSideEffect?: boolean;
93
+ };
87
94
 
88
95
  /**
89
96
  * Build a stable per-event dedup key from the incoming message. The same
@@ -204,6 +211,25 @@ function collectToolResultSummaries(
204
211
  return completedRun.events
205
212
  .map((runEvent) => runEvent.event)
206
213
  .filter((event): event is ToolDoneEvent => event.type === "tool_done")
214
+ .map((event) => ({
215
+ tool: event.tool,
216
+ result: event.result,
217
+ isError: event.isError,
218
+ completedSideEffect: event.completedSideEffect,
219
+ }));
220
+ }
221
+
222
+ function collectCompletedMutationToolResultSummaries(
223
+ completedRun: ActiveRun,
224
+ ): A2AToolResultSummary[] {
225
+ return completedRun.events
226
+ .map((runEvent) => runEvent.event)
227
+ .filter(
228
+ (event): event is ToolDoneEvent =>
229
+ event.type === "tool_done" &&
230
+ event.completedSideEffect === true &&
231
+ event.isError !== true,
232
+ )
207
233
  .map((event) => ({ tool: event.tool, result: event.result }));
208
234
  }
209
235
 
@@ -923,6 +949,18 @@ async function processIncomingMessage(
923
949
  queuedA2AContinuation &&
924
950
  isQueuedA2AContinuationDeferral(responseText);
925
951
 
952
+ // Compute trusted tool receipts before choosing the empty-answer
953
+ // fallback. A completed write must not be reported as though nothing
954
+ // happened merely because the model ran out of time before its prose
955
+ // summary. Read-only and unverified tool results do not qualify.
956
+ const baseUrl = process.env.APP_URL || process.env.URL || "";
957
+ const appBaseUrl = baseUrl ? withConfiguredAppBasePath(baseUrl) : "";
958
+ const toolResults = collectToolResultSummaries(completedRun);
959
+ const verifiedMutationReceipt = buildA2AVerifiedMutationReceipt(
960
+ collectCompletedMutationToolResultSummaries(completedRun),
961
+ { baseUrl: appBaseUrl || undefined },
962
+ );
963
+
926
964
  // If the run errored OR produced no text, post a graceful fallback so
927
965
  // the user isn't left wondering whether the bot saw their message.
928
966
  // Common case: an A2A delegation timed out and the agent loop bailed
@@ -953,7 +991,8 @@ async function processIncomingMessage(
953
991
  "If it was a complex analytics question, opening the analytics app " +
954
992
  "directly is the most reliable way to get an answer right now.";
955
993
  } else {
956
- responseText = EMPTY_INTEGRATION_RESPONSE_MESSAGE;
994
+ responseText =
995
+ verifiedMutationReceipt ?? EMPTY_INTEGRATION_RESPONSE_MESSAGE;
957
996
  }
958
997
  }
959
998
  if (approval?.type === "approval_required") {
@@ -965,9 +1004,6 @@ async function processIncomingMessage(
965
1004
  // platforms with rich blocks (Slack) can render a button instead
966
1005
  // of inlining a `<url|text>` link that auto-unfurls into a giant
967
1006
  // preview card.
968
- const baseUrl = process.env.APP_URL || process.env.URL || "";
969
- const appBaseUrl = baseUrl ? withConfiguredAppBasePath(baseUrl) : "";
970
- const toolResults = collectToolResultSummaries(completedRun);
971
1007
  if (!suppressPlatformReply) {
972
1008
  responseText = appendA2AArtifactLinks(responseText, toolResults, {
973
1009
  baseUrl: appBaseUrl || undefined,