@agent-native/core 0.101.6 → 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 (101) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +6 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/a2a/artifact-response.ts +255 -12
  5. package/corpus/core/src/agent/thread-data-builder.ts +84 -11
  6. package/corpus/core/src/integrations/a2a-continuation-processor.ts +99 -7
  7. package/corpus/core/src/integrations/adapters/discord.ts +3 -1
  8. package/corpus/core/src/integrations/adapters/email.ts +4 -1
  9. package/corpus/core/src/integrations/adapters/google-docs.ts +4 -1
  10. package/corpus/core/src/integrations/adapters/microsoft-teams.ts +3 -1
  11. package/corpus/core/src/integrations/adapters/slack.ts +38 -9
  12. package/corpus/core/src/integrations/adapters/telegram.ts +32 -10
  13. package/corpus/core/src/integrations/adapters/whatsapp.ts +5 -1
  14. package/corpus/core/src/integrations/index.ts +1 -0
  15. package/corpus/core/src/integrations/types.ts +9 -2
  16. package/corpus/core/src/integrations/webhook-handler.ts +69 -45
  17. package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +1 -0
  18. package/corpus/templates/content/.agents/skills/content/SKILL.md +31 -0
  19. package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +35 -21
  20. package/corpus/templates/content/AGENTS.md +29 -7
  21. package/corpus/templates/content/actions/_database-source-utils.ts +30 -16
  22. package/corpus/templates/content/actions/_database-utils.ts +15 -1
  23. package/corpus/templates/content/actions/_local-file-documents.ts +53 -1
  24. package/corpus/templates/content/actions/_property-utils.ts +7 -1
  25. package/corpus/templates/content/actions/configure-document-property.ts +11 -0
  26. package/corpus/templates/content/actions/create-content-database.ts +11 -0
  27. package/corpus/templates/content/actions/create-document.ts +9 -0
  28. package/corpus/templates/content/actions/create-inline-content-database.ts +3 -0
  29. package/corpus/templates/content/actions/export-content-source.ts +1 -0
  30. package/corpus/templates/content/actions/get-content-database.ts +13 -1
  31. package/corpus/templates/content/actions/get-document.ts +12 -2
  32. package/corpus/templates/content/actions/import-content-source.ts +6 -0
  33. package/corpus/templates/content/actions/list-documents.ts +3 -0
  34. package/corpus/templates/content/actions/pull-document.ts +2 -0
  35. package/corpus/templates/content/actions/search-documents.ts +5 -1
  36. package/corpus/templates/content/actions/update-document.ts +16 -1
  37. package/corpus/templates/content/actions/view-screen.ts +15 -2
  38. package/corpus/templates/content/app/components/editor/DescriptionField.tsx +121 -0
  39. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +14 -0
  40. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +223 -34
  41. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +56 -8
  42. package/corpus/templates/content/app/i18n/zh-TW.ts +5 -0
  43. package/corpus/templates/content/app/i18n-data.ts +52 -0
  44. package/corpus/templates/content/changelog/2026-07-14-pages-and-databases-can-describe-themselves-with-guidance-fo.md +6 -0
  45. package/corpus/templates/content/changelog/2026-07-14-slack-follow-ups-now-retain-created-content-identity-and-per.md +6 -0
  46. package/corpus/templates/content/server/db/schema.ts +4 -0
  47. package/corpus/templates/content/server/lib/document-context.ts +76 -0
  48. package/corpus/templates/content/server/lib/public-documents.ts +3 -0
  49. package/corpus/templates/content/server/plugins/db.ts +6 -0
  50. package/corpus/templates/content/server/routes/api/document-agent-context.json.get.ts +5 -0
  51. package/corpus/templates/content/shared/api.ts +17 -0
  52. package/corpus/templates/content/shared/content-source.ts +7 -0
  53. package/corpus/templates/content/shared/properties.ts +2 -0
  54. package/dist/a2a/artifact-response.d.ts +15 -0
  55. package/dist/a2a/artifact-response.d.ts.map +1 -1
  56. package/dist/a2a/artifact-response.js +204 -5
  57. package/dist/a2a/artifact-response.js.map +1 -1
  58. package/dist/agent/thread-data-builder.d.ts +6 -0
  59. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  60. package/dist/agent/thread-data-builder.js +71 -8
  61. package/dist/agent/thread-data-builder.js.map +1 -1
  62. package/dist/integrations/a2a-continuation-processor.d.ts.map +1 -1
  63. package/dist/integrations/a2a-continuation-processor.js +72 -6
  64. package/dist/integrations/a2a-continuation-processor.js.map +1 -1
  65. package/dist/integrations/adapters/discord.d.ts.map +1 -1
  66. package/dist/integrations/adapters/discord.js +1 -0
  67. package/dist/integrations/adapters/discord.js.map +1 -1
  68. package/dist/integrations/adapters/email.d.ts.map +1 -1
  69. package/dist/integrations/adapters/email.js +2 -0
  70. package/dist/integrations/adapters/email.js.map +1 -1
  71. package/dist/integrations/adapters/google-docs.d.ts.map +1 -1
  72. package/dist/integrations/adapters/google-docs.js +2 -0
  73. package/dist/integrations/adapters/google-docs.js.map +1 -1
  74. package/dist/integrations/adapters/microsoft-teams.d.ts.map +1 -1
  75. package/dist/integrations/adapters/microsoft-teams.js +1 -0
  76. package/dist/integrations/adapters/microsoft-teams.js.map +1 -1
  77. package/dist/integrations/adapters/slack.d.ts.map +1 -1
  78. package/dist/integrations/adapters/slack.js +21 -6
  79. package/dist/integrations/adapters/slack.js.map +1 -1
  80. package/dist/integrations/adapters/telegram.d.ts.map +1 -1
  81. package/dist/integrations/adapters/telegram.js +11 -2
  82. package/dist/integrations/adapters/telegram.js.map +1 -1
  83. package/dist/integrations/adapters/whatsapp.d.ts.map +1 -1
  84. package/dist/integrations/adapters/whatsapp.js +3 -0
  85. package/dist/integrations/adapters/whatsapp.js.map +1 -1
  86. package/dist/integrations/index.d.ts +1 -1
  87. package/dist/integrations/index.d.ts.map +1 -1
  88. package/dist/integrations/index.js.map +1 -1
  89. package/dist/integrations/types.d.ts +8 -2
  90. package/dist/integrations/types.d.ts.map +1 -1
  91. package/dist/integrations/types.js.map +1 -1
  92. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  93. package/dist/integrations/webhook-handler.js +40 -43
  94. package/dist/integrations/webhook-handler.js.map +1 -1
  95. package/dist/observability/routes.d.ts +1 -1
  96. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  97. package/dist/resources/handlers.d.ts +1 -1
  98. package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
  99. package/dist/server/agent-chat/action-filters-a2a.js +1 -0
  100. package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
  101. package/package.json +1 -1
@@ -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
@@ -2,6 +2,7 @@ import type { H3Event } from "h3";
2
2
 
3
3
  import {
4
4
  appendA2AArtifactLinks,
5
+ extractA2AArtifactIdentities,
5
6
  type A2AToolResultSummary,
6
7
  } from "../a2a/artifact-response.js";
7
8
  import { collectFinalResponseTextFromAgentEvents } from "../a2a/response-text.js";
@@ -35,6 +36,7 @@ import {
35
36
  import {
36
37
  buildAssistantMessage,
37
38
  extractThreadMeta,
39
+ threadDataToEngineMessages,
38
40
  } from "../agent/thread-data-builder.js";
39
41
  import { attachToolSearch } from "../agent/tool-search.js";
40
42
  import { createThread, getThread } from "../chat-threads/store.js";
@@ -63,7 +65,11 @@ import {
63
65
  } from "./pending-tasks-store.js";
64
66
  import { integrationScopeSubjectKey } from "./scope-store.js";
65
67
  import { getThreadMapping, saveThreadMapping } from "./thread-mapping-store.js";
66
- import type { PlatformAdapter, IncomingMessage } from "./types.js";
68
+ import type {
69
+ PlatformAdapter,
70
+ IncomingMessage,
71
+ PlatformDeliveryReceipt,
72
+ } from "./types.js";
67
73
  import {
68
74
  listIntegrationUsageBudgets,
69
75
  releaseIntegrationUsageBudget,
@@ -710,34 +716,7 @@ async function processIncomingMessage(
710
716
  }
711
717
  const existingMessages: EngineMessage[] = [];
712
718
  if (thread?.threadData) {
713
- try {
714
- const data = JSON.parse(thread.threadData);
715
- if (Array.isArray(data.messages)) {
716
- for (const msg of data.messages) {
717
- const m = msg.message ?? msg;
718
- const textContent =
719
- typeof m.content === "string"
720
- ? m.content
721
- : Array.isArray(m.content)
722
- ? m.content
723
- .filter((c: any) => c.type === "text")
724
- .map((c: any) => c.text)
725
- .join("\n")
726
- : "";
727
- if (m.role === "user") {
728
- existingMessages.push({
729
- role: "user",
730
- content: [{ type: "text", text: textContent }],
731
- });
732
- } else if (m.role === "assistant") {
733
- existingMessages.push({
734
- role: "assistant",
735
- content: [{ type: "text", text: textContent }],
736
- });
737
- }
738
- }
739
- }
740
- } catch {}
719
+ existingMessages.push(...threadDataToEngineMessages(thread.threadData));
741
720
  }
742
721
 
743
722
  // Add the new user message. Include verified platform identity as lightweight
@@ -988,12 +967,11 @@ async function processIncomingMessage(
988
967
  // preview card.
989
968
  const baseUrl = process.env.APP_URL || process.env.URL || "";
990
969
  const appBaseUrl = baseUrl ? withConfiguredAppBasePath(baseUrl) : "";
970
+ const toolResults = collectToolResultSummaries(completedRun);
991
971
  if (!suppressPlatformReply) {
992
- responseText = appendA2AArtifactLinks(
993
- responseText,
994
- collectToolResultSummaries(completedRun),
995
- { baseUrl: appBaseUrl || undefined },
996
- );
972
+ responseText = appendA2AArtifactLinks(responseText, toolResults, {
973
+ baseUrl: appBaseUrl || undefined,
974
+ });
997
975
  }
998
976
  const threadDeepLinkUrl =
999
977
  appBaseUrl && threadId
@@ -1002,34 +980,60 @@ async function processIncomingMessage(
1002
980
 
1003
981
  // Format and send back to platform — update the "thinking…"
1004
982
  // placeholder in place if the adapter supplied one.
983
+ let deliveredResponse:
984
+ | {
985
+ platform: string;
986
+ status: "delivered";
987
+ text: string;
988
+ deliveredAt: string;
989
+ messageRefs?: string[];
990
+ }
991
+ | undefined;
1005
992
  if (!suppressPlatformReply) {
1006
993
  const outgoing = adapter.formatAgentResponse(responseText, {
1007
994
  threadDeepLinkUrl,
1008
995
  });
1009
- let delivered = false;
996
+ let deliveryReceipt: void | PlatformDeliveryReceipt;
1010
997
  if (queuedA2AContinuation && progress?.ref) {
1011
998
  // Post substantive parent results as a normal thread reply while
1012
999
  // the one continuation that claimed this resumable stream keeps
1013
1000
  // it open for its eventual terminal result.
1014
- await adapter.sendResponse(outgoing, incoming, {
1001
+ deliveryReceipt = await adapter.sendResponse(outgoing, incoming, {
1015
1002
  placeholderRef: opts.placeholderRef,
1016
1003
  });
1017
- delivered = true;
1018
1004
  } else if (progress) {
1019
1005
  try {
1020
- await progress.complete(outgoing);
1021
- delivered = true;
1006
+ deliveryReceipt = await progress.complete(outgoing);
1022
1007
  } catch {
1023
- await adapter.sendResponse(outgoing, incoming, {
1024
- placeholderRef: opts.placeholderRef,
1025
- });
1026
- delivered = true;
1008
+ deliveryReceipt = await adapter.sendResponse(
1009
+ outgoing,
1010
+ incoming,
1011
+ {
1012
+ placeholderRef: opts.placeholderRef,
1013
+ },
1014
+ );
1027
1015
  }
1028
1016
  } else {
1029
- await adapter.sendResponse(outgoing, incoming, {
1017
+ deliveryReceipt = await adapter.sendResponse(outgoing, incoming, {
1030
1018
  placeholderRef: opts.placeholderRef,
1031
1019
  });
1032
- delivered = true;
1020
+ }
1021
+ const delivered = deliveryReceipt?.status === "delivered";
1022
+ if (!delivered) {
1023
+ throw new Error(
1024
+ `${incoming.platform} response completed without delivery proof`,
1025
+ );
1026
+ }
1027
+ if (delivered) {
1028
+ deliveredResponse = {
1029
+ platform: incoming.platform,
1030
+ status: "delivered",
1031
+ text: outgoing.text,
1032
+ deliveredAt: new Date().toISOString(),
1033
+ ...(deliveryReceipt?.messageRefs?.length
1034
+ ? { messageRefs: deliveryReceipt.messageRefs }
1035
+ : {}),
1036
+ };
1033
1037
  }
1034
1038
  if (slackInputRequest && delivered && incoming.senderId) {
1035
1039
  await setIntegrationAwaitingInput({
@@ -1078,6 +1082,8 @@ async function processIncomingMessage(
1078
1082
  incoming.text,
1079
1083
  completedRun,
1080
1084
  thread,
1085
+ deliveredResponse,
1086
+ toolResults,
1081
1087
  );
1082
1088
  await recordIntegrationUsage({
1083
1089
  usage,
@@ -1533,6 +1539,14 @@ async function persistThreadData(
1533
1539
  userText: string,
1534
1540
  completedRun: ActiveRun,
1535
1541
  thread: any,
1542
+ deliveredResponse?: {
1543
+ platform: string;
1544
+ status: "delivered";
1545
+ text: string;
1546
+ deliveredAt: string;
1547
+ messageRefs?: string[];
1548
+ },
1549
+ toolResults: A2AToolResultSummary[] = [],
1536
1550
  ): Promise<void> {
1537
1551
  try {
1538
1552
  let repo: any;
@@ -1556,6 +1570,16 @@ async function persistThreadData(
1556
1570
  completedRun.events ?? [],
1557
1571
  completedRun.runId,
1558
1572
  );
1573
+ if (assistantMsg) {
1574
+ assistantMsg.metadata.integrationDeliveryAttempted = true;
1575
+ if (deliveredResponse) {
1576
+ assistantMsg.metadata.integrationDelivery = deliveredResponse;
1577
+ const artifactIdentities = extractA2AArtifactIdentities(toolResults);
1578
+ if (artifactIdentities.length > 0) {
1579
+ assistantMsg.metadata.integrationArtifacts = artifactIdentities;
1580
+ }
1581
+ }
1582
+ }
1559
1583
 
1560
1584
  repo.messages.push(userMsg);
1561
1585
  if (assistantMsg) {
@@ -110,6 +110,7 @@ export function assembleA2AFinalResponse(
110
110
  const finalText = appendA2AArtifactLinks(responseText, [...toolResults], {
111
111
  baseUrl: options.baseUrl ?? resolveArtifactBaseUrl(options.event),
112
112
  includeReferencedArtifacts: true,
113
+ includePersistedArtifactMarker: true,
113
114
  });
114
115
  if (terminalError && !finalText.trim()) {
115
116
  throw new Error(formatA2ATerminalError(terminalError));
@@ -101,6 +101,37 @@ inferences for confirmation, then submit once. When required information is
101
101
  missing, keep the clarification in the originating thread and retain earlier
102
102
  answers as context.
103
103
 
104
+ ### Slack Follow-ups And Corrections
105
+
106
+ A follow-up in an existing Slack thread is not automatically a new intake. Read
107
+ the thread context and inspect the prior Content artifact identity first,
108
+ including any returned document ID or `/page/<id>` path and the canonical
109
+ database row when available. Then choose the operation that matches the user's
110
+ intent:
111
+
112
+ - **Update** the same document for corrections, refinements, status changes, or
113
+ renames that still describe the same request. A rename changes the title, not
114
+ the artifact identity: preserve the stable Content document ID and page path.
115
+ - **Add** new details to the same document when the follow-up extends the
116
+ original request without replacing it.
117
+ - **Supersede** only when the user intends a replacement artifact or distinct
118
+ successor and the workspace's schema or instructions define how that
119
+ relationship is recorded. Preserve a concrete link to the prior artifact.
120
+ - **Create** only when the follow-up is genuinely a separate request or the user
121
+ explicitly asks for a new artifact. Do not blindly submit another row merely
122
+ because a new Slack message arrived.
123
+
124
+ Apply people fields from verified identity and intent, not from convenient
125
+ guesswork:
126
+
127
+ - When the database has a `Requester` field, default it to the verified Slack
128
+ sender unless the user explicitly identifies a different requester.
129
+ - A named doer such as "for Apoorva" maps to `Assignee` when that field exists.
130
+ Naming an assignee never changes or replaces `Requester`.
131
+ - Resolve named people to the database's accepted person identity before
132
+ writing. If a named person cannot be resolved unambiguously, clarify in the
133
+ originating Slack thread; never omit, downgrade, or silently drop the person.
134
+
104
135
  ## Local File Mode
105
136
 
106
137
  Install into an existing repo with:
@@ -7,7 +7,10 @@ description: >-
7
7
 
8
8
  # Document Editing
9
9
 
10
- Documents are stored in the SQL database via Drizzle ORM. Each document has a title, markdown content, optional parent (for nesting), and a position for ordering.
10
+ Documents are stored in the SQL database via Drizzle ORM. Each document has a
11
+ title, stable description, markdown content, optional parent (for nesting), and
12
+ a position for ordering. The description explains why the page exists and what
13
+ belongs there; it is not a changing synopsis of the current body.
11
14
 
12
15
  ## Scripts
13
16
 
@@ -48,6 +51,7 @@ Create a new document.
48
51
  pnpm action create-document --title "Meeting Notes" --content "# Meeting Notes\n\nAttendees: ..."
49
52
  pnpm action create-document --title "Sub Page" --parentId parent123
50
53
  pnpm action create-document --title "My Page" --icon "📝"
54
+ pnpm action create-document --title "Research" --description "Evidence and source notes that support the current project"
51
55
  ```
52
56
 
53
57
  ### edit-document
@@ -73,6 +77,7 @@ Update an existing document. Use for **full rewrites or new content**, not for s
73
77
  pnpm action update-document --id abc123 --title "New Title"
74
78
  pnpm action update-document --id abc123 --content "# Updated Content\n\nNew text here"
75
79
  pnpm action update-document --id abc123 --title "New Title" --content "New content"
80
+ pnpm action update-document --id abc123 --description "Stable guidance for what belongs on this page"
76
81
  ```
77
82
 
78
83
  ### delete-document
@@ -123,17 +128,18 @@ Always run this after any document modification to update the sidebar.
123
128
 
124
129
  ## Document Schema
125
130
 
126
- | Column | Type | Description |
127
- | ------------ | ------- | --------------------------------------- |
128
- | `id` | text | Primary key (12-char hex string) |
129
- | `parent_id` | text | Parent document ID (null for root) |
130
- | `title` | text | Document title (default: "Untitled") |
131
- | `content` | text | Markdown content |
132
- | `icon` | text | Emoji icon (optional) |
133
- | `position` | integer | Sort order within parent (0-based) |
134
- | `is_favorite`| integer | Whether document is favorited (0 or 1) |
135
- | `created_at` | text | ISO timestamp |
136
- | `updated_at` | text | ISO timestamp |
131
+ | Column | Type | Description |
132
+ | ------------- | ------- | -------------------------------------- |
133
+ | `id` | text | Primary key (12-char hex string) |
134
+ | `parent_id` | text | Parent document ID (null for root) |
135
+ | `title` | text | Document title (default: "Untitled") |
136
+ | `description` | text | Stable semantic guidance for the page |
137
+ | `content` | text | Markdown content |
138
+ | `icon` | text | Emoji icon (optional) |
139
+ | `position` | integer | Sort order within parent (0-based) |
140
+ | `is_favorite` | integer | Whether document is favorited (0 or 1) |
141
+ | `created_at` | text | ISO timestamp |
142
+ | `updated_at` | text | ISO timestamp |
137
143
 
138
144
  ## Content Format
139
145
 
@@ -142,21 +148,29 @@ Documents use **markdown** for content. The editor renders markdown in real time
142
148
  ## Parent-Child Hierarchy
143
149
 
144
150
  Documents form a tree via `parent_id`:
151
+
145
152
  - Root documents have `parent_id = null`
146
153
  - Child documents reference their parent's `id`
147
154
  - Deleting a parent recursively deletes all children
148
155
  - Position determines ordering within the same parent
149
156
 
157
+ Descriptions are owned; context is inherited. `get-document` and `view-screen`
158
+ return the focused page's own description plus a computed root-to-parent
159
+ `contextPath`. Use that path to understand where the page lives, but never copy
160
+ ancestor descriptions into the child. Database, property, and option
161
+ descriptions narrow the guidance further when working with structured values.
162
+
150
163
  ## Common Tasks
151
164
 
152
- | User says | What to do |
153
- | ----------------------------- | ---------------------------------------------------------------- |
154
- | "Create a page about X" | `create-document --title "X" --content "# X\n\n..."` |
155
- | "Find my meeting notes" | `search-documents --query "meeting notes"` |
156
- | "Fix a typo / edit a line" | `view-screen` to get ID, then `edit-document --id ... --find "old" --replace "new"` |
157
- | "Rewrite this document" | `view-screen` to get ID, then `update-document --id ... --content ...` |
158
- | "Delete this page" | `view-screen` to get ID, then `delete-document --id ...` |
159
- | "Add a sub-page" | `create-document --title "Sub" --parentId <parentId>` |
160
- | "Show me the document tree" | `list-documents` |
165
+ | User says | What to do |
166
+ | ---------------------------- | ----------------------------------------------------------------------------------- |
167
+ | "Create a page about X" | `create-document --title "X" --content "# X\n\n..."` |
168
+ | "Describe what belongs here" | `update-document --id ... --description "..."` |
169
+ | "Find my meeting notes" | `search-documents --query "meeting notes"` |
170
+ | "Fix a typo / edit a line" | `view-screen` to get ID, then `edit-document --id ... --find "old" --replace "new"` |
171
+ | "Rewrite this document" | `view-screen` to get ID, then `update-document --id ... --content ...` |
172
+ | "Delete this page" | `view-screen` to get ID, then `delete-document --id ...` |
173
+ | "Add a sub-page" | `create-document --title "Sub" --parentId <parentId>` |
174
+ | "Show me the document tree" | `list-documents` |
161
175
 
162
176
  Always run `refresh-list` after any create, update, or delete operation.