@agent-native/core 0.101.5 → 0.101.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.
Files changed (149) hide show
  1. package/README.md +1 -1
  2. package/corpus/README.md +1 -1
  3. package/corpus/core/CHANGELOG.md +12 -0
  4. package/corpus/core/docs/content/locales/ar-SA/pure-agent-apps.mdx +2 -2
  5. package/corpus/core/docs/content/locales/de-DE/pure-agent-apps.mdx +2 -2
  6. package/corpus/core/docs/content/locales/es-ES/pure-agent-apps.mdx +2 -2
  7. package/corpus/core/docs/content/locales/fr-FR/pure-agent-apps.mdx +2 -2
  8. package/corpus/core/docs/content/locales/hi-IN/pure-agent-apps.mdx +2 -2
  9. package/corpus/core/docs/content/locales/ja-JP/pure-agent-apps.mdx +2 -2
  10. package/corpus/core/docs/content/locales/ko-KR/pure-agent-apps.mdx +2 -2
  11. package/corpus/core/docs/content/locales/pt-BR/pure-agent-apps.mdx +2 -2
  12. package/corpus/core/docs/content/locales/zh-CN/pure-agent-apps.mdx +2 -2
  13. package/corpus/core/docs/content/locales/zh-TW/pure-agent-apps.mdx +2 -2
  14. package/corpus/core/docs/content/pure-agent-apps.mdx +58 -14
  15. package/corpus/core/package.json +1 -1
  16. package/corpus/core/src/a2a/artifact-response.ts +255 -12
  17. package/corpus/core/src/agent/thread-data-builder.ts +84 -11
  18. package/corpus/core/src/integrations/a2a-continuation-processor.ts +99 -7
  19. package/corpus/core/src/integrations/adapters/discord.ts +3 -1
  20. package/corpus/core/src/integrations/adapters/email.ts +4 -1
  21. package/corpus/core/src/integrations/adapters/google-docs.ts +4 -1
  22. package/corpus/core/src/integrations/adapters/microsoft-teams.ts +3 -1
  23. package/corpus/core/src/integrations/adapters/slack.ts +38 -9
  24. package/corpus/core/src/integrations/adapters/telegram.ts +32 -10
  25. package/corpus/core/src/integrations/adapters/whatsapp.ts +5 -1
  26. package/corpus/core/src/integrations/index.ts +1 -0
  27. package/corpus/core/src/integrations/types.ts +9 -2
  28. package/corpus/core/src/integrations/webhook-handler.ts +69 -45
  29. package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +1 -0
  30. package/corpus/templates/analytics/AGENTS.md +6 -0
  31. package/corpus/templates/analytics/README.md +1 -1
  32. package/corpus/templates/analytics/actions/data-source-status.ts +19 -1
  33. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +5 -1
  34. package/corpus/templates/analytics/app/routes/_index.tsx +2 -2
  35. package/corpus/templates/analytics/changelog/2026-07-14-analytics-chat-guides-you-to-connect-a-data-source-with-a-di.md +6 -0
  36. package/corpus/templates/analytics/changelog/2026-07-14-analytics-landing-pages-now-clearly-position-the-app-as-an-o.md +6 -0
  37. package/corpus/templates/analytics/changelog/2026-07-15-daily-dashboard-emails-now-include-the-complete-dashboard-sc.md +6 -0
  38. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +8 -1
  39. package/corpus/templates/analytics/server/lib/dashboard-report.ts +64 -12
  40. package/corpus/templates/analytics/server/lib/real-data-actions.ts +15 -3
  41. package/corpus/templates/analytics/server/plugins/agent-chat.ts +364 -4
  42. package/corpus/templates/clips/app/components/dictate/vocabulary-section.tsx +1 -0
  43. package/corpus/templates/clips/app/components/library/folder-tree.tsx +12 -9
  44. package/corpus/templates/clips/app/components/library/library-layout.tsx +19 -16
  45. package/corpus/templates/clips/app/routes/_app.spaces._index.tsx +13 -9
  46. package/corpus/templates/clips/changelog/2026-07-15-folders-and-sidebar-links-are-clickable-again-immediately-af.md +6 -0
  47. package/corpus/templates/clips/changelog/2026-07-15-hovering-a-truncated-folder-name-now-shows-the-full-name.md +6 -0
  48. package/corpus/templates/clips/changelog/2026-07-15-space-creation-controls-are-now-shown-only-to-organization-a.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-07-15-vocabulary-terms-can-now-be-deleted-from-the-dictation-dicti.md +6 -0
  50. package/corpus/templates/clips/desktop/src/styles.css +23 -1
  51. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +49 -7
  52. package/corpus/templates/clips/server/lib/recordings.ts +5 -2
  53. package/corpus/templates/content/.agents/skills/content/SKILL.md +31 -0
  54. package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +35 -21
  55. package/corpus/templates/content/AGENTS.md +29 -7
  56. package/corpus/templates/content/actions/_database-source-utils.ts +30 -16
  57. package/corpus/templates/content/actions/_database-utils.ts +15 -1
  58. package/corpus/templates/content/actions/_local-file-documents.ts +53 -1
  59. package/corpus/templates/content/actions/_property-utils.ts +7 -1
  60. package/corpus/templates/content/actions/configure-document-property.ts +11 -0
  61. package/corpus/templates/content/actions/create-content-database.ts +11 -0
  62. package/corpus/templates/content/actions/create-document.ts +9 -0
  63. package/corpus/templates/content/actions/create-inline-content-database.ts +3 -0
  64. package/corpus/templates/content/actions/export-content-source.ts +1 -0
  65. package/corpus/templates/content/actions/get-content-database.ts +13 -1
  66. package/corpus/templates/content/actions/get-document.ts +12 -2
  67. package/corpus/templates/content/actions/import-content-source.ts +6 -0
  68. package/corpus/templates/content/actions/list-documents.ts +3 -0
  69. package/corpus/templates/content/actions/pull-document.ts +2 -0
  70. package/corpus/templates/content/actions/search-documents.ts +5 -1
  71. package/corpus/templates/content/actions/update-document.ts +16 -1
  72. package/corpus/templates/content/actions/view-screen.ts +15 -2
  73. package/corpus/templates/content/app/components/editor/DescriptionField.tsx +121 -0
  74. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +14 -0
  75. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +223 -34
  76. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +56 -8
  77. package/corpus/templates/content/app/i18n/zh-TW.ts +5 -0
  78. package/corpus/templates/content/app/i18n-data.ts +52 -0
  79. package/corpus/templates/content/changelog/2026-07-14-pages-and-databases-can-describe-themselves-with-guidance-fo.md +6 -0
  80. package/corpus/templates/content/changelog/2026-07-14-slack-follow-ups-now-retain-created-content-identity-and-per.md +6 -0
  81. package/corpus/templates/content/server/db/schema.ts +4 -0
  82. package/corpus/templates/content/server/lib/document-context.ts +76 -0
  83. package/corpus/templates/content/server/lib/public-documents.ts +3 -0
  84. package/corpus/templates/content/server/plugins/db.ts +6 -0
  85. package/corpus/templates/content/server/routes/api/document-agent-context.json.get.ts +5 -0
  86. package/corpus/templates/content/shared/api.ts +17 -0
  87. package/corpus/templates/content/shared/content-source.ts +7 -0
  88. package/corpus/templates/content/shared/properties.ts +2 -0
  89. package/dist/a2a/artifact-response.d.ts +15 -0
  90. package/dist/a2a/artifact-response.d.ts.map +1 -1
  91. package/dist/a2a/artifact-response.js +204 -5
  92. package/dist/a2a/artifact-response.js.map +1 -1
  93. package/dist/agent/thread-data-builder.d.ts +6 -0
  94. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  95. package/dist/agent/thread-data-builder.js +71 -8
  96. package/dist/agent/thread-data-builder.js.map +1 -1
  97. package/dist/collab/struct-routes.d.ts +1 -1
  98. package/dist/integrations/a2a-continuation-processor.d.ts.map +1 -1
  99. package/dist/integrations/a2a-continuation-processor.js +72 -6
  100. package/dist/integrations/a2a-continuation-processor.js.map +1 -1
  101. package/dist/integrations/adapters/discord.d.ts.map +1 -1
  102. package/dist/integrations/adapters/discord.js +1 -0
  103. package/dist/integrations/adapters/discord.js.map +1 -1
  104. package/dist/integrations/adapters/email.d.ts.map +1 -1
  105. package/dist/integrations/adapters/email.js +2 -0
  106. package/dist/integrations/adapters/email.js.map +1 -1
  107. package/dist/integrations/adapters/google-docs.d.ts.map +1 -1
  108. package/dist/integrations/adapters/google-docs.js +2 -0
  109. package/dist/integrations/adapters/google-docs.js.map +1 -1
  110. package/dist/integrations/adapters/microsoft-teams.d.ts.map +1 -1
  111. package/dist/integrations/adapters/microsoft-teams.js +1 -0
  112. package/dist/integrations/adapters/microsoft-teams.js.map +1 -1
  113. package/dist/integrations/adapters/slack.d.ts.map +1 -1
  114. package/dist/integrations/adapters/slack.js +21 -6
  115. package/dist/integrations/adapters/slack.js.map +1 -1
  116. package/dist/integrations/adapters/telegram.d.ts.map +1 -1
  117. package/dist/integrations/adapters/telegram.js +11 -2
  118. package/dist/integrations/adapters/telegram.js.map +1 -1
  119. package/dist/integrations/adapters/whatsapp.d.ts.map +1 -1
  120. package/dist/integrations/adapters/whatsapp.js +3 -0
  121. package/dist/integrations/adapters/whatsapp.js.map +1 -1
  122. package/dist/integrations/index.d.ts +1 -1
  123. package/dist/integrations/index.d.ts.map +1 -1
  124. package/dist/integrations/index.js.map +1 -1
  125. package/dist/integrations/types.d.ts +8 -2
  126. package/dist/integrations/types.d.ts.map +1 -1
  127. package/dist/integrations/types.js.map +1 -1
  128. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  129. package/dist/integrations/webhook-handler.js +40 -43
  130. package/dist/integrations/webhook-handler.js.map +1 -1
  131. package/dist/progress/routes.d.ts +1 -1
  132. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  133. package/dist/resources/handlers.d.ts +2 -2
  134. package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
  135. package/dist/server/agent-chat/action-filters-a2a.js +1 -0
  136. package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
  137. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  138. package/docs/content/locales/ar-SA/pure-agent-apps.mdx +2 -2
  139. package/docs/content/locales/de-DE/pure-agent-apps.mdx +2 -2
  140. package/docs/content/locales/es-ES/pure-agent-apps.mdx +2 -2
  141. package/docs/content/locales/fr-FR/pure-agent-apps.mdx +2 -2
  142. package/docs/content/locales/hi-IN/pure-agent-apps.mdx +2 -2
  143. package/docs/content/locales/ja-JP/pure-agent-apps.mdx +2 -2
  144. package/docs/content/locales/ko-KR/pure-agent-apps.mdx +2 -2
  145. package/docs/content/locales/pt-BR/pure-agent-apps.mdx +2 -2
  146. package/docs/content/locales/zh-CN/pure-agent-apps.mdx +2 -2
  147. package/docs/content/locales/zh-TW/pure-agent-apps.mdx +2 -2
  148. package/docs/content/pure-agent-apps.mdx +58 -14
  149. package/package.json +3 -3
@@ -637,23 +637,96 @@ export function threadDataToEngineMessages(
637
637
  for (const entry of data.messages) {
638
638
  const m = entry?.message ?? entry;
639
639
  if (!m || (m.role !== "user" && m.role !== "assistant")) continue;
640
- const text =
641
- typeof m.content === "string"
642
- ? m.content
643
- : Array.isArray(m.content)
644
- ? m.content
645
- .filter(
646
- (c: any) => c?.type === "text" && typeof c.text === "string",
647
- )
648
- .map((c: any) => c.text)
649
- .join("\n")
650
- : "";
640
+ const text = threadMessageTextForEngine(m);
651
641
  if (!text.trim()) continue;
652
642
  messages.push({ role: m.role, content: [{ type: "text", text }] });
653
643
  }
654
644
  return messages;
655
645
  }
656
646
 
647
+ const MAX_INTEGRATION_ARTIFACTS_IN_CONTEXT = 12;
648
+ const MAX_INTEGRATION_ARTIFACT_FIELD_CHARS = 500;
649
+
650
+ function boundedString(value: unknown): string | undefined {
651
+ if (typeof value !== "string") return undefined;
652
+ const trimmed = value.trim();
653
+ return trimmed
654
+ ? trimmed.slice(0, MAX_INTEGRATION_ARTIFACT_FIELD_CHARS)
655
+ : undefined;
656
+ }
657
+
658
+ function promptSafeJson(value: unknown): string {
659
+ return JSON.stringify(value)
660
+ .replaceAll("&", "\\u0026")
661
+ .replaceAll("<", "\\u003c")
662
+ .replaceAll(">", "\\u003e");
663
+ }
664
+
665
+ function messageTextContent(message: any): string {
666
+ if (typeof message?.content === "string") return message.content;
667
+ if (!Array.isArray(message?.content)) return "";
668
+ return message.content
669
+ .filter(
670
+ (part: any) => part?.type === "text" && typeof part.text === "string",
671
+ )
672
+ .map((part: any) => part.text)
673
+ .join("\n");
674
+ }
675
+
676
+ /**
677
+ * Select the participant-visible delivery for integration turns while keeping
678
+ * a compact, trusted resource ledger available to the agent. Raw tool results
679
+ * remain in thread_data for UI/audit use but are not replayed into the prompt.
680
+ */
681
+ export function threadMessageTextForEngine(message: any): string {
682
+ const delivery = message?.metadata?.integrationDelivery;
683
+ const deliveryAttempted =
684
+ message?.metadata?.integrationDeliveryAttempted === true;
685
+ const deliveredText =
686
+ message?.role === "assistant" &&
687
+ delivery?.status === "delivered" &&
688
+ typeof delivery.text === "string" &&
689
+ delivery.text.trim()
690
+ ? delivery.text
691
+ : undefined;
692
+ let text =
693
+ deliveredText ??
694
+ (message?.role === "assistant" && deliveryAttempted
695
+ ? ""
696
+ : messageTextContent(message));
697
+
698
+ if (message?.role !== "assistant") return text;
699
+ const storedArtifacts = message?.metadata?.integrationArtifacts;
700
+ if (!Array.isArray(storedArtifacts)) return text;
701
+
702
+ const artifacts = storedArtifacts
703
+ .slice(0, MAX_INTEGRATION_ARTIFACTS_IN_CONTEXT)
704
+ .map((artifact: any) => ({
705
+ resourceType: boundedString(artifact?.resourceType),
706
+ id: boundedString(artifact?.id),
707
+ sourceAction: boundedString(artifact?.sourceAction),
708
+ titleAtAction: boundedString(artifact?.titleAtAction),
709
+ url: boundedString(artifact?.url),
710
+ }))
711
+ .filter(
712
+ (artifact: {
713
+ resourceType?: string;
714
+ id?: string;
715
+ sourceAction?: string;
716
+ }) => artifact.resourceType && artifact.id && artifact.sourceAction,
717
+ );
718
+ if (artifacts.length === 0) return text;
719
+
720
+ const context = [
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.",
723
+ promptSafeJson(artifacts),
724
+ "</integration_artifact_context>",
725
+ ].join("\n");
726
+ text = text.trim() ? `${text.trim()}\n\n${context}` : context;
727
+ return text;
728
+ }
729
+
657
730
  export interface CodeAgentThreadTranscriptEvent {
658
731
  id: string;
659
732
  runId: string;
@@ -1,4 +1,8 @@
1
- import { appendA2AArtifactLinks } from "../a2a/artifact-response.js";
1
+ import {
2
+ appendA2AArtifactLinks,
3
+ extractA2AArtifactIdentities,
4
+ stripA2APersistedArtifactMarkers,
5
+ } from "../a2a/artifact-response.js";
2
6
  import { A2AClient, signA2AToken } from "../a2a/client.js";
3
7
  import type { Task } from "../a2a/types.js";
4
8
  import {
@@ -6,6 +10,8 @@ import {
6
10
  isLlmCredentialError,
7
11
  LLM_MISSING_CREDENTIALS_ERROR_CODE,
8
12
  } from "../agent/engine/credential-errors.js";
13
+ import { extractThreadMeta } from "../agent/thread-data-builder.js";
14
+ import { getThread, updateThreadData } from "../chat-threads/store.js";
9
15
  import { withConfiguredAppBasePath } from "../server/app-base-path.js";
10
16
  import { FRAMEWORK_ROUTE_PREFIX } from "../server/core-routes-plugin.js";
11
17
  import {
@@ -19,9 +25,11 @@ import {
19
25
  type A2AContinuation,
20
26
  } from "./a2a-continuations-store.js";
21
27
  import { signInternalToken } from "./internal-token.js";
28
+ import { getThreadMapping } from "./thread-mapping-store.js";
22
29
  import type {
23
30
  OutgoingMessage,
24
31
  PlatformAdapter,
32
+ PlatformDeliveryReceipt,
25
33
  PlatformRunProgress,
26
34
  } from "./types.js";
27
35
 
@@ -359,8 +367,10 @@ async function deliverAndCompleteA2AContinuation(
359
367
  if (!deliveryContinuation) return;
360
368
 
361
369
  try {
362
- const outgoing = adapter.formatAgentResponse(text);
363
- await withTimeout(
370
+ const outgoing = adapter.formatAgentResponse(
371
+ stripA2APersistedArtifactMarkers(text),
372
+ );
373
+ const deliveryReceipt = await withTimeout(
364
374
  deliverA2AContinuationResponse(
365
375
  adapter,
366
376
  deliveryContinuation,
@@ -371,6 +381,27 @@ async function deliverAndCompleteA2AContinuation(
371
381
  PLATFORM_SEND_TIMEOUT_MS,
372
382
  `${deliveryContinuation.platform} response delivery timed out`,
373
383
  );
384
+ let persistenceError: unknown;
385
+ for (let attempt = 0; attempt < 3; attempt += 1) {
386
+ try {
387
+ await persistA2AContinuationDelivery(
388
+ deliveryContinuation,
389
+ outgoing,
390
+ deliveryReceipt,
391
+ text,
392
+ );
393
+ persistenceError = undefined;
394
+ break;
395
+ } catch (err) {
396
+ persistenceError = err;
397
+ }
398
+ }
399
+ if (persistenceError) {
400
+ console.error(
401
+ `[integrations] Delivered A2A continuation ${deliveryContinuation.id} but could not persist its thread history:`,
402
+ persistenceError,
403
+ );
404
+ }
374
405
  } catch (err) {
375
406
  if (deliveryContinuation.attempts >= MAX_ATTEMPTS) {
376
407
  await failA2AContinuation(
@@ -392,7 +423,7 @@ async function deliverA2AContinuationResponse(
392
423
  message: OutgoingMessage,
393
424
  progress: PlatformRunProgress | null,
394
425
  status: "done" | "error",
395
- ): Promise<void> {
426
+ ): Promise<PlatformDeliveryReceipt> {
396
427
  if (progress) {
397
428
  try {
398
429
  await progress.onEvent({
@@ -400,8 +431,9 @@ async function deliverA2AContinuationResponse(
400
431
  agent: continuation.agentName,
401
432
  status,
402
433
  });
403
- await progress.complete(message);
404
- return;
434
+ const receipt = await progress.complete(message);
435
+ if (receipt?.status === "delivered") return receipt;
436
+ throw new Error("Continuation progress completed without delivery proof");
405
437
  } catch {
406
438
  // A resumed Slack stream can no longer be finalized (for example when
407
439
  // chat.stopStream rejects). Preserve the final answer with the same
@@ -417,9 +449,69 @@ async function deliverA2AContinuationResponse(
417
449
  }
418
450
  }
419
451
  }
420
- await adapter.sendResponse(message, continuation.incoming, {
452
+ const receipt = await adapter.sendResponse(message, continuation.incoming, {
421
453
  placeholderRef: continuation.placeholderRef ?? undefined,
422
454
  });
455
+ if (receipt?.status !== "delivered") {
456
+ throw new Error("Continuation response completed without delivery proof");
457
+ }
458
+ return receipt;
459
+ }
460
+
461
+ async function persistA2AContinuationDelivery(
462
+ continuation: A2AContinuation,
463
+ outgoing: OutgoingMessage,
464
+ receipt: PlatformDeliveryReceipt,
465
+ artifactText: string,
466
+ ): Promise<void> {
467
+ const mapping = await getThreadMapping(
468
+ continuation.platform,
469
+ continuation.externalThreadId,
470
+ );
471
+ if (!mapping) return;
472
+ const thread = await getThread(mapping.internalThreadId);
473
+ if (!thread) return;
474
+
475
+ let repo: any;
476
+ try {
477
+ repo = JSON.parse(thread.threadData || "{}");
478
+ } catch {
479
+ repo = {};
480
+ }
481
+ if (!Array.isArray(repo.messages)) repo.messages = [];
482
+
483
+ const artifacts = extractA2AArtifactIdentities([
484
+ { tool: "call-agent", result: artifactText },
485
+ ]);
486
+ const metadata: Record<string, unknown> = {
487
+ integrationDeliveryAttempted: true,
488
+ integrationDelivery: {
489
+ platform: continuation.platform,
490
+ status: "delivered",
491
+ text: outgoing.text,
492
+ deliveredAt: new Date().toISOString(),
493
+ ...(receipt.messageRefs?.length
494
+ ? { messageRefs: receipt.messageRefs }
495
+ : {}),
496
+ },
497
+ };
498
+ if (artifacts.length > 0) metadata.integrationArtifacts = artifacts;
499
+
500
+ repo.messages.push({
501
+ id: `msg-${Date.now()}-assistant-continuation`,
502
+ role: "assistant",
503
+ content: [{ type: "text", text: outgoing.text }],
504
+ createdAt: new Date().toISOString(),
505
+ metadata,
506
+ });
507
+ const meta = extractThreadMeta(repo);
508
+ await updateThreadData(
509
+ mapping.internalThreadId,
510
+ JSON.stringify(repo),
511
+ meta.title || thread.title || "Integration Chat",
512
+ meta.preview || thread.preview || "",
513
+ repo.messages.length,
514
+ );
423
515
  }
424
516
 
425
517
  async function rescheduleAndRedispatchA2AContinuation(
@@ -9,6 +9,7 @@ import type {
9
9
  IntegrationStatus,
10
10
  OutgoingMessage,
11
11
  PlatformAdapter,
12
+ PlatformDeliveryReceipt,
12
13
  } from "../types.js";
13
14
 
14
15
  const DISCORD_MAX_CONTENT_LENGTH = 2_000;
@@ -209,7 +210,7 @@ export function discordAdapter(): PlatformAdapter {
209
210
  async sendResponse(
210
211
  message: OutgoingMessage,
211
212
  context: IncomingMessage,
212
- ): Promise<void> {
213
+ ): Promise<void | PlatformDeliveryReceipt> {
213
214
  const applicationId = readString(context.platformContext.applicationId);
214
215
  const interactionToken = readString(
215
216
  context.responseContext?.interactionToken,
@@ -236,6 +237,7 @@ export function discordAdapter(): PlatformAdapter {
236
237
  chunk,
237
238
  );
238
239
  }
240
+ return { status: "delivered" };
239
241
  },
240
242
 
241
243
  formatAgentResponse(text: string): OutgoingMessage {
@@ -18,6 +18,7 @@ import type {
18
18
  OutgoingMessage,
19
19
  IntegrationStatus,
20
20
  OutboundTarget,
21
+ PlatformDeliveryReceipt,
21
22
  } from "../types.js";
22
23
 
23
24
  /** Max body length before truncation */
@@ -249,7 +250,7 @@ export function emailAdapter(): PlatformAdapter {
249
250
  async sendResponse(
250
251
  message: OutgoingMessage,
251
252
  context: IncomingMessage,
252
- ): Promise<void> {
253
+ ): Promise<void | PlatformDeliveryReceipt> {
253
254
  const agentAddress = await resolveSecret("EMAIL_AGENT_ADDRESS");
254
255
  if (!agentAddress) {
255
256
  console.error("[email] EMAIL_AGENT_ADDRESS not configured");
@@ -284,7 +285,9 @@ export function emailAdapter(): PlatformAdapter {
284
285
  });
285
286
  } catch (err) {
286
287
  console.error("[email] Failed to send response:", err);
288
+ throw err;
287
289
  }
290
+ return { status: "delivered" };
288
291
  },
289
292
 
290
293
  async sendMessageToTarget(
@@ -6,6 +6,7 @@ import type {
6
6
  IncomingMessage,
7
7
  OutgoingMessage,
8
8
  IntegrationStatus,
9
+ PlatformDeliveryReceipt,
9
10
  } from "../types.js";
10
11
 
11
12
  /** Google Docs comment replies have no formal length limit but keep it reasonable */
@@ -303,7 +304,7 @@ export function googleDocsAdapter(): PlatformAdapter {
303
304
  async sendResponse(
304
305
  message: OutgoingMessage,
305
306
  context: IncomingMessage,
306
- ): Promise<void> {
307
+ ): Promise<void | PlatformDeliveryReceipt> {
307
308
  const fileId = context.platformContext.fileId as string;
308
309
  const commentId = context.platformContext.commentId as string;
309
310
 
@@ -319,8 +320,10 @@ export function googleDocsAdapter(): PlatformAdapter {
319
320
  await replyToComment(fileId, commentId, chunk, accessToken);
320
321
  } catch (err) {
321
322
  console.error("[google-docs] Failed to send reply:", err);
323
+ throw err;
322
324
  }
323
325
  }
326
+ return { status: "delivered" };
324
327
  },
325
328
 
326
329
  formatAgentResponse(text: string): OutgoingMessage {
@@ -8,6 +8,7 @@ import type {
8
8
  IntegrationStatus,
9
9
  OutgoingMessage,
10
10
  PlatformAdapter,
11
+ PlatformDeliveryReceipt,
11
12
  } from "../types.js";
12
13
 
13
14
  const BOT_FRAMEWORK_SCOPE = "https://api.botframework.com/.default";
@@ -201,7 +202,7 @@ export function microsoftTeamsAdapter(): PlatformAdapter {
201
202
  async sendResponse(
202
203
  message: OutgoingMessage,
203
204
  context: IncomingMessage,
204
- ): Promise<void> {
205
+ ): Promise<void | PlatformDeliveryReceipt> {
205
206
  const serviceUrl = normalizeServiceUrl(
206
207
  context.platformContext.serviceUrl,
207
208
  );
@@ -242,6 +243,7 @@ export function microsoftTeamsAdapter(): PlatformAdapter {
242
243
  `Microsoft Teams reply failed (HTTP ${response.status})`,
243
244
  );
244
245
  }
246
+ return { status: "delivered" };
245
247
  },
246
248
 
247
249
  formatAgentResponse(text: string): OutgoingMessage {
@@ -23,6 +23,7 @@ import type {
23
23
  OutboundTarget,
24
24
  PlatformRunProgress,
25
25
  PlatformRunProgressRef,
26
+ PlatformDeliveryReceipt,
26
27
  IntegrationContextMessage,
27
28
  IntegrationFileReference,
28
29
  } from "../types.js";
@@ -431,11 +432,12 @@ export function slackAdapter(
431
432
  message: OutgoingMessage,
432
433
  context: IncomingMessage,
433
434
  opts?: { placeholderRef?: string },
434
- ): Promise<void> {
435
+ ): Promise<void | PlatformDeliveryReceipt> {
435
436
  const token = await resolveBotToken(context);
436
437
  if (!token) {
437
- console.error("[slack] SLACK_BOT_TOKEN not configured");
438
- return;
438
+ throw new Error(
439
+ "[slack] Cannot deliver response: no Slack bot token is configured",
440
+ );
439
441
  }
440
442
 
441
443
  const channelId = context.platformContext.channelId as string;
@@ -458,6 +460,7 @@ export function slackAdapter(
458
460
  return;
459
461
  }
460
462
  const restChunks = chunks.slice(1);
463
+ const messageRefs: string[] = [];
461
464
 
462
465
  const finalBlocks =
463
466
  blocks ??
@@ -489,14 +492,29 @@ export function slackAdapter(
489
492
  const data = (await res.json()) as {
490
493
  ok: boolean;
491
494
  error?: string;
495
+ ts?: string;
492
496
  };
493
497
  if (!data.ok) {
494
498
  console.error("[slack] chat.update error:", data.error);
495
499
  // Fall back to a fresh post so the user still sees a reply
496
- await postFresh(token, channelId, threadTs, baseBody);
500
+ const postedTs = await postFresh(
501
+ token,
502
+ channelId,
503
+ threadTs,
504
+ baseBody,
505
+ );
506
+ if (postedTs) messageRefs.push(postedTs);
507
+ } else {
508
+ messageRefs.push(data.ts || placeholderRef);
497
509
  }
498
510
  } else {
499
- await postFresh(token, channelId, threadTs, baseBody);
511
+ const postedTs = await postFresh(
512
+ token,
513
+ channelId,
514
+ threadTs,
515
+ baseBody,
516
+ );
517
+ if (postedTs) messageRefs.push(postedTs);
500
518
  }
501
519
 
502
520
  // Clear the AI-assistant "is thinking…" status now that we've
@@ -507,14 +525,19 @@ export function slackAdapter(
507
525
 
508
526
  // Overflow chunks (rare) — post as plain follow-ups in the same thread
509
527
  for (const chunk of restChunks) {
510
- await postFresh(token, channelId, threadTs, {
528
+ const postedTs = await postFresh(token, channelId, threadTs, {
511
529
  channel: channelId,
512
530
  text: chunk,
513
531
  unfurl_links: false,
514
532
  unfurl_media: false,
515
533
  mrkdwn: true,
516
534
  });
535
+ if (postedTs) messageRefs.push(postedTs);
517
536
  }
537
+ return {
538
+ status: "delivered",
539
+ ...(messageRefs.length > 0 ? { messageRefs } : {}),
540
+ };
518
541
  } catch (err) {
519
542
  console.error("[slack] Failed to send message:", err);
520
543
  throw err;
@@ -992,7 +1015,7 @@ async function postFresh(
992
1015
  channelId: string,
993
1016
  threadTs: string | undefined,
994
1017
  body: Record<string, unknown>,
995
- ): Promise<void> {
1018
+ ): Promise<string | undefined> {
996
1019
  const hasBlocks =
997
1020
  Array.isArray(body.blocks) && (body.blocks as unknown[]).length > 0;
998
1021
  if (
@@ -1000,7 +1023,7 @@ async function postFresh(
1000
1023
  body.text.trim().length === 0 &&
1001
1024
  !hasBlocks
1002
1025
  ) {
1003
- return;
1026
+ return undefined;
1004
1027
  }
1005
1028
 
1006
1029
  const payload: Record<string, unknown> = {
@@ -1016,11 +1039,16 @@ async function postFresh(
1016
1039
  },
1017
1040
  body: JSON.stringify(payload),
1018
1041
  });
1019
- const data = (await res.json()) as { ok: boolean; error?: string };
1042
+ const data = (await res.json()) as {
1043
+ ok: boolean;
1044
+ error?: string;
1045
+ ts?: string;
1046
+ };
1020
1047
  if (!data.ok) {
1021
1048
  console.error("[slack] chat.postMessage error:", data.error);
1022
1049
  throw new Error(data.error || "chat.postMessage failed");
1023
1050
  }
1051
+ return data.ts;
1024
1052
  }
1025
1053
 
1026
1054
  async function slackApiFetch(
@@ -1871,6 +1899,7 @@ function createSlackRunProgress(
1871
1899
  : {}),
1872
1900
  });
1873
1901
  setSlackAssistantStatus(token, channel, threadTs, "");
1902
+ return { status: "delivered", messageRefs: [streamTs] };
1874
1903
  },
1875
1904
  async fail(message) {
1876
1905
  if (pendingTimer) clearTimeout(pendingTimer);
@@ -10,6 +10,7 @@ import type {
10
10
  OutgoingMessage,
11
11
  IntegrationStatus,
12
12
  OutboundTarget,
13
+ PlatformDeliveryReceipt,
13
14
  } from "../types.js";
14
15
 
15
16
  /** Telegram's max message length */
@@ -194,7 +195,7 @@ export function telegramAdapter(): PlatformAdapter {
194
195
  async sendResponse(
195
196
  message: OutgoingMessage,
196
197
  context: IncomingMessage,
197
- ): Promise<void> {
198
+ ): Promise<void | PlatformDeliveryReceipt> {
198
199
  const token = await resolveSecret("TELEGRAM_BOT_TOKEN");
199
200
  if (!token) {
200
201
  console.error("[telegram] TELEGRAM_BOT_TOKEN not configured");
@@ -235,22 +236,43 @@ export function telegramAdapter(): PlatformAdapter {
235
236
  if (!data.ok) {
236
237
  // Retry without Markdown if parsing fails
237
238
  if (data.description?.includes("parse")) {
238
- await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
239
- method: "POST",
240
- headers: { "Content-Type": "application/json" },
241
- body: JSON.stringify({
242
- ...body,
243
- parse_mode: undefined,
244
- }),
245
- });
239
+ const retry = await fetch(
240
+ `https://api.telegram.org/bot${token}/sendMessage`,
241
+ {
242
+ method: "POST",
243
+ headers: { "Content-Type": "application/json" },
244
+ body: JSON.stringify({
245
+ ...body,
246
+ parse_mode: undefined,
247
+ }),
248
+ },
249
+ );
250
+ if (!retry.ok) {
251
+ throw new Error(
252
+ `Telegram sendMessage retry failed (HTTP ${retry.status})`,
253
+ );
254
+ }
255
+ const retryData = (await retry.json()) as {
256
+ ok: boolean;
257
+ description?: string;
258
+ };
259
+ if (!retryData.ok) {
260
+ throw new Error(
261
+ `Telegram sendMessage retry failed: ${retryData.description ?? "unknown error"}`,
262
+ );
263
+ }
246
264
  } else {
247
- console.error("[telegram] sendMessage error:", data.description);
265
+ throw new Error(
266
+ `Telegram sendMessage failed: ${data.description ?? "unknown error"}`,
267
+ );
248
268
  }
249
269
  }
250
270
  } catch (err) {
251
271
  console.error("[telegram] Failed to send message:", err);
272
+ throw err;
252
273
  }
253
274
  }
275
+ return { status: "delivered" };
254
276
  },
255
277
 
256
278
  async sendMessageToTarget(
@@ -8,6 +8,7 @@ import type {
8
8
  IncomingMessage,
9
9
  OutgoingMessage,
10
10
  IntegrationStatus,
11
+ PlatformDeliveryReceipt,
11
12
  } from "../types.js";
12
13
 
13
14
  /** WhatsApp's max message length */
@@ -240,7 +241,7 @@ export function whatsappAdapter(): PlatformAdapter {
240
241
  async sendResponse(
241
242
  message: OutgoingMessage,
242
243
  context: IncomingMessage,
243
- ): Promise<void> {
244
+ ): Promise<void | PlatformDeliveryReceipt> {
244
245
  const accessToken = await resolveSecret("WHATSAPP_ACCESS_TOKEN");
245
246
  const phoneNumberId = await resolveSecret("WHATSAPP_PHONE_NUMBER_ID");
246
247
  if (!accessToken || !phoneNumberId) {
@@ -279,11 +280,14 @@ export function whatsappAdapter(): PlatformAdapter {
279
280
  if (!res.ok) {
280
281
  const data = await res.json().catch(() => ({}));
281
282
  console.error("[whatsapp] sendMessage error:", data);
283
+ throw new Error(`WhatsApp sendMessage failed (HTTP ${res.status})`);
282
284
  }
283
285
  } catch (err) {
284
286
  console.error("[whatsapp] Failed to send message:", err);
287
+ throw err;
285
288
  }
286
289
  }
290
+ return { status: "delivered" };
287
291
  },
288
292
 
289
293
  formatAgentResponse(text: string): OutgoingMessage {
@@ -15,6 +15,7 @@ export type {
15
15
  IntegrationConversationType,
16
16
  IntegrationTriggerKind,
17
17
  PlatformRunProgress,
18
+ PlatformDeliveryReceipt,
18
19
  } from "./types.js";
19
20
  export { assertPlatformCapability } from "./types.js";
20
21
 
@@ -127,6 +127,13 @@ export interface OutgoingMessage {
127
127
  platformContext: Record<string, unknown>;
128
128
  }
129
129
 
130
+ /** Provider-confirmed references for a successfully delivered response. */
131
+ export interface PlatformDeliveryReceipt {
132
+ status: "delivered";
133
+ /** Opaque provider message ids/timestamps; never message content. */
134
+ messageRefs?: string[];
135
+ }
136
+
130
137
  /**
131
138
  * Proactive outbound message target for a platform.
132
139
  * Used when the agent needs to send to a saved destination instead of replying
@@ -197,7 +204,7 @@ export interface PlatformRunProgress {
197
204
  /** Receive normalized agent events. Implementations should throttle writes. */
198
205
  onEvent(event: AgentChatEvent): Promise<void> | void;
199
206
  /** Finalize the provider-native progress surface with the answer. */
200
- complete(message: OutgoingMessage): Promise<void>;
207
+ complete(message: OutgoingMessage): Promise<void | PlatformDeliveryReceipt>;
201
208
  /** Mark the provider-native surface failed and leave a retryable explanation. */
202
209
  fail?(message: string): Promise<void>;
203
210
  }
@@ -316,7 +323,7 @@ export interface PlatformAdapter {
316
323
  message: OutgoingMessage,
317
324
  context: IncomingMessage,
318
325
  opts?: { placeholderRef?: string },
319
- ): Promise<void>;
326
+ ): Promise<void | PlatformDeliveryReceipt>;
320
327
 
321
328
  /**
322
329
  * Send a short best-effort system notice to the conversation the incoming