@agent-native/core 0.164.3 → 0.164.4

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 (28) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +1 -1
  3. package/corpus/templates/clips/app/routes/share.$shareId.tsx +3 -0
  4. package/corpus/templates/clips/changelog/2026-08-19-social-preview-video-frames.md +5 -0
  5. package/corpus/templates/clips/server/lib/slack-unfurls.ts +7 -13
  6. package/corpus/templates/clips/shared/share-meta.ts +67 -3
  7. package/corpus/templates/content/actions/list-content-databases.ts +95 -1
  8. package/corpus/templates/content/changelog/2026-08-11-content-mutations-delegated-from-verified-slack-dms-now-use-.md +6 -0
  9. package/corpus/templates/content/shared/api.ts +11 -0
  10. package/dist/a2a/artifact-response.d.ts +50 -0
  11. package/dist/a2a/artifact-response.js +266 -39
  12. package/dist/a2a/index.d.ts +2 -0
  13. package/dist/a2a/index.js +1 -0
  14. package/dist/collab/awareness.d.ts +2 -2
  15. package/dist/integrations/adapters/slack.js +58 -7
  16. package/dist/notifications/routes.d.ts +3 -3
  17. package/dist/progress/routes.d.ts +1 -1
  18. package/dist/server/agent-chat/action-filters-a2a.d.ts +2 -1
  19. package/dist/server/agent-chat/action-filters-a2a.js +10 -2
  20. package/dist/server/agent-chat-plugin.js +33 -11
  21. package/dist/server/realtime-token.d.ts +1 -1
  22. package/docs/content/drop-in-agent.mdx +4 -4
  23. package/docs/content/faq.mdx +15 -15
  24. package/docs/content/getting-started.mdx +173 -41
  25. package/docs/content/key-concepts.mdx +10 -10
  26. package/docs/content/using-your-agent.mdx +2 -2
  27. package/docs/content/what-is-agent-native.mdx +79 -228
  28. package/package.json +1 -1
@@ -1,9 +1,19 @@
1
1
  import { createHmac, timingSafeEqual } from "node:crypto";
2
+ import { readDeployCredentialEnv } from "../server/credential-provider.js";
3
+ function a2aSecret() {
4
+ return readDeployCredentialEnv("A2A_SECRET");
5
+ }
6
+ function a2aSecrets() {
7
+ const secret = a2aSecret();
8
+ return secret ? [secret] : [];
9
+ }
2
10
  const ARTIFACT_IDENTITY_WRITE_TOOLS = new Set([
3
11
  "save-monitor",
4
12
  "create-form",
5
13
  "submit-content-database-form",
6
14
  "add-database-item",
15
+ "upsert-database-item-by-key",
16
+ "mutate-content-database-block",
7
17
  "create-document",
8
18
  "update-document",
9
19
  "set-document-property",
@@ -44,50 +54,79 @@ const ARTIFACT_RESOURCE_TYPES = new Set([
44
54
  "monitor",
45
55
  "form",
46
56
  ]);
47
- function persistedArtifactIdentitiesFromMarker(result, secrets = process.env.A2A_SECRET
48
- ? [process.env.A2A_SECRET]
49
- : []) {
57
+ function persistedArtifactLedgerFromMarker(result, secrets = a2aSecrets(), expectedDelegatedTaskId) {
50
58
  if (secrets.length === 0)
51
- return [];
52
- const match = result.match(/<!--\s*agent-native:persisted-artifacts=([A-Za-z0-9_-]+)\.([a-f0-9]{64})\s*-->/);
53
- if (!match)
54
- return [];
55
- try {
56
- const payload = match[1];
57
- const supplied = Buffer.from(match[2], "hex");
58
- const verified = secrets.some((secret) => {
59
- const expected = createHmac("sha256", secret).update(payload).digest();
60
- return (supplied.length === expected.length &&
61
- timingSafeEqual(supplied, expected));
62
- });
63
- if (!verified) {
64
- return [];
59
+ return null;
60
+ const matches = result.matchAll(/<!--\s*agent-native:persisted-artifacts=([A-Za-z0-9_-]+)\.([a-f0-9]{64})\s*-->/g);
61
+ for (const match of matches) {
62
+ try {
63
+ const payload = match[1];
64
+ const supplied = Buffer.from(match[2], "hex");
65
+ const verified = secrets.some((secret) => {
66
+ const expected = createHmac("sha256", secret).update(payload).digest();
67
+ return (supplied.length === expected.length &&
68
+ timingSafeEqual(supplied, expected));
69
+ });
70
+ if (!verified)
71
+ continue;
72
+ const parsed = JSON.parse(Buffer.from(payload, "base64url").toString());
73
+ if (Array.isArray(parsed)) {
74
+ return { version: 1, identities: parsed, mutationReceipts: [] };
75
+ }
76
+ const ledger = asRecord(parsed);
77
+ if (!ledger || ledger.version !== 1)
78
+ continue;
79
+ const parsedLedger = {
80
+ version: 1,
81
+ identities: Array.isArray(ledger.identities) ? ledger.identities : [],
82
+ mutationReceipts: Array.isArray(ledger.mutationReceipts)
83
+ ? ledger.mutationReceipts
84
+ : [],
85
+ ...(typeof ledger.delegatedTaskId === "string"
86
+ ? { delegatedTaskId: ledger.delegatedTaskId }
87
+ : {}),
88
+ };
89
+ if (expectedDelegatedTaskId &&
90
+ parsedLedger.delegatedTaskId !== expectedDelegatedTaskId) {
91
+ continue;
92
+ }
93
+ return parsedLedger;
94
+ }
95
+ catch {
96
+ // coercion-ok: malformed signed-marker payloads are untrusted absence, never a successful receipt ledger
97
+ continue;
65
98
  }
66
- const parsed = JSON.parse(Buffer.from(payload, "base64url").toString());
67
- if (!Array.isArray(parsed))
68
- return [];
69
- return parsed
70
- .slice(0, 12)
71
- .filter((identity) => {
72
- const item = asRecord(identity);
73
- return (!!item &&
74
- ARTIFACT_RESOURCE_TYPES.has(item.resourceType) &&
75
- typeof item.id === "string" &&
76
- typeof item.sourceAction === "string");
77
- });
78
- }
79
- catch {
80
- return [];
81
99
  }
100
+ return null;
82
101
  }
83
- function withPersistedArtifactMarker(text, toolResults, secret = process.env.A2A_SECRET) {
84
- const verificationSecrets = [secret, process.env.A2A_SECRET].filter((value, index, values) => !!value && values.indexOf(value) === index);
102
+ function persistedArtifactIdentitiesFromMarker(result, secrets = a2aSecrets()) {
103
+ return (persistedArtifactLedgerFromMarker(result, secrets)?.identities ?? [])
104
+ .slice(0, 12)
105
+ .filter((identity) => {
106
+ const item = asRecord(identity);
107
+ return (!!item &&
108
+ ARTIFACT_RESOURCE_TYPES.has(item.resourceType) &&
109
+ typeof item.id === "string" &&
110
+ typeof item.sourceAction === "string");
111
+ });
112
+ }
113
+ function withPersistedArtifactMarker(text, toolResults, secret = a2aSecret(), delegatedTaskId) {
114
+ const verificationSecrets = [secret, a2aSecret()].filter((value, index, values) => !!value && values.indexOf(value) === index);
85
115
  const identities = extractA2AArtifactIdentities(toolResults, {
86
116
  persistedArtifactSecrets: verificationSecrets,
87
117
  }).slice(0, 12);
88
- if (identities.length === 0 || !secret)
118
+ const mutationReceipts = extractA2APersistedMutationReceipts(toolResults, {
119
+ persistedArtifactSecrets: secret ? [secret] : [],
120
+ expectedDelegatedTaskId: delegatedTaskId,
121
+ }).slice(0, 12);
122
+ if ((identities.length === 0 && mutationReceipts.length === 0) || !secret)
89
123
  return text;
90
- const payload = Buffer.from(JSON.stringify(identities)).toString("base64url");
124
+ const payload = Buffer.from(JSON.stringify({
125
+ version: 1,
126
+ identities,
127
+ mutationReceipts,
128
+ ...(delegatedTaskId ? { delegatedTaskId } : {}),
129
+ })).toString("base64url");
91
130
  const signature = createHmac("sha256", secret).update(payload).digest("hex");
92
131
  const marker = `<!-- ${PERSISTED_ARTIFACT_MARKER}${payload}.${signature} -->`;
93
132
  return text ? `${text}\n\n${marker}` : marker;
@@ -474,6 +513,22 @@ function collectArtifacts(results) {
474
513
  documents.set(artifact.id, artifact);
475
514
  continue;
476
515
  }
516
+ if (toolResult.tool === "upsert-database-item-by-key" ||
517
+ toolResult.tool === "mutate-content-database-block") {
518
+ const receipt = asRecord(parsed.receipt);
519
+ const receiptRow = asRecord(receipt?.row);
520
+ const receiptTarget = asRecord(receipt?.target);
521
+ const rowLink = asRecord(receipt?.rowLink);
522
+ const rowDocumentId = stringValue(receiptRow?.documentId) ??
523
+ stringValue(receiptTarget?.rowDocumentId);
524
+ if (rowDocumentId) {
525
+ documents.set(rowDocumentId, {
526
+ id: rowDocumentId,
527
+ url: stringValue(receiptRow?.urlPath) ?? stringValue(rowLink?.urlPath),
528
+ });
529
+ }
530
+ continue;
531
+ }
477
532
  if (toolResult.tool === "create-document" ||
478
533
  toolResult.tool === "update-document") {
479
534
  if (parsed.conflict === true)
@@ -772,6 +827,175 @@ export function extractA2AArtifactIdentities(results, options = {}) {
772
827
  }
773
828
  return [...identities.values()];
774
829
  }
830
+ function boundedStrings(value) {
831
+ if (!Array.isArray(value))
832
+ return undefined;
833
+ return value
834
+ .map(stringValue)
835
+ .filter((item) => !!item)
836
+ .slice(0, 50);
837
+ }
838
+ function parsePersistedMutationReceipt(value, sourceAction) {
839
+ const receipt = asRecord(value);
840
+ const target = asRecord(receipt?.target);
841
+ const authorityScope = asRecord(target?.authorityScope);
842
+ const row = asRecord(receipt?.row);
843
+ const rowLink = asRecord(receipt?.rowLink);
844
+ const idempotency = asRecord(receipt?.idempotency);
845
+ const revisions = asRecord(receipt?.revisions);
846
+ const rowRevisions = asRecord(revisions?.row);
847
+ const fieldRevisions = asRecord(revisions?.field);
848
+ const affected = asRecord(receipt?.affected);
849
+ const readback = asRecord(receipt?.readback);
850
+ const authorityScopeKind = stringValue(authorityScope?.kind) ??
851
+ stringValue(target?.authorityScopeKind);
852
+ const idempotencyResult = stringValue(idempotency?.result);
853
+ const rowDocumentId = stringValue(row?.documentId) ?? stringValue(target?.rowDocumentId);
854
+ const urlPath = stringValue(row?.urlPath) ?? stringValue(rowLink?.urlPath);
855
+ const receiptId = stringValue(receipt?.receiptId);
856
+ const operation = stringValue(receipt?.operation);
857
+ const outcome = stringValue(receipt?.outcome);
858
+ const authorityScopeId = stringValue(authorityScope?.id) ?? stringValue(target?.authorityScopeId);
859
+ const spaceId = stringValue(target?.spaceId);
860
+ const databaseId = stringValue(target?.databaseId);
861
+ const databaseDocumentId = stringValue(target?.databaseDocumentId);
862
+ const idempotencyKey = stringValue(idempotency?.key);
863
+ const payloadDigest = stringValue(idempotency?.payloadDigest);
864
+ if (!receipt ||
865
+ !target ||
866
+ (authorityScopeKind !== "personal" &&
867
+ authorityScopeKind !== "organization") ||
868
+ (idempotencyResult !== "applied" && idempotencyResult !== "replayed") ||
869
+ (readback?.verified !== true && receipt.readbackVerified !== true) ||
870
+ !receiptId ||
871
+ !operation ||
872
+ !outcome ||
873
+ !authorityScopeId ||
874
+ !spaceId ||
875
+ !databaseId ||
876
+ !databaseDocumentId ||
877
+ !idempotencyKey ||
878
+ !payloadDigest ||
879
+ !rowDocumentId ||
880
+ !urlPath) {
881
+ return null;
882
+ }
883
+ const propertyIds = boundedStrings(affected?.propertyIds);
884
+ const blockIds = boundedStrings(affected?.blockIds);
885
+ const deletedBlockIds = boundedStrings(affected?.deletedBlockIds);
886
+ const order = boundedStrings(affected?.order);
887
+ const compactAffected = {
888
+ ...(typeof affected?.title === "boolean" ? { title: affected.title } : {}),
889
+ ...(propertyIds ? { propertyIds } : {}),
890
+ ...(blockIds ? { blockIds } : {}),
891
+ ...(deletedBlockIds ? { deletedBlockIds } : {}),
892
+ ...(order ? { order } : {}),
893
+ };
894
+ const itemId = stringValue(row?.itemId) ?? stringValue(target.itemId);
895
+ const targetItemId = stringValue(target.itemId);
896
+ const targetRowDocumentId = stringValue(target.rowDocumentId);
897
+ const targetPropertyId = stringValue(target.propertyId);
898
+ const after = stringValue(revisions?.after);
899
+ const rowBefore = stringValue(rowRevisions?.before) ?? stringValue(revisions?.rowBefore);
900
+ const rowAfter = stringValue(rowRevisions?.after) ?? stringValue(revisions?.rowAfter);
901
+ return {
902
+ receiptId,
903
+ sourceAction,
904
+ operation,
905
+ outcome,
906
+ target: {
907
+ authorityScopeKind,
908
+ authorityScopeId,
909
+ spaceId,
910
+ databaseId,
911
+ databaseDocumentId,
912
+ ...(targetItemId ? { itemId: targetItemId } : {}),
913
+ ...(targetRowDocumentId ? { rowDocumentId: targetRowDocumentId } : {}),
914
+ ...(targetPropertyId ? { propertyId: targetPropertyId } : {}),
915
+ },
916
+ row: {
917
+ ...(itemId ? { itemId } : {}),
918
+ documentId: rowDocumentId,
919
+ urlPath,
920
+ },
921
+ idempotency: {
922
+ key: idempotencyKey,
923
+ result: idempotencyResult,
924
+ payloadDigest,
925
+ },
926
+ revisions: {
927
+ ...(revisions?.before === null || typeof revisions?.before === "string"
928
+ ? { before: revisions.before }
929
+ : {}),
930
+ ...(after ? { after } : {}),
931
+ ...(rowBefore ? { rowBefore } : {}),
932
+ ...(rowAfter ? { rowAfter } : {}),
933
+ ...(typeof (fieldRevisions?.before ?? revisions?.fieldBefore) === "number"
934
+ ? {
935
+ fieldBefore: (fieldRevisions?.before ??
936
+ revisions?.fieldBefore),
937
+ }
938
+ : {}),
939
+ ...(typeof (fieldRevisions?.after ?? revisions?.fieldAfter) === "number"
940
+ ? {
941
+ fieldAfter: (fieldRevisions?.after ??
942
+ revisions?.fieldAfter),
943
+ }
944
+ : {}),
945
+ },
946
+ ...(Object.keys(compactAffected).length > 0
947
+ ? { affected: compactAffected }
948
+ : {}),
949
+ readbackVerified: true,
950
+ };
951
+ }
952
+ /**
953
+ * Extract bounded, read-back-verified mutation receipts. Nested agent results
954
+ * are accepted only through the signed persisted ledger.
955
+ */
956
+ export function extractA2APersistedMutationReceipts(results, options = {}) {
957
+ const receipts = new Map();
958
+ for (const result of results) {
959
+ if (result.isError === true || result.completedSideEffect === false)
960
+ continue;
961
+ if (result.tool === "call-agent") {
962
+ const nested = persistedArtifactLedgerFromMarker(result.result, options.persistedArtifactSecrets, options.expectedDelegatedTaskId);
963
+ for (const receipt of nested?.mutationReceipts ?? []) {
964
+ const parsed = parsePersistedMutationReceipt(receipt, "call-agent");
965
+ if (parsed)
966
+ receipts.set(parsed.receiptId, parsed);
967
+ }
968
+ continue;
969
+ }
970
+ if (result.tool !== "upsert-database-item-by-key" &&
971
+ result.tool !== "mutate-content-database-block") {
972
+ continue;
973
+ }
974
+ const parsedResult = parseToolResultJson(result.result);
975
+ const receipt = parsePersistedMutationReceipt(parsedResult?.receipt, result.tool);
976
+ if (receipt)
977
+ receipts.set(receipt.receiptId, receipt);
978
+ }
979
+ return [...receipts.values()].slice(0, 12);
980
+ }
981
+ export function appendA2APersistedMutationReceipts(text, toolResults, options = {}) {
982
+ const receiptSecret = options.persistedArtifactSecret ?? a2aSecret();
983
+ const receipts = extractA2APersistedMutationReceipts(toolResults, {
984
+ persistedArtifactSecrets: receiptSecret ? [receiptSecret] : [],
985
+ expectedDelegatedTaskId: options.delegatedTaskId,
986
+ }).filter((receipt) => !text.includes(receipt.receiptId));
987
+ if (receipts.length === 0)
988
+ return text;
989
+ const lines = receipts.map((receipt) => {
990
+ const url = artifactUrl(options.baseUrl, receipt.row.urlPath);
991
+ const item = receipt.row.itemId ? `; item ID: ${receipt.row.itemId}` : "";
992
+ return (`- ${receipt.receiptId}: ${receipt.outcome} via ${receipt.sourceAction}; ` +
993
+ `row ${url} (document ID: ${receipt.row.documentId}${item}); ` +
994
+ `idempotency: ${receipt.idempotency.result}`);
995
+ });
996
+ const section = ["Mutation receipts:", ...lines].join("\n");
997
+ return text.trim() ? `${text.trim()}\n\n${section}` : section;
998
+ }
775
999
  function parseDownstreamArtifactBlock(result) {
776
1000
  const artifacts = [];
777
1001
  for (const line of downstreamArtifactLines(result)) {
@@ -1076,9 +1300,12 @@ function formatUnverifiedArtifactMessage(refs, documents, decks, dashboards, ana
1076
1300
  export function guardA2AArtifactResponse(responseText, toolResults, options = {}) {
1077
1301
  const baseUrl = normalizeBaseUrl(options.baseUrl);
1078
1302
  const includeReferencedArtifacts = options.includeReferencedArtifacts ?? false;
1079
- const finalize = (value) => options.includePersistedArtifactMarker
1080
- ? withPersistedArtifactMarker(value, toolResults, options.persistedArtifactSecret ?? process.env.A2A_SECRET)
1081
- : value;
1303
+ const finalize = (value) => {
1304
+ const withReceipts = appendA2APersistedMutationReceipts(value, toolResults, options);
1305
+ return options.includePersistedArtifactMarker
1306
+ ? withPersistedArtifactMarker(withReceipts, toolResults, options.persistedArtifactSecret ?? a2aSecret(), options.delegatedTaskId)
1307
+ : withReceipts;
1308
+ };
1082
1309
  const { documents, decks, dashboards, analyses, images, designShells, generatedDesigns, monitors, forms, } = collectArtifacts(toolResults);
1083
1310
  const generatedDesignIds = new Set(generatedDesigns.map((design) => design.id));
1084
1311
  const incompleteShells = designShells.filter((shell) => !generatedDesignIds.has(shell.id));
@@ -9,3 +9,5 @@ export type { A2ACallerAuth } from "./caller-auth.js";
9
9
  export { AgentInvocationError, buildAgentInvocationPrompt, invokeAgent, invokeAgentAction, looksLikeAgentUrl, resolveAgentInvocationTarget, } from "./invoke.js";
10
10
  export type { A2AConfig, A2AHandler, A2AHandlerContext, A2AHandlerResult, A2ASourceContext, AgentCard, AgentSkill, AgentCapabilities, Task, TaskState, TaskStatus, Message, Part, TextPart, FilePart, DataPart, Artifact, JsonRpcRequest, JsonRpcResponse, A2ACorrelationMetadata, A2AReadOnlyActionInvocation, A2AReadOnlyActionResult, A2AAgentActivityPhase, A2AAgentActivitySnapshot, A2AAgentActivityState, A2AAgentActivityToolCall, A2AAgentActivityToolStatus, } from "./types.js";
11
11
  export type { AgentInvocationErrorCode, AgentActionInvocationResult, AgentInvocationResult, AgentInvocationRuntime, InvokeAgentActionOptions, InvokeAgentOptions, ResolveAgentInvocationTargetOptions, ResolvedAgentInvocationTarget, } from "./invoke.js";
12
+ export { extractA2APersistedMutationReceipts, stripA2APersistedArtifactMarkers, } from "./artifact-response.js";
13
+ export type { A2APersistedMutationReceipt } from "./artifact-response.js";
package/dist/a2a/index.js CHANGED
@@ -7,3 +7,4 @@ export { A2AClient, callAction, callAgent, signA2AToken } from "./client.js";
7
7
  export { canonicalA2AAudience } from "./audience.js";
8
8
  export { resolveA2ACallerAuth } from "./caller-auth.js";
9
9
  export { AgentInvocationError, buildAgentInvocationPrompt, invokeAgent, invokeAgentAction, looksLikeAgentUrl, resolveAgentInvocationTarget, } from "./invoke.js";
10
+ export { extractA2APersistedMutationReceipts, stripA2APersistedArtifactMarkers, } from "./artifact-response.js";
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
62
62
  error: string;
63
63
  states?: undefined;
64
64
  } | {
65
+ error?: undefined;
65
66
  states: {
66
67
  clientId: number;
67
68
  state: string;
68
69
  }[];
69
- error?: undefined;
70
70
  }>>;
71
71
  /**
72
72
  * GET /_agent-native/collab/:docId/users
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
77
77
  error: string;
78
78
  users?: undefined;
79
79
  } | {
80
+ error?: undefined;
80
81
  users: {
81
82
  clientId: number;
82
83
  lastSeen: number;
83
84
  }[];
84
- error?: undefined;
85
85
  }>>;
@@ -53,6 +53,25 @@ function claimSlackSystemNoticeSlot(key, ttlMs = SLACK_SYSTEM_NOTICE_DEDUPE_TTL_
53
53
  slackSystemNoticeCache.set(key, now + ttlMs);
54
54
  return true;
55
55
  }
56
+ function slackEventInstallationScope(payload) {
57
+ const teamId = typeof payload?.team_id === "string" ? payload.team_id : undefined;
58
+ const authorizations = Array.isArray(payload?.authorizations)
59
+ ? payload.authorizations.filter((value) => !!value && typeof value === "object" && !Array.isArray(value))
60
+ : [];
61
+ const enterpriseAuthorization = authorizations.find((authorization) => authorization.is_enterprise_install === true &&
62
+ typeof authorization.enterprise_id === "string");
63
+ const teamAuthorization = authorizations.find((authorization) => authorization.team_id === teamId);
64
+ const authorization = teamAuthorization ?? enterpriseAuthorization;
65
+ return {
66
+ enterpriseId: typeof authorization?.enterprise_id === "string"
67
+ ? authorization.enterprise_id
68
+ : typeof payload?.enterprise_id === "string"
69
+ ? payload.enterprise_id
70
+ : undefined,
71
+ isEnterpriseInstall: authorization?.is_enterprise_install === true ||
72
+ payload?.is_enterprise_install === true,
73
+ };
74
+ }
56
75
  /**
57
76
  * Create a Slack platform adapter.
58
77
  *
@@ -204,6 +223,7 @@ export function slackAdapter(options = {}) {
204
223
  const apiAppId = typeof payload.api_app_id === "string"
205
224
  ? payload.api_app_id
206
225
  : "unknown";
226
+ const installationScope = slackEventInstallationScope(payload);
207
227
  const agentContext = normalizeSlackAgentContext(e.app_context, teamId);
208
228
  const isDm = typeof e.channel_type === "string"
209
229
  ? e.channel_type === "im"
@@ -238,9 +258,8 @@ export function slackAdapter(options = {}) {
238
258
  messageTs: e.ts,
239
259
  teamId,
240
260
  apiAppId,
241
- enterpriseId: typeof payload.enterprise_id === "string"
242
- ? payload.enterprise_id
243
- : undefined,
261
+ enterpriseId: installationScope.enterpriseId,
262
+ isEnterpriseInstall: installationScope.isEnterpriseInstall,
244
263
  eventId: payload.event_id,
245
264
  ...(agentContext
246
265
  ? {
@@ -614,6 +633,7 @@ async function resolveManagedSlackBotToken(incoming) {
614
633
  const enterpriseId = typeof incoming.platformContext.enterpriseId === "string"
615
634
  ? incoming.platformContext.enterpriseId
616
635
  : undefined;
636
+ const isEnterpriseInstall = incoming.platformContext.isEnterpriseInstall === true;
617
637
  const installationKeyHint = typeof incoming.platformContext.installationKey === "string"
618
638
  ? incoming.platformContext.installationKey
619
639
  : undefined;
@@ -624,7 +644,12 @@ async function resolveManagedSlackBotToken(incoming) {
624
644
  ? await getActiveIntegrationInstallationByKey("slack", installationKeyHint)
625
645
  : null;
626
646
  if (!installation && apiAppId) {
627
- installation = await getActiveIntegrationInstallationByKey("slack", slackInstallationKey({ teamId, enterpriseId, apiAppId }));
647
+ installation = await getActiveIntegrationInstallationByKey("slack", slackInstallationKey({
648
+ teamId,
649
+ enterpriseId,
650
+ apiAppId,
651
+ isEnterpriseInstall,
652
+ }));
628
653
  }
629
654
  if (!installation && !apiAppId) {
630
655
  // Without an app id the tenant can match several connected Slack apps.
@@ -641,7 +666,12 @@ async function resolveManagedSlackBotToken(incoming) {
641
666
  installation = candidates[0] ?? null;
642
667
  }
643
668
  const key = installation?.installationKey ??
644
- slackInstallationKey({ teamId, enterpriseId, apiAppId });
669
+ slackInstallationKey({
670
+ teamId,
671
+ enterpriseId,
672
+ apiAppId,
673
+ isEnterpriseInstall,
674
+ });
645
675
  return (await resolveIntegrationTokenBundle("slack", key))?.accessToken;
646
676
  }
647
677
  catch {
@@ -721,13 +751,19 @@ let _missingAllowlistWarned = false;
721
751
  async function enforceWorkspaceAllowlist(payload) {
722
752
  const teamId = typeof payload?.team_id === "string" ? payload.team_id : undefined;
723
753
  const apiAppId = typeof payload?.api_app_id === "string" ? payload.api_app_id : undefined;
754
+ const installationScope = slackEventInstallationScope(payload);
724
755
  const allowedTeamIds = parseAllowlistEnv("SLACK_ALLOWED_TEAM_IDS");
725
756
  const allowedAppIds = parseAllowlistEnv("SLACK_ALLOWED_API_APP_IDS");
726
757
  if (!allowedTeamIds) {
727
758
  if (process.env.NODE_ENV === "production") {
728
759
  let managed = false;
729
760
  try {
730
- const key = slackInstallationKey({ teamId, apiAppId });
761
+ const key = slackInstallationKey({
762
+ teamId,
763
+ apiAppId,
764
+ enterpriseId: installationScope.enterpriseId,
765
+ isEnterpriseInstall: installationScope.isEnterpriseInstall,
766
+ });
731
767
  managed = !!(await getActiveIntegrationInstallationByKey("slack", key));
732
768
  }
733
769
  catch { }
@@ -745,7 +781,22 @@ async function enforceWorkspaceAllowlist(payload) {
745
781
  }
746
782
  }
747
783
  if (allowedTeamIds) {
748
- if (!teamId || !allowedTeamIds.has(teamId)) {
784
+ let allowedEnterpriseInstall = false;
785
+ if (installationScope.isEnterpriseInstall) {
786
+ try {
787
+ const key = slackInstallationKey({
788
+ teamId,
789
+ apiAppId,
790
+ enterpriseId: installationScope.enterpriseId,
791
+ isEnterpriseInstall: true,
792
+ });
793
+ allowedEnterpriseInstall =
794
+ !!(await getActiveIntegrationInstallationByKey("slack", key));
795
+ // coercion-ok: lookup failure leaves the enterprise event denied below
796
+ }
797
+ catch { }
798
+ }
799
+ if ((!teamId || !allowedTeamIds.has(teamId)) && !allowedEnterpriseInstall) {
749
800
  throw createError({
750
801
  statusCode: 401,
751
802
  statusMessage: "Unrecognized Slack workspace",
@@ -11,14 +11,14 @@
11
11
  * DELETE /_agent-native/notifications/:id — delete
12
12
  */
13
13
  export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
14
+ error?: undefined;
14
15
  count: number;
15
16
  updated?: undefined;
16
- error?: undefined;
17
17
  ok?: undefined;
18
18
  } | {
19
+ error?: undefined;
19
20
  count?: undefined;
20
21
  updated: number;
21
- error?: undefined;
22
22
  ok?: undefined;
23
23
  } | {
24
24
  count?: undefined;
@@ -26,8 +26,8 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
26
26
  error: string;
27
27
  ok?: undefined;
28
28
  } | {
29
+ error?: undefined;
29
30
  count?: undefined;
30
31
  updated?: undefined;
31
- error?: undefined;
32
32
  ok: boolean;
33
33
  }>>;
@@ -15,6 +15,6 @@ export declare function createProgressHandler(): import("h3").EventHandlerWithFe
15
15
  error: string;
16
16
  ok?: undefined;
17
17
  } | {
18
- error?: undefined;
19
18
  ok: boolean;
19
+ error?: undefined;
20
20
  }>>;
@@ -1,4 +1,4 @@
1
- import { type A2AArtifactResponseOptions, type A2AToolResultSummary } from "../../a2a/artifact-response.js";
1
+ import { type A2AArtifactResponseOptions, type A2APersistedMutationReceipt, type A2AToolResultSummary } from "../../a2a/artifact-response.js";
2
2
  import type { AgentSkill } from "../../a2a/types.js";
3
3
  import type { EngineTool } from "../../agent/engine/types.js";
4
4
  import { type ActionEntry, type AgentLoopOutcome } from "../../agent/production-agent.js";
@@ -74,6 +74,7 @@ export declare function assembleA2AFinalResponse(events: readonly AgentChatEvent
74
74
  }): {
75
75
  responseText: string;
76
76
  finalText: string;
77
+ mutationReceipts: A2APersistedMutationReceipt[];
77
78
  };
78
79
  type A2AAgentLoopRunner = typeof runAgentLoopDirectWithSoftTimeout;
79
80
  /**
@@ -1,5 +1,5 @@
1
1
  import { getHeader } from "h3";
2
- import { appendA2AArtifactLinks, } from "../../a2a/artifact-response.js";
2
+ import { appendA2AArtifactLinks, extractA2APersistedMutationReceipts, } from "../../a2a/artifact-response.js";
3
3
  import { collectFinalResponseTextFromAgentEvents } from "../../a2a/response-text.js";
4
4
  import { resolveMainChatMaxOutputTokens } from "../../agent/engine/output-tokens.js";
5
5
  import { filterInitialEngineTools, resolveAgentRequestReasoningEffort, } from "../../agent/production-agent.js";
@@ -200,7 +200,15 @@ export function assembleA2AFinalResponse(events, toolResults, options = {}) {
200
200
  baseUrl: options.baseUrl ?? resolveArtifactBaseUrl(options.event),
201
201
  includeReferencedArtifacts: true,
202
202
  includePersistedArtifactMarker: true,
203
+ persistedArtifactSecret: options.persistedArtifactSecret,
204
+ delegatedTaskId: options.delegatedTaskId,
203
205
  });
206
+ const mutationReceipts = extractA2APersistedMutationReceipts([...toolResults], options.persistedArtifactSecret
207
+ ? {
208
+ persistedArtifactSecrets: [options.persistedArtifactSecret],
209
+ expectedDelegatedTaskId: options.delegatedTaskId,
210
+ }
211
+ : {});
204
212
  if (terminalError) {
205
213
  const partialResult = finalText.trim()
206
214
  ? `\n\nPartial verified results before the failure:\n${finalText.trim()}`
@@ -210,7 +218,7 @@ export function assembleA2AFinalResponse(events, toolResults, options = {}) {
210
218
  if (!finalText.trim()) {
211
219
  throw new Error("Agent completed without a response or verified artifact.\ncode: empty_agent_response");
212
220
  }
213
- return { responseText, finalText };
221
+ return { responseText, finalText, mutationReceipts };
214
222
  }
215
223
  function terminalErrorFromOutcome(outcome) {
216
224
  if (outcome.state === "completed")