@agent-native/core 0.98.6 → 0.98.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +6 -0
- package/corpus/core/docs/content/external-agents.mdx +15 -11
- package/corpus/core/docs/content/locales/ar-SA/external-agents.mdx +3 -1
- package/corpus/core/docs/content/locales/de-DE/external-agents.mdx +3 -1
- package/corpus/core/docs/content/locales/es-ES/external-agents.mdx +3 -1
- package/corpus/core/docs/content/locales/fr-FR/external-agents.mdx +3 -1
- package/corpus/core/docs/content/locales/hi-IN/external-agents.mdx +3 -1
- package/corpus/core/docs/content/locales/ja-JP/external-agents.mdx +3 -1
- package/corpus/core/docs/content/locales/ko-KR/external-agents.mdx +3 -1
- package/corpus/core/docs/content/locales/pt-BR/external-agents.mdx +3 -1
- package/corpus/core/docs/content/locales/zh-CN/external-agents.mdx +3 -1
- package/corpus/core/docs/content/locales/zh-TW/external-agents.mdx +3 -1
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/handlers.ts +6 -5
- package/corpus/core/src/a2a/task-store.ts +63 -0
- package/corpus/core/src/client/session-replay.ts +67 -5
- package/corpus/core/src/integrations/adapters/slack.ts +99 -19
- package/corpus/core/src/integrations/identity.ts +103 -39
- package/corpus/core/src/integrations/plugin.ts +81 -9
- package/corpus/core/src/integrations/types.ts +30 -0
- package/corpus/core/src/integrations/webhook-handler.ts +1 -0
- package/corpus/core/src/mcp/builtin-tools.ts +5 -2
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +4 -1
- package/corpus/templates/analytics/AGENTS.md +9 -9
- package/corpus/templates/analytics/changelog/2026-07-12-analytics-uses-the-full-in-app-agent-for-multi-step-incident.md +6 -0
- package/corpus/templates/analytics/server/handlers/session-replay.ts +15 -9
- package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +23 -5
- package/corpus/templates/analytics/server/lib/session-replay.ts +26 -5
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +4 -1
- package/corpus/templates/analytics/server/routes/api/session-replay/agent-diagnostics.json.get.ts +8 -5
- package/corpus/templates/analytics/server/routes/api/session-replay/agent-events.json.get.ts +8 -3
- package/corpus/templates/calendar/app/components/calendar/DeleteEventDialog.tsx +12 -2
- package/dist/a2a/handlers.d.ts.map +1 -1
- package/dist/a2a/handlers.js +6 -6
- package/dist/a2a/handlers.js.map +1 -1
- package/dist/a2a/task-store.d.ts +12 -0
- package/dist/a2a/task-store.d.ts.map +1 -1
- package/dist/a2a/task-store.js +54 -0
- package/dist/a2a/task-store.js.map +1 -1
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +53 -8
- package/dist/client/session-replay.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/integrations/adapters/slack.d.ts.map +1 -1
- package/dist/integrations/adapters/slack.js +85 -11
- package/dist/integrations/adapters/slack.js.map +1 -1
- package/dist/integrations/identity.d.ts +19 -2
- package/dist/integrations/identity.d.ts.map +1 -1
- package/dist/integrations/identity.js +76 -34
- package/dist/integrations/identity.js.map +1 -1
- package/dist/integrations/plugin.d.ts.map +1 -1
- package/dist/integrations/plugin.js +56 -6
- package/dist/integrations/plugin.js.map +1 -1
- package/dist/integrations/types.d.ts +25 -0
- package/dist/integrations/types.d.ts.map +1 -1
- package/dist/integrations/types.js.map +1 -1
- package/dist/integrations/webhook-handler.js +1 -0
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/mcp/builtin-tools.d.ts.map +1 -1
- package/dist/mcp/builtin-tools.js +5 -2
- package/dist/mcp/builtin-tools.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/docs/content/external-agents.mdx +15 -11
- package/docs/content/locales/ar-SA/external-agents.mdx +3 -1
- package/docs/content/locales/de-DE/external-agents.mdx +3 -1
- package/docs/content/locales/es-ES/external-agents.mdx +3 -1
- package/docs/content/locales/fr-FR/external-agents.mdx +3 -1
- package/docs/content/locales/hi-IN/external-agents.mdx +3 -1
- package/docs/content/locales/ja-JP/external-agents.mdx +3 -1
- package/docs/content/locales/ko-KR/external-agents.mdx +3 -1
- package/docs/content/locales/pt-BR/external-agents.mdx +3 -1
- package/docs/content/locales/zh-CN/external-agents.mdx +3 -1
- package/docs/content/locales/zh-TW/external-agents.mdx +3 -1
- package/package.json +1 -1
|
@@ -2,6 +2,22 @@ import { getDbExec } from "../db/client.js";
|
|
|
2
2
|
import { upsertVerifiedIntegrationIdentity } from "./identity-links-store.js";
|
|
3
3
|
import { getActiveIntegrationInstallationByKey, getActiveIntegrationInstallationForTenant, } from "./installations-store.js";
|
|
4
4
|
import { slackInstallationKey } from "./slack-oauth.js";
|
|
5
|
+
/**
|
|
6
|
+
* Thrown when the default Slack DM identity ladder declines to run a message.
|
|
7
|
+
* `reason` is a stable machine-readable discriminator (used e.g. to dedupe
|
|
8
|
+
* decline replies); `userFacingMessage` is safe to send back to the sender as
|
|
9
|
+
* a polite reply; `message` stays log-only.
|
|
10
|
+
*/
|
|
11
|
+
export class IntegrationIdentityDeclinedError extends Error {
|
|
12
|
+
reason;
|
|
13
|
+
userFacingMessage;
|
|
14
|
+
constructor(reason, message, userFacingMessage) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "IntegrationIdentityDeclinedError";
|
|
17
|
+
this.reason = reason;
|
|
18
|
+
this.userFacingMessage = userFacingMessage;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
5
21
|
function serviceOwner(platform) {
|
|
6
22
|
return `integration@${platform}`;
|
|
7
23
|
}
|
|
@@ -33,26 +49,26 @@ async function resolveSlackInstallation(incoming) {
|
|
|
33
49
|
: null;
|
|
34
50
|
}
|
|
35
51
|
async function isMemberOfOrg(email, orgId) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
sql: `SELECT 1 FROM org_members
|
|
52
|
+
const { rows } = await getDbExec().execute({
|
|
53
|
+
sql: `SELECT 1 FROM org_members
|
|
39
54
|
WHERE org_id = ? AND LOWER(email) = ?
|
|
40
55
|
LIMIT 1`,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
56
|
+
args: [orgId, email],
|
|
57
|
+
});
|
|
58
|
+
return rows.length > 0;
|
|
48
59
|
}
|
|
49
60
|
/**
|
|
50
61
|
* Resolve the default integration principal.
|
|
51
62
|
*
|
|
52
63
|
* Slack DMs become a user principal only after the adapter has verified the
|
|
53
64
|
* sender email and the email is already a member of the managed installation's
|
|
54
|
-
* Agent Native organization.
|
|
55
|
-
*
|
|
65
|
+
* Agent Native organization. Hydrated full workspace members whose email is
|
|
66
|
+
* missing, unverified, or not an organization member run as an anonymous
|
|
67
|
+
* org-scoped service principal instead — the same visibility shared channels
|
|
68
|
+
* get. Hydration failures, guests/external members, and workspaces without a
|
|
69
|
+
* connected organization are declined with a user-facing message. Shared
|
|
70
|
+
* channels deliberately stay service-scoped so a channel message cannot borrow
|
|
71
|
+
* one participant's private permissions.
|
|
56
72
|
*/
|
|
57
73
|
export async function resolveDefaultIntegrationExecutionContext(incoming) {
|
|
58
74
|
const installation = incoming.platform === "slack"
|
|
@@ -66,35 +82,61 @@ export async function resolveDefaultIntegrationExecutionContext(incoming) {
|
|
|
66
82
|
...(installation?.id ? { installationId: installation.id } : {}),
|
|
67
83
|
};
|
|
68
84
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
!incoming.tenantId) {
|
|
74
|
-
throw new Error("Slack DM sender identity could not be verified; refusing to run with a service principal.");
|
|
85
|
+
// Hydration check first: a transient users.info failure must land on the
|
|
86
|
+
// retry decline here, never on the anonymous org-scoped tier below.
|
|
87
|
+
if (incoming.actorTrust?.verified !== true) {
|
|
88
|
+
throw new IntegrationIdentityDeclinedError("unverified", "Slack DM sender identity could not be hydrated; declining instead of guessing a principal.", "I couldn't verify your Slack identity just now, so I can't run this request. Please try again in a moment.");
|
|
75
89
|
}
|
|
76
|
-
if (incoming.actorTrust
|
|
77
|
-
incoming.actorTrust
|
|
78
|
-
throw new
|
|
90
|
+
if (incoming.actorTrust.memberType === "guest" ||
|
|
91
|
+
incoming.actorTrust.memberType === "external") {
|
|
92
|
+
throw new IntegrationIdentityDeclinedError("guest", "External or guest Slack members cannot use this integration.", "This assistant is only available to members of this workspace's organization.");
|
|
79
93
|
}
|
|
80
94
|
if (!installation?.orgId) {
|
|
81
|
-
throw new
|
|
95
|
+
throw new IntegrationIdentityDeclinedError("unlinked-workspace", "Slack workspace is not connected to an Agent Native organization.", "This Slack workspace isn't connected to an organization yet.");
|
|
82
96
|
}
|
|
83
|
-
|
|
84
|
-
|
|
97
|
+
const email = normalizedEmail(incoming.senderEmail);
|
|
98
|
+
let isOrgMember = false;
|
|
99
|
+
if (email &&
|
|
100
|
+
incoming.senderVerified === true &&
|
|
101
|
+
incoming.senderId &&
|
|
102
|
+
incoming.tenantId) {
|
|
103
|
+
try {
|
|
104
|
+
isOrgMember = await isMemberOfOrg(email, installation.orgId);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// A membership-store outage is not evidence that the sender is merely
|
|
108
|
+
// unlinked. Fail closed instead of widening them to org-wide access.
|
|
109
|
+
throw new IntegrationIdentityDeclinedError("membership-check-failed", "Slack DM organization membership could not be verified.", "I couldn't verify your organization membership just now, so I can't run this request. Please try again in a moment.");
|
|
110
|
+
}
|
|
85
111
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
112
|
+
if (isOrgMember && email && incoming.senderId && incoming.tenantId) {
|
|
113
|
+
const link = await upsertVerifiedIntegrationIdentity({
|
|
114
|
+
platform: incoming.platform,
|
|
115
|
+
tenantId: incoming.tenantId,
|
|
116
|
+
externalUserId: incoming.senderId,
|
|
117
|
+
userEmail: email,
|
|
118
|
+
orgId: installation.orgId,
|
|
119
|
+
});
|
|
120
|
+
return {
|
|
121
|
+
ownerEmail: link.userEmail,
|
|
122
|
+
orgId: link.orgId,
|
|
123
|
+
principalType: "user",
|
|
124
|
+
installationId: installation.id,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// Hydrated full workspace member (member/admin/owner) whose email is
|
|
128
|
+
// unverified, missing (legacy install without users:read.email), or not an
|
|
129
|
+
// organization member: run with the anonymous org-scoped service principal —
|
|
130
|
+
// the same visibility shared channels get. Nothing user-private is
|
|
131
|
+
// accessible. One structured line keeps 100%-anonymous workspaces (legacy
|
|
132
|
+
// scope missing) visible in logs.
|
|
133
|
+
console.warn(`[integrations] anonymous org-scoped principal used: platform=${incoming.platform} teamId=${incoming.tenantId ?? "unknown"} emailPresent=${Boolean(email)} memberType=${incoming.actorTrust.memberType}`);
|
|
93
134
|
return {
|
|
94
|
-
ownerEmail:
|
|
95
|
-
orgId:
|
|
96
|
-
principalType: "
|
|
135
|
+
ownerEmail: serviceOwner(incoming.platform),
|
|
136
|
+
orgId: installation.orgId,
|
|
137
|
+
principalType: "service",
|
|
97
138
|
installationId: installation.id,
|
|
139
|
+
anonymousMember: true,
|
|
98
140
|
};
|
|
99
141
|
}
|
|
100
142
|
//# sourceMappingURL=identity.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/integrations/identity.ts"],"names":[],"mappings":"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","sourcesContent":["import { getDbExec } from \"../db/client.js\";\nimport { upsertVerifiedIntegrationIdentity } from \"./identity-links-store.js\";\nimport {\n getActiveIntegrationInstallationByKey,\n getActiveIntegrationInstallationForTenant,\n} from \"./installations-store.js\";\nimport { slackInstallationKey } from \"./slack-oauth.js\";\nimport type { IncomingMessage, IntegrationExecutionContext } from \"./types.js\";\n\nfunction serviceOwner(platform: string): string {\n return `integration@${platform}`;\n}\n\nfunction normalizedEmail(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n const email = value.trim().toLowerCase();\n if (!email || email.length > 320 || !email.includes(\"@\")) return null;\n return email;\n}\n\nasync function resolveSlackInstallation(incoming: IncomingMessage) {\n const teamId =\n typeof incoming.platformContext.teamId === \"string\"\n ? incoming.platformContext.teamId\n : incoming.tenantId;\n const enterpriseId =\n typeof incoming.platformContext.enterpriseId === \"string\"\n ? incoming.platformContext.enterpriseId\n : undefined;\n const apiAppId =\n typeof incoming.platformContext.apiAppId === \"string\"\n ? incoming.platformContext.apiAppId\n : undefined;\n if (apiAppId && (teamId || enterpriseId)) {\n const byKey = await getActiveIntegrationInstallationByKey(\n \"slack\",\n slackInstallationKey({ teamId, enterpriseId, apiAppId }),\n );\n if (byKey) return byKey;\n }\n return teamId || enterpriseId\n ? getActiveIntegrationInstallationForTenant(\n \"slack\",\n teamId ?? enterpriseId!,\n )\n : null;\n}\n\nasync function isMemberOfOrg(email: string, orgId: string): Promise<boolean> {\n try {\n const { rows } = await getDbExec().execute({\n sql: `SELECT 1 FROM org_members\n WHERE org_id = ? AND LOWER(email) = ?\n LIMIT 1`,\n args: [orgId, email],\n });\n return rows.length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Resolve the default integration principal.\n *\n * Slack DMs become a user principal only after the adapter has verified the\n * sender email and the email is already a member of the managed installation's\n * Agent Native organization. Shared channels deliberately stay service-scoped\n * so a channel message cannot borrow one participant's private permissions.\n */\nexport async function resolveDefaultIntegrationExecutionContext(\n incoming: IncomingMessage,\n): Promise<IntegrationExecutionContext> {\n const installation =\n incoming.platform === \"slack\"\n ? await resolveSlackInstallation(incoming)\n : null;\n\n if (incoming.platform !== \"slack\" || incoming.conversationType !== \"dm\") {\n return {\n ownerEmail: serviceOwner(incoming.platform),\n orgId: installation?.orgId ?? null,\n principalType: \"service\",\n ...(installation?.id ? { installationId: installation.id } : {}),\n };\n }\n\n const email = normalizedEmail(incoming.senderEmail);\n if (\n !email ||\n incoming.senderVerified !== true ||\n !incoming.senderId ||\n !incoming.tenantId\n ) {\n throw new Error(\n \"Slack DM sender identity could not be verified; refusing to run with a service principal.\",\n );\n }\n if (\n incoming.actorTrust?.memberType === \"guest\" ||\n incoming.actorTrust?.memberType === \"external\"\n ) {\n throw new Error(\n \"External or guest Slack members cannot use personal Agent Native permissions.\",\n );\n }\n if (!installation?.orgId) {\n throw new Error(\n \"Slack workspace is not connected to an Agent Native organization.\",\n );\n }\n\n if (!(await isMemberOfOrg(email, installation.orgId))) {\n throw new Error(\n \"Slack DM sender is not a member of the connected Agent Native organization.\",\n );\n }\n\n const link = await upsertVerifiedIntegrationIdentity({\n platform: incoming.platform,\n tenantId: incoming.tenantId,\n externalUserId: incoming.senderId,\n userEmail: email,\n orgId: installation.orgId,\n });\n return {\n ownerEmail: link.userEmail,\n orgId: link.orgId,\n principalType: \"user\",\n installationId: installation.id,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/integrations/identity.ts"],"names":[],"mappings":"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","sourcesContent":["import { getDbExec } from \"../db/client.js\";\nimport { upsertVerifiedIntegrationIdentity } from \"./identity-links-store.js\";\nimport {\n getActiveIntegrationInstallationByKey,\n getActiveIntegrationInstallationForTenant,\n} from \"./installations-store.js\";\nimport { slackInstallationKey } from \"./slack-oauth.js\";\nimport type { IncomingMessage, IntegrationExecutionContext } from \"./types.js\";\n\nexport type IntegrationIdentityDeclineReason =\n | \"unverified\"\n | \"guest\"\n | \"unlinked-workspace\"\n | \"membership-check-failed\";\n\n/**\n * Thrown when the default Slack DM identity ladder declines to run a message.\n * `reason` is a stable machine-readable discriminator (used e.g. to dedupe\n * decline replies); `userFacingMessage` is safe to send back to the sender as\n * a polite reply; `message` stays log-only.\n */\nexport class IntegrationIdentityDeclinedError extends Error {\n readonly reason: IntegrationIdentityDeclineReason;\n readonly userFacingMessage: string;\n\n constructor(\n reason: IntegrationIdentityDeclineReason,\n message: string,\n userFacingMessage: string,\n ) {\n super(message);\n this.name = \"IntegrationIdentityDeclinedError\";\n this.reason = reason;\n this.userFacingMessage = userFacingMessage;\n }\n}\n\nfunction serviceOwner(platform: string): string {\n return `integration@${platform}`;\n}\n\nfunction normalizedEmail(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n const email = value.trim().toLowerCase();\n if (!email || email.length > 320 || !email.includes(\"@\")) return null;\n return email;\n}\n\nasync function resolveSlackInstallation(incoming: IncomingMessage) {\n const teamId =\n typeof incoming.platformContext.teamId === \"string\"\n ? incoming.platformContext.teamId\n : incoming.tenantId;\n const enterpriseId =\n typeof incoming.platformContext.enterpriseId === \"string\"\n ? incoming.platformContext.enterpriseId\n : undefined;\n const apiAppId =\n typeof incoming.platformContext.apiAppId === \"string\"\n ? incoming.platformContext.apiAppId\n : undefined;\n if (apiAppId && (teamId || enterpriseId)) {\n const byKey = await getActiveIntegrationInstallationByKey(\n \"slack\",\n slackInstallationKey({ teamId, enterpriseId, apiAppId }),\n );\n if (byKey) return byKey;\n }\n return teamId || enterpriseId\n ? getActiveIntegrationInstallationForTenant(\n \"slack\",\n teamId ?? enterpriseId!,\n )\n : null;\n}\n\nasync function isMemberOfOrg(email: string, orgId: string): Promise<boolean> {\n const { rows } = await getDbExec().execute({\n sql: `SELECT 1 FROM org_members\n WHERE org_id = ? AND LOWER(email) = ?\n LIMIT 1`,\n args: [orgId, email],\n });\n return rows.length > 0;\n}\n\n/**\n * Resolve the default integration principal.\n *\n * Slack DMs become a user principal only after the adapter has verified the\n * sender email and the email is already a member of the managed installation's\n * Agent Native organization. Hydrated full workspace members whose email is\n * missing, unverified, or not an organization member run as an anonymous\n * org-scoped service principal instead — the same visibility shared channels\n * get. Hydration failures, guests/external members, and workspaces without a\n * connected organization are declined with a user-facing message. Shared\n * channels deliberately stay service-scoped so a channel message cannot borrow\n * one participant's private permissions.\n */\nexport async function resolveDefaultIntegrationExecutionContext(\n incoming: IncomingMessage,\n): Promise<IntegrationExecutionContext> {\n const installation =\n incoming.platform === \"slack\"\n ? await resolveSlackInstallation(incoming)\n : null;\n\n if (incoming.platform !== \"slack\" || incoming.conversationType !== \"dm\") {\n return {\n ownerEmail: serviceOwner(incoming.platform),\n orgId: installation?.orgId ?? null,\n principalType: \"service\",\n ...(installation?.id ? { installationId: installation.id } : {}),\n };\n }\n\n // Hydration check first: a transient users.info failure must land on the\n // retry decline here, never on the anonymous org-scoped tier below.\n if (incoming.actorTrust?.verified !== true) {\n throw new IntegrationIdentityDeclinedError(\n \"unverified\",\n \"Slack DM sender identity could not be hydrated; declining instead of guessing a principal.\",\n \"I couldn't verify your Slack identity just now, so I can't run this request. Please try again in a moment.\",\n );\n }\n if (\n incoming.actorTrust.memberType === \"guest\" ||\n incoming.actorTrust.memberType === \"external\"\n ) {\n throw new IntegrationIdentityDeclinedError(\n \"guest\",\n \"External or guest Slack members cannot use this integration.\",\n \"This assistant is only available to members of this workspace's organization.\",\n );\n }\n if (!installation?.orgId) {\n throw new IntegrationIdentityDeclinedError(\n \"unlinked-workspace\",\n \"Slack workspace is not connected to an Agent Native organization.\",\n \"This Slack workspace isn't connected to an organization yet.\",\n );\n }\n\n const email = normalizedEmail(incoming.senderEmail);\n let isOrgMember = false;\n if (\n email &&\n incoming.senderVerified === true &&\n incoming.senderId &&\n incoming.tenantId\n ) {\n try {\n isOrgMember = await isMemberOfOrg(email, installation.orgId);\n } catch {\n // A membership-store outage is not evidence that the sender is merely\n // unlinked. Fail closed instead of widening them to org-wide access.\n throw new IntegrationIdentityDeclinedError(\n \"membership-check-failed\",\n \"Slack DM organization membership could not be verified.\",\n \"I couldn't verify your organization membership just now, so I can't run this request. Please try again in a moment.\",\n );\n }\n }\n if (isOrgMember && email && incoming.senderId && incoming.tenantId) {\n const link = await upsertVerifiedIntegrationIdentity({\n platform: incoming.platform,\n tenantId: incoming.tenantId,\n externalUserId: incoming.senderId,\n userEmail: email,\n orgId: installation.orgId,\n });\n return {\n ownerEmail: link.userEmail,\n orgId: link.orgId,\n principalType: \"user\",\n installationId: installation.id,\n };\n }\n\n // Hydrated full workspace member (member/admin/owner) whose email is\n // unverified, missing (legacy install without users:read.email), or not an\n // organization member: run with the anonymous org-scoped service principal —\n // the same visibility shared channels get. Nothing user-private is\n // accessible. One structured line keeps 100%-anonymous workspaces (legacy\n // scope missing) visible in logs.\n console.warn(\n `[integrations] anonymous org-scoped principal used: platform=${incoming.platform} teamId=${incoming.tenantId ?? \"unknown\"} emailPresent=${Boolean(email)} memberType=${incoming.actorTrust.memberType}`,\n );\n return {\n ownerEmail: serviceOwner(incoming.platform),\n orgId: installation.orgId,\n principalType: \"service\",\n installationId: installation.id,\n anonymousMember: true,\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/integrations/plugin.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/integrations/plugin.ts"],"names":[],"mappings":"AAyIA,OAAO,KAAK,EAEV,yBAAyB,EAG1B,MAAM,YAAY,CAAC;AAOpB,KAAK,cAAc,GAAG,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AA4F9D,KAAK,yBAAyB,GAAG;IAC/B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAeF,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,yBAAyB,GAClC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAsFlC;AA+ND;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,CAAC,EAAE,yBAAyB,GAClC,cAAc,CA6hEhB;AAED;;GAEG;AACH,eAAO,MAAM,yBAAyB,gBAA6B,CAAC"}
|
|
@@ -26,7 +26,7 @@ import { ComputerSupervisionError } from "./computer-supervision.js";
|
|
|
26
26
|
import { getIntegrationConfig, saveIntegrationConfig } from "./config-store.js";
|
|
27
27
|
import { claimIntegrationControl } from "./controls-store.js";
|
|
28
28
|
import { startGoogleDocsPoller, handlePushNotification, } from "./google-docs-poller.js";
|
|
29
|
-
import { resolveDefaultIntegrationExecutionContext } from "./identity.js";
|
|
29
|
+
import { IntegrationIdentityDeclinedError, resolveDefaultIntegrationExecutionContext, } from "./identity.js";
|
|
30
30
|
import { disconnectIntegrationInstallation, listIntegrationInstallations, resolveIntegrationTokenBundle, updateIntegrationInstallation, upsertIntegrationInstallation, } from "./installations-store.js";
|
|
31
31
|
import { forgetIntegrationMemory, integrationMemoryActions, listIntegrationMemory, rememberForIntegrationScope, } from "./integration-memory.js";
|
|
32
32
|
import { extractBearerToken, signInternalToken, verifyInternalToken, } from "./internal-token.js";
|
|
@@ -124,6 +124,10 @@ Keep responses concise — messaging platforms have character limits and users e
|
|
|
124
124
|
|
|
125
125
|
If a task requires many steps, summarize what you did rather than streaming every detail.`;
|
|
126
126
|
const REMOTE_DEVICE_ONLINE_MS = 90_000;
|
|
127
|
+
// One decline reply per sender + decline reason per window: during a Slack
|
|
128
|
+
// API outage every message would otherwise get another identical "try again"
|
|
129
|
+
// reply. Short enough that a persistent condition still reminds the sender.
|
|
130
|
+
const DECLINE_NOTICE_DEDUPE_TTL_MS = 5 * 60 * 1_000;
|
|
127
131
|
export async function enqueueRemoteCommand(envelope) {
|
|
128
132
|
const ownerEmail = readString(envelope.ownerEmail);
|
|
129
133
|
if (!ownerEmail)
|
|
@@ -1912,11 +1916,33 @@ export function createIntegrationsPlugin(options) {
|
|
|
1912
1916
|
defaultExecutionContext = await withCredentialContext(credentialContext, () => resolveDefaultIntegrationExecutionContext(incoming));
|
|
1913
1917
|
}
|
|
1914
1918
|
catch (err) {
|
|
1915
|
-
//
|
|
1916
|
-
// Slack
|
|
1917
|
-
//
|
|
1918
|
-
|
|
1919
|
-
|
|
1919
|
+
// Only an explicit execution-context resolver may override the
|
|
1920
|
+
// default Slack identity ladder. The legacy owner-only resolver
|
|
1921
|
+
// predates org-bound identities and must not turn a rejected DM
|
|
1922
|
+
// into an authenticated owner run.
|
|
1923
|
+
if (!options?.resolveExecutionContext) {
|
|
1924
|
+
const declined = err instanceof IntegrationIdentityDeclinedError ? err : null;
|
|
1925
|
+
if (declined) {
|
|
1926
|
+
console.warn(`[integrations] default Slack DM identity declined message:`, declined.message);
|
|
1927
|
+
// Best-effort polite reply back to the DM, deduped per
|
|
1928
|
+
// sender + reason so an outage doesn't produce an identical
|
|
1929
|
+
// reply for every message. Do not await Slack's API: webhook
|
|
1930
|
+
// acknowledgement has a stricter deadline than notice
|
|
1931
|
+
// delivery and must not be delayed by a slow postMessage.
|
|
1932
|
+
void withCredentialContext(credentialContext, () => adapter.sendSystemNotice
|
|
1933
|
+
? adapter.sendSystemNotice(incoming, declined.userFacingMessage, {
|
|
1934
|
+
dedupeKey: `decline:${incoming.tenantId ?? "unknown"}:${incoming.senderId ?? "unknown"}:${declined.reason}`,
|
|
1935
|
+
dedupeTtlMs: DECLINE_NOTICE_DEDUPE_TTL_MS,
|
|
1936
|
+
})
|
|
1937
|
+
: Promise.resolve()).catch((noticeErr) => {
|
|
1938
|
+
console.warn(`[integrations] decline notice failed:`, noticeErr instanceof Error
|
|
1939
|
+
? noticeErr.message
|
|
1940
|
+
: noticeErr);
|
|
1941
|
+
});
|
|
1942
|
+
}
|
|
1943
|
+
else {
|
|
1944
|
+
console.error(`[integrations] default Slack DM identity denied message:`, err);
|
|
1945
|
+
}
|
|
1920
1946
|
setResponseStatus(event, 200);
|
|
1921
1947
|
return "ok";
|
|
1922
1948
|
}
|
|
@@ -1939,6 +1965,30 @@ export function createIntegrationsPlugin(options) {
|
|
|
1939
1965
|
}
|
|
1940
1966
|
else if (defaultExecutionContext) {
|
|
1941
1967
|
executionContext = defaultExecutionContext;
|
|
1968
|
+
if (defaultExecutionContext.anonymousMember) {
|
|
1969
|
+
// The anonymous tier must never be silent. (1) The agent run
|
|
1970
|
+
// can tell: the note rides the serialized `incoming` into the
|
|
1971
|
+
// queued task and surfaces via <integration-context>.
|
|
1972
|
+
incoming.identityNote =
|
|
1973
|
+
"Caller is an unlinked Slack workspace member running with organization-wide visibility only; personal or privately-shared data is not accessible. They can get personal access by having an admin add their Slack email to the organization (or by reconnecting Slack with the users:read.email scope).";
|
|
1974
|
+
// (2) The sender gets a one-time heads-up (adapter-throttled per
|
|
1975
|
+
// sender). Fire-and-forget so it never delays or replaces the
|
|
1976
|
+
// normal agent run.
|
|
1977
|
+
if (adapter.sendSystemNotice) {
|
|
1978
|
+
const senderEmail = typeof incoming.senderEmail === "string" &&
|
|
1979
|
+
incoming.senderEmail.trim()
|
|
1980
|
+
? incoming.senderEmail.trim()
|
|
1981
|
+
: null;
|
|
1982
|
+
const noticeText = senderEmail
|
|
1983
|
+
? `Heads up: I couldn't match your Slack account to an organization member, so I can only use org-wide data. Ask an admin to add ${senderEmail} to the organization for personal access.`
|
|
1984
|
+
: "Heads up: I couldn't verify your Slack account's email, so I can only use org-wide data. Ask an admin to update the Slack connection with the users:read.email scope for personal access.";
|
|
1985
|
+
void withCredentialContext(credentialContext, () => adapter.sendSystemNotice(incoming, noticeText, {
|
|
1986
|
+
dedupeKey: `anonymous-tier:${incoming.tenantId ?? "unknown"}:${incoming.senderId ?? "unknown"}`,
|
|
1987
|
+
})).catch((noticeErr) => {
|
|
1988
|
+
console.warn(`[integrations] anonymous-tier notice failed:`, noticeErr instanceof Error ? noticeErr.message : noticeErr);
|
|
1989
|
+
});
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1942
1992
|
}
|
|
1943
1993
|
else if (options?.resolveOwner) {
|
|
1944
1994
|
try {
|