@anthusai/papyrus 0.0.0 → 1.0.0-next.2

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 (254) hide show
  1. package/LICENSE +21 -0
  2. package/amplify/auth/resource.js +61 -0
  3. package/amplify/auth/resource.ts +65 -0
  4. package/amplify/data/resource.js +2037 -0
  5. package/amplify/data/resource.ts +2148 -0
  6. package/amplify/data/schema.js +1850 -0
  7. package/amplify/data/schema.ts +1953 -0
  8. package/amplify/functions/assignment-action/handler.ts +1416 -0
  9. package/amplify/functions/assignment-action/resource.js +8 -0
  10. package/amplify/functions/assignment-action/resource.ts +9 -0
  11. package/amplify/functions/category-action/handler.ts +2259 -0
  12. package/amplify/functions/category-action/resource.js +8 -0
  13. package/amplify/functions/category-action/resource.ts +9 -0
  14. package/amplify/functions/console-chat-responder/Cargo.lock +3456 -0
  15. package/amplify/functions/console-chat-responder/Cargo.toml +26 -0
  16. package/amplify/functions/console-chat-responder/Dockerfile +19 -0
  17. package/amplify/functions/console-chat-responder/py/execute_tactus_runner.py +116 -0
  18. package/amplify/functions/console-chat-responder/resource.js +153 -0
  19. package/amplify/functions/console-chat-responder/resource.ts +179 -0
  20. package/amplify/functions/console-chat-responder/src/main.rs +3242 -0
  21. package/amplify/functions/email-submission-processor/handler.py +46 -0
  22. package/amplify/functions/email-submission-processor/requirements.txt +2 -0
  23. package/amplify/functions/email-submission-processor/resource.js +56 -0
  24. package/amplify/functions/email-submission-processor/resource.ts +69 -0
  25. package/amplify/functions/graphql-jwt-authorizer/handler.ts +193 -0
  26. package/amplify/functions/graphql-jwt-authorizer/resource.js +14 -0
  27. package/amplify/functions/graphql-jwt-authorizer/resource.ts +15 -0
  28. package/amplify/functions/knowledge-query/handler.py +1 -0
  29. package/amplify/functions/knowledge-query/requirements.txt +3 -0
  30. package/amplify/functions/knowledge-query/resource.js +50 -0
  31. package/amplify/functions/knowledge-query/resource.ts +61 -0
  32. package/amplify/functions/manage-user-role/handler.ts +799 -0
  33. package/amplify/functions/manage-user-role/resource.js +8 -0
  34. package/amplify/functions/manage-user-role/resource.ts +9 -0
  35. package/amplify/functions/model-attachment-upload/handler.ts +387 -0
  36. package/amplify/functions/model-attachment-upload/resource.js +8 -0
  37. package/amplify/functions/model-attachment-upload/resource.ts +9 -0
  38. package/amplify/functions/newsroom-summary/handler.ts +321 -0
  39. package/amplify/functions/newsroom-summary/resource.js +8 -0
  40. package/amplify/functions/newsroom-summary/resource.ts +9 -0
  41. package/amplify/functions/procedure-action/handler.ts +621 -0
  42. package/amplify/functions/procedure-action/resource.js +8 -0
  43. package/amplify/functions/procedure-action/resource.ts +9 -0
  44. package/amplify/functions/reader-settings/handler.ts +322 -0
  45. package/amplify/functions/reader-settings/resource.js +8 -0
  46. package/amplify/functions/reader-settings/resource.ts +9 -0
  47. package/amplify/functions/ses-inbound-receive/handler.ts +493 -0
  48. package/amplify/functions/ses-inbound-receive/resource.js +8 -0
  49. package/amplify/functions/ses-inbound-receive/resource.ts +9 -0
  50. package/amplify/functions/shared/amplify-secrets.ts +65 -0
  51. package/amplify/functions/shared/console-chat-enqueue.ts +117 -0
  52. package/amplify/functions/shared/email-mime-intake.ts +196 -0
  53. package/amplify/functions/shared/email-submission.ts +415 -0
  54. package/amplify/functions/shared/inbound-email-intake.ts +37 -0
  55. package/amplify/functions/shared/lambda-data-client.ts +21 -0
  56. package/amplify/functions/shared/model-payloads.ts +186 -0
  57. package/amplify/functions/shared/python-bundle.js +115 -0
  58. package/amplify/functions/shared/python-bundle.ts +141 -0
  59. package/amplify/functions/shared/slack-events.ts +111 -0
  60. package/amplify/functions/slack-delivery/handler.py +112 -0
  61. package/amplify/functions/slack-delivery/requirements.txt +2 -0
  62. package/amplify/functions/slack-delivery/resource.js +38 -0
  63. package/amplify/functions/slack-delivery/resource.ts +51 -0
  64. package/amplify/functions/slack-events/handler.ts +155 -0
  65. package/amplify/functions/slack-events/resource.js +11 -0
  66. package/amplify/functions/slack-events/resource.ts +12 -0
  67. package/amplify/inbound-email/stack.js +46 -0
  68. package/amplify/inbound-email/stack.ts +61 -0
  69. package/amplify/package.json +3 -0
  70. package/amplify/site-backend-config.js +1 -0
  71. package/amplify/site-backend-config.ts +30 -0
  72. package/amplify/site-backend.js +587 -0
  73. package/amplify/site-backend.ts +704 -0
  74. package/amplify/storage/resource.js +18 -0
  75. package/amplify/storage/resource.ts +19 -0
  76. package/app/[year]/[month]/[day]/[articleSlug]/page.tsx +62 -0
  77. package/app/[year]/[month]/[day]/edition-route-page.tsx +53 -0
  78. package/app/[year]/[month]/[day]/page/[pageNumber]/page.tsx +32 -0
  79. package/app/[year]/[month]/[day]/page.tsx +22 -0
  80. package/app/[year]/[month]/[day]/section/[sectionKey]/page.tsx +32 -0
  81. package/app/api/archive/editions/route.ts +18 -0
  82. package/app/api/media/[...path]/route.ts +35 -0
  83. package/app/api/revalidate/route.ts +94 -0
  84. package/app/archive/page.tsx +24 -0
  85. package/app/articles/[slug]/page.tsx +35 -0
  86. package/app/globals.css +10329 -0
  87. package/app/layout.tsx +146 -0
  88. package/app/newsroom/[section]/[...selection]/page.tsx +50 -0
  89. package/app/newsroom/[section]/page.tsx +47 -0
  90. package/app/newsroom/forum/[threadId]/page.tsx +35 -0
  91. package/app/newsroom/insights/[threadId]/page.tsx +22 -0
  92. package/app/newsroom/insights/page.tsx +14 -0
  93. package/app/newsroom/layout.tsx +12 -0
  94. package/app/newsroom/page.tsx +31 -0
  95. package/app/newsroom/sections/[sectionId]/page.tsx +17 -0
  96. package/app/page.tsx +272 -0
  97. package/app/settings/page.tsx +14 -0
  98. package/app/tailwind.css +65 -0
  99. package/bin/papyrus-app.mjs +10 -0
  100. package/bin/papyrus-infra.mjs +39 -0
  101. package/components/ai-elements/conversation.tsx +168 -0
  102. package/components/ai-elements/message.tsx +360 -0
  103. package/components/ai-elements/model-selector.tsx +30 -0
  104. package/components/ai-elements/prompt-input.tsx +1461 -0
  105. package/components/ai-elements/shimmer.tsx +76 -0
  106. package/components/ai-elements/suggestion.tsx +54 -0
  107. package/components/ai-elements/tool.tsx +173 -0
  108. package/components/amplify-client-provider.tsx +140 -0
  109. package/components/archive-grid.tsx +89 -0
  110. package/components/archive-shell.tsx +41 -0
  111. package/components/article-video.tsx +47 -0
  112. package/components/blog-page-background.tsx +20 -0
  113. package/components/dev-sandbox-editor-sign-in.tsx +60 -0
  114. package/components/news-desk-client-provider.tsx +211 -0
  115. package/components/news-desk-page.tsx +119 -0
  116. package/components/news-desk-taxonomy-client.ts +3684 -0
  117. package/components/newspaper.tsx +1929 -0
  118. package/components/newsroom-assignments-view.tsx +664 -0
  119. package/components/newsroom-ops-overview.tsx +226 -0
  120. package/components/newsroom-ops-shell.tsx +319 -0
  121. package/components/newsroom-references-view.tsx +598 -0
  122. package/components/newsroom-topics-view.tsx +425 -0
  123. package/components/papyrus-console-shell.tsx +1912 -0
  124. package/components/pictogram-figure.tsx +65 -0
  125. package/components/presentation-footer.tsx +146 -0
  126. package/components/reader-auth-control.tsx +162 -0
  127. package/components/reader-auth-state.ts +122 -0
  128. package/components/reader-settings.ts +332 -0
  129. package/components/reader-theme-control.tsx +85 -0
  130. package/components/reference-source-preview.tsx +163 -0
  131. package/components/settings-page.tsx +239 -0
  132. package/components/topic-steering-workspace.tsx +18063 -0
  133. package/components/ui/badge.tsx +30 -0
  134. package/components/ui/button-group.tsx +87 -0
  135. package/components/ui/button.tsx +58 -0
  136. package/components/ui/card.tsx +32 -0
  137. package/components/ui/command.tsx +194 -0
  138. package/components/ui/dialog.tsx +158 -0
  139. package/components/ui/dropdown-menu.tsx +266 -0
  140. package/components/ui/hover-card.tsx +51 -0
  141. package/components/ui/input-group.tsx +158 -0
  142. package/components/ui/input.tsx +20 -0
  143. package/components/ui/scroll-area.tsx +16 -0
  144. package/components/ui/select.tsx +199 -0
  145. package/components/ui/separator.tsx +23 -0
  146. package/components/ui/sheet.tsx +92 -0
  147. package/components/ui/spinner.tsx +10 -0
  148. package/components/ui/tabs.tsx +113 -0
  149. package/components/ui/textarea.tsx +18 -0
  150. package/components/ui/tooltip.tsx +66 -0
  151. package/components/use-resolved-papyrus-theme.ts +47 -0
  152. package/infra/amplify-app-shell.js +109 -0
  153. package/infra/amplify-app-shell.ts +155 -0
  154. package/infra/index.js +12 -0
  155. package/lib/amplify-outputs-guard.ts +24 -0
  156. package/lib/amplify-outputs-path.ts +16 -0
  157. package/lib/amplify-server-runtime.ts +63 -0
  158. package/lib/archive-data.ts +30 -0
  159. package/lib/archive-types.ts +15 -0
  160. package/lib/articles.ts +320 -0
  161. package/lib/cached-content-repository.ts +89 -0
  162. package/lib/category-dashboard.ts +37 -0
  163. package/lib/category-repository.ts +1907 -0
  164. package/lib/console-chat-client.ts +1316 -0
  165. package/lib/console-web-context.ts +118 -0
  166. package/lib/content-load-timing.ts +23 -0
  167. package/lib/content-repository.ts +40 -0
  168. package/lib/content-types.ts +104 -0
  169. package/lib/define-site.js +3 -0
  170. package/lib/define-site.ts +21 -0
  171. package/lib/demo-amplify-outputs.ts +15 -0
  172. package/lib/dev-sandbox-editor-auth.ts +18 -0
  173. package/lib/doctrine.ts +128 -0
  174. package/lib/edition-routes.ts +155 -0
  175. package/lib/edition-sections.ts +111 -0
  176. package/lib/empty-edition-layout-plan.ts +109 -0
  177. package/lib/excerpts.ts +8 -0
  178. package/lib/graphql-content-repository.ts +986 -0
  179. package/lib/image-url.ts +10 -0
  180. package/lib/layout-plan.ts +981 -0
  181. package/lib/layout-scenarios.ts +564 -0
  182. package/lib/markus-body.ts +168 -0
  183. package/lib/markus-ir.ts +235 -0
  184. package/lib/markus-projection.ts +243 -0
  185. package/lib/markus-to-article.ts +93 -0
  186. package/lib/news-desk-session.ts +160 -0
  187. package/lib/newspaper-layout.ts +3459 -0
  188. package/lib/newsroom-assignments.ts +173 -0
  189. package/lib/newsroom-base-path.ts +48 -0
  190. package/lib/newsroom-card-layout.ts +102 -0
  191. package/lib/newsroom-category-drilldown.ts +133 -0
  192. package/lib/newsroom-demo-dashboard.ts +267 -0
  193. package/lib/newsroom-demo-profile.ts +46 -0
  194. package/lib/newsroom-forum-routes.ts +153 -0
  195. package/lib/newsroom-index-filters.ts +202 -0
  196. package/lib/newsroom-knowledge-query-request.ts +51 -0
  197. package/lib/newsroom-list-selection.ts +42 -0
  198. package/lib/newsroom-nav.ts +42 -0
  199. package/lib/newsroom-references.ts +116 -0
  200. package/lib/newsroom-sections.ts +52 -0
  201. package/lib/newsroom-topics.ts +118 -0
  202. package/lib/papyrus-object-kinds.ts +24 -0
  203. package/lib/papyrus-web-locations.ts +480 -0
  204. package/lib/pilobol-sample.ts +167 -0
  205. package/lib/presentation-footer.ts +92 -0
  206. package/lib/pretext-layout.ts +179 -0
  207. package/lib/public-placeholder-config.ts +102 -0
  208. package/lib/publication-items.ts +181 -0
  209. package/lib/reader-route-config.ts +1 -0
  210. package/lib/reader-static-params.ts +56 -0
  211. package/lib/reader-storage-url.ts +63 -0
  212. package/lib/reference-display.ts +74 -0
  213. package/lib/reference-policy.ts +97 -0
  214. package/lib/reference-source-preview.ts +175 -0
  215. package/lib/renderer-config.ts +50 -0
  216. package/lib/renderer.ts +87 -0
  217. package/lib/reporting-story-budget.ts +638 -0
  218. package/lib/semantic-graph.ts +554 -0
  219. package/lib/site-brand-tenant.ts +41 -0
  220. package/lib/site-brand.ts +179 -0
  221. package/lib/site-renderer.ts +17 -0
  222. package/lib/site-stack.ts +195 -0
  223. package/lib/themed-image.ts +30 -0
  224. package/lib/utils.ts +6 -0
  225. package/middleware.ts +78 -0
  226. package/package.json +296 -1
  227. package/publications/anth_us/brand.ts +68 -0
  228. package/publications/anth_us/theme.css +66 -0
  229. package/publications/papyrus/theme.css +11 -0
  230. package/publications/pilobol_us/brand.ts +37 -0
  231. package/publications/pilobol_us/theme.css +140 -0
  232. package/publications/threat_intelligence/blog-defense/graph.ts +636 -0
  233. package/publications/threat_intelligence/blog-defense/layout.ts +934 -0
  234. package/publications/threat_intelligence/blog-defense/page-background.tsx +590 -0
  235. package/publications/threat_intelligence/brand.ts +40 -0
  236. package/publications/threat_intelligence/pictograms/art.tsx +608 -0
  237. package/publications/threat_intelligence/pictograms/figure.tsx +82 -0
  238. package/publications/threat_intelligence/pictograms/registry.ts +30 -0
  239. package/publications/threat_intelligence/pictograms/system.tsx +305 -0
  240. package/publications/threat_intelligence/seed/seed-edition-content.json +629 -0
  241. package/publications/threat_intelligence/theme.css +1440 -0
  242. package/renderers/markus/index.ts +25 -0
  243. package/renderers/markus/stubs.tsx +33 -0
  244. package/renderers/pretext/article-page.tsx +174 -0
  245. package/renderers/pretext/index.ts +30 -0
  246. package/renderers/pretext/layouts.ts +16 -0
  247. package/renderers/pretext/presentation-shell.tsx +477 -0
  248. package/routes.manifest.json +182 -0
  249. package/scripts/fixtures/pilobol-sample.ast.json +234 -0
  250. package/src/backend.js +1 -0
  251. package/src/backend.ts +3 -0
  252. package/src/sync.mjs +87 -0
  253. package/src/with-papyrus.d.mts +2 -0
  254. package/src/with-papyrus.mjs +47 -0
@@ -0,0 +1,38 @@
1
+ import { defineFunction } from "@aws-amplify/backend";
2
+ import { Duration } from "aws-cdk-lib";
3
+ import { Architecture, Function, Runtime } from "aws-cdk-lib/aws-lambda";
4
+ import { execSync } from "node:child_process";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { pythonLambdaCode } from "../shared/python-bundle.js";
8
+ const dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ // Repo root in a checkout, package root when installed from npm.
10
+ const projectRoot = path.resolve(dirname, "../../..");
11
+ export const slackDelivery = defineFunction((scope) => {
12
+ return new Function(scope, "papyrus-slack-delivery", {
13
+ runtime: Runtime.PYTHON_3_12,
14
+ architecture: Architecture.ARM_64,
15
+ handler: "handler.handler",
16
+ timeout: Duration.seconds(60),
17
+ memorySize: 512,
18
+ code: pythonLambdaCode({
19
+ functionDir: "slack-delivery",
20
+ modules: ["papyrus_newsroom", "papyrus_content"],
21
+ requirements: ["PyYAML>=6.0.2,<7.0.0", "boto3>=1.34.0,<2.0.0"],
22
+ checkoutExtras(outputDir) {
23
+ execSync(`python3 -m pip install -r "${path.join(dirname, "requirements.txt")}" -t "${outputDir}" --no-cache-dir`, { cwd: projectRoot, stdio: "inherit" });
24
+ },
25
+ }, [
26
+ "bash",
27
+ "-c",
28
+ [
29
+ "set -euo pipefail",
30
+ "mkdir -p /asset-output/papyrus_newsroom /asset-output/papyrus_content",
31
+ "cp amplify/functions/slack-delivery/handler.py /asset-output/handler.py",
32
+ "cp -R src/papyrus_newsroom/. /asset-output/papyrus_newsroom/",
33
+ "cp -R src/papyrus_content/. /asset-output/papyrus_content/",
34
+ "python3 -m pip install -r amplify/functions/slack-delivery/requirements.txt -t /asset-output --no-cache-dir",
35
+ ].join(" && "),
36
+ ]),
37
+ });
38
+ }, { resourceGroupName: "data" });
@@ -0,0 +1,51 @@
1
+ import { defineFunction } from "@aws-amplify/backend";
2
+ import { Duration } from "aws-cdk-lib";
3
+ import { Architecture, Function, Runtime } from "aws-cdk-lib/aws-lambda";
4
+ import { Construct } from "constructs";
5
+ import { execSync } from "node:child_process";
6
+ import fs from "node:fs";
7
+ import path from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ import { pythonLambdaCode } from "../shared/python-bundle";
10
+
11
+ const dirname = path.dirname(fileURLToPath(import.meta.url));
12
+ // Repo root in a checkout, package root when installed from npm.
13
+ const projectRoot = path.resolve(dirname, "../../..");
14
+
15
+ export const slackDelivery = defineFunction(
16
+ (scope: Construct) => {
17
+ return new Function(scope, "papyrus-slack-delivery", {
18
+ runtime: Runtime.PYTHON_3_12,
19
+ architecture: Architecture.ARM_64,
20
+ handler: "handler.handler",
21
+ timeout: Duration.seconds(60),
22
+ memorySize: 512,
23
+ code: pythonLambdaCode(
24
+ {
25
+ functionDir: "slack-delivery",
26
+ modules: ["papyrus_newsroom", "papyrus_content"],
27
+ requirements: ["PyYAML>=6.0.2,<7.0.0", "boto3>=1.34.0,<2.0.0"],
28
+ checkoutExtras(outputDir) {
29
+ execSync(
30
+ `python3 -m pip install -r "${path.join(dirname, "requirements.txt")}" -t "${outputDir}" --no-cache-dir`,
31
+ { cwd: projectRoot, stdio: "inherit" },
32
+ );
33
+ },
34
+ },
35
+ [
36
+ "bash",
37
+ "-c",
38
+ [
39
+ "set -euo pipefail",
40
+ "mkdir -p /asset-output/papyrus_newsroom /asset-output/papyrus_content",
41
+ "cp amplify/functions/slack-delivery/handler.py /asset-output/handler.py",
42
+ "cp -R src/papyrus_newsroom/. /asset-output/papyrus_newsroom/",
43
+ "cp -R src/papyrus_content/. /asset-output/papyrus_content/",
44
+ "python3 -m pip install -r amplify/functions/slack-delivery/requirements.txt -t /asset-output --no-cache-dir",
45
+ ].join(" && "),
46
+ ],
47
+ ),
48
+ });
49
+ },
50
+ { resourceGroupName: "data" },
51
+ );
@@ -0,0 +1,155 @@
1
+ import {
2
+ buildSlackPrompt,
3
+ chatMessageIdForSlackEvent,
4
+ shouldIgnoreSlackEvent,
5
+ slackSigningSecret,
6
+ slackThreadId,
7
+ verifySlackRequestSignature,
8
+ } from "../shared/slack-events";
9
+ import { enqueueConsoleChatTurn } from "../shared/console-chat-enqueue";
10
+ import { getLambdaDataClient, LAMBDA_DATA_AUTH_MODE } from "../shared/lambda-data-client";
11
+
12
+ type FunctionUrlEvent = {
13
+ headers?: Record<string, string | undefined>;
14
+ body?: string | null;
15
+ isBase64Encoded?: boolean;
16
+ };
17
+
18
+ export const handler = async (event: FunctionUrlEvent) => {
19
+ const rawBody = decodeBody(event);
20
+ const headers = normalizeHeaders(event.headers ?? {});
21
+ const timestamp = headers["x-slack-request-timestamp"] ?? "";
22
+ const signature = headers["x-slack-signature"] ?? "";
23
+
24
+ let payload: Record<string, unknown>;
25
+ try {
26
+ payload = JSON.parse(rawBody.toString("utf8")) as Record<string, unknown>;
27
+ } catch {
28
+ return jsonResponse(400, { ok: false, error: "invalid-json" });
29
+ }
30
+
31
+ const payloadType = String(payload.type ?? "").trim();
32
+ if (payloadType === "url_verification") {
33
+ // Slack only checks that the endpoint echoes `challenge` (no signature on this step).
34
+ // Match slack_agent.py; real events below still require a valid signing secret.
35
+ const challenge = String(payload.challenge ?? "").trim();
36
+ if (!challenge) {
37
+ return jsonResponse(400, { ok: false, error: "missing-challenge" });
38
+ }
39
+ return jsonResponse(200, { challenge });
40
+ }
41
+
42
+ let secret: string;
43
+ try {
44
+ secret = await slackSigningSecret();
45
+ } catch (error) {
46
+ return jsonResponse(500, {
47
+ ok: false,
48
+ error: error instanceof Error ? error.message : "PAPYRUS_SLACK_SIGNING_SECRET is not configured.",
49
+ });
50
+ }
51
+ if (!verifySlackRequestSignature({ signingSecret: secret, timestamp, rawBody, signature })) {
52
+ return jsonResponse(401, { ok: false, error: "invalid-slack-signature" });
53
+ }
54
+
55
+ if (payloadType !== "event_callback") {
56
+ return jsonResponse(200, { ok: true, ignored: true, reason: `unsupported-type:${payloadType || "missing"}` });
57
+ }
58
+
59
+ const eventId = String(payload.event_id ?? "").trim();
60
+ const teamId = String(payload.team_id ?? "").trim() || null;
61
+ const slackEvent = payload.event;
62
+ if (!slackEvent || typeof slackEvent !== "object") {
63
+ return jsonResponse(400, { ok: false, error: "missing-event" });
64
+ }
65
+
66
+ const eventRecord = slackEvent as Record<string, unknown>;
67
+ const eventType = String(eventRecord.type ?? "").trim();
68
+ if (eventType !== "message" && eventType !== "app_mention") {
69
+ return jsonResponse(200, { ok: true, ignored: true, reason: `unsupported-event:${eventType}` });
70
+ }
71
+
72
+ const ignoreReason = shouldIgnoreSlackEvent(eventRecord);
73
+ if (ignoreReason) {
74
+ return jsonResponse(200, { ok: true, ignored: true, reason: ignoreReason });
75
+ }
76
+
77
+ const channelId = String(eventRecord.channel ?? "").trim();
78
+ const threadTs = String(eventRecord.thread_ts ?? eventRecord.ts ?? "").trim();
79
+ if (!channelId || !threadTs) {
80
+ return jsonResponse(400, { ok: false, error: "missing-channel-or-ts" });
81
+ }
82
+
83
+ const resolvedEventId = eventId || `${eventType}-${threadTs}`;
84
+ const chatMessageId = chatMessageIdForSlackEvent(resolvedEventId);
85
+ const dataClient = await getLambdaDataClient();
86
+ const existing = await dataClient.models.Message.get(
87
+ { id: chatMessageId },
88
+ { authMode: LAMBDA_DATA_AUTH_MODE },
89
+ );
90
+ if (existing.data?.id) {
91
+ return jsonResponse(200, { ok: true, idempotent: true, chatMessageId });
92
+ }
93
+
94
+ const user = String(eventRecord.user ?? "slack-user").trim();
95
+ const threadId = slackThreadId(teamId ?? "", channelId, threadTs);
96
+ const prompt = buildSlackPrompt({
97
+ event: eventRecord,
98
+ teamId,
99
+ channelId,
100
+ threadTs,
101
+ });
102
+
103
+ const chat = await enqueueConsoleChatTurn(dataClient, {
104
+ threadId,
105
+ threadTitle: `Slack ${channelId}`,
106
+ threadSummary: "Slack agent conversation",
107
+ primaryAnchorKind: "slack_thread",
108
+ primaryAnchorId: threadId,
109
+ createdByLabel: `slack:${user}`,
110
+ prompt,
111
+ messageSummary: String(eventRecord.text ?? "Slack message").slice(0, 180),
112
+ source: "slack",
113
+ channel: "slack",
114
+ channelMetadata: {
115
+ slackTeamId: teamId,
116
+ slackChannelId: channelId,
117
+ slackThreadTs: threadTs,
118
+ slackUserId: user,
119
+ slackEventId: resolvedEventId,
120
+ },
121
+ threadMetadata: {
122
+ slackTeamId: teamId,
123
+ slackChannelId: channelId,
124
+ slackThreadTs: threadTs,
125
+ },
126
+ captureModelContext: true,
127
+ chatMessageId,
128
+ });
129
+
130
+ return jsonResponse(200, { ok: true, chat, eventId: resolvedEventId });
131
+ };
132
+
133
+ function decodeBody(event: FunctionUrlEvent): Buffer {
134
+ const body = event.body ?? "";
135
+ if (event.isBase64Encoded) {
136
+ return Buffer.from(body, "base64");
137
+ }
138
+ return Buffer.from(body, "utf8");
139
+ }
140
+
141
+ function normalizeHeaders(headers: Record<string, string | undefined>): Record<string, string> {
142
+ const normalized: Record<string, string> = {};
143
+ for (const [key, value] of Object.entries(headers)) {
144
+ if (typeof value === "string") normalized[key.toLowerCase()] = value;
145
+ }
146
+ return normalized;
147
+ }
148
+
149
+ function jsonResponse(statusCode: number, body: unknown) {
150
+ return {
151
+ statusCode,
152
+ headers: { "content-type": "application/json" },
153
+ body: JSON.stringify(body),
154
+ };
155
+ }
@@ -0,0 +1,11 @@
1
+ import { defineFunction, secret } from "@aws-amplify/backend";
2
+ export const slackEvents = defineFunction({
3
+ name: "papyrus-slack-events",
4
+ entry: "./handler.ts",
5
+ timeoutSeconds: 30,
6
+ memoryMB: 512,
7
+ resourceGroupName: "data",
8
+ environment: {
9
+ PAPYRUS_SLACK_SIGNING_SECRET: secret("PAPYRUS_SLACK_SIGNING_SECRET"),
10
+ },
11
+ });
@@ -0,0 +1,12 @@
1
+ import { defineFunction, secret } from "@aws-amplify/backend";
2
+
3
+ export const slackEvents = defineFunction({
4
+ name: "papyrus-slack-events",
5
+ entry: "./handler.ts",
6
+ timeoutSeconds: 30,
7
+ memoryMB: 512,
8
+ resourceGroupName: "data",
9
+ environment: {
10
+ PAPYRUS_SLACK_SIGNING_SECRET: secret("PAPYRUS_SLACK_SIGNING_SECRET"),
11
+ },
12
+ });
@@ -0,0 +1,46 @@
1
+ import { Duration } from "aws-cdk-lib";
2
+ import * as events from "aws-cdk-lib/aws-events";
3
+ import * as eventTargets from "aws-cdk-lib/aws-events-targets";
4
+ import * as iam from "aws-cdk-lib/aws-iam";
5
+ import * as lambda from "aws-cdk-lib/aws-lambda";
6
+ import { Construct } from "constructs";
7
+ /** EventBridge triggers live in the data stack with the receive Lambda. */
8
+ export class InboundEmailStack extends Construct {
9
+ constructor(scope, id, props) {
10
+ super(scope, id);
11
+ const receiveFunction = lambda.Function.fromFunctionArn(this, "InboundReceiveFunction", props.receiveFunctionArn);
12
+ const objectCreatedRule = new events.Rule(this, "PapyrusInboundEmailObjectCreated", {
13
+ description: "Process inbound SES MIME objects stored under inbound-email/",
14
+ eventPattern: {
15
+ source: ["aws.s3"],
16
+ detailType: ["Object Created"],
17
+ detail: {
18
+ bucket: { name: [props.storageBucket.bucketName] },
19
+ object: { key: [{ prefix: "inbound-email/" }] },
20
+ },
21
+ },
22
+ targets: [new eventTargets.LambdaFunction(receiveFunction)],
23
+ });
24
+ // fromFunctionArn does not create invoke permissions; EventBridge needs an explicit grant.
25
+ receiveFunction.addPermission("AllowInboundEmailEventBridgeInvoke", {
26
+ principal: new iam.ServicePrincipal("events.amazonaws.com"),
27
+ sourceArn: objectCreatedRule.ruleArn,
28
+ });
29
+ const retrySweepRule = new events.Rule(this, "PapyrusInboundEmailRetrySweep", {
30
+ description: "Re-process inbound MIME objects left in inbound-email/ after failures",
31
+ schedule: events.Schedule.rate(Duration.minutes(15)),
32
+ targets: [
33
+ new eventTargets.LambdaFunction(receiveFunction, {
34
+ event: events.RuleTargetInput.fromObject({
35
+ source: "papyrus.inbound-email",
36
+ action: "retry-pending",
37
+ }),
38
+ }),
39
+ ],
40
+ });
41
+ receiveFunction.addPermission("AllowInboundEmailRetrySweepInvoke", {
42
+ principal: new iam.ServicePrincipal("events.amazonaws.com"),
43
+ sourceArn: retrySweepRule.ruleArn,
44
+ });
45
+ }
46
+ }
@@ -0,0 +1,61 @@
1
+ import { Duration, type StackProps } from "aws-cdk-lib";
2
+ import * as events from "aws-cdk-lib/aws-events";
3
+ import * as eventTargets from "aws-cdk-lib/aws-events-targets";
4
+ import * as iam from "aws-cdk-lib/aws-iam";
5
+ import * as lambda from "aws-cdk-lib/aws-lambda";
6
+ import * as s3 from "aws-cdk-lib/aws-s3";
7
+ import { Construct } from "constructs";
8
+
9
+ export type InboundEmailStackProps = StackProps & {
10
+ storageBucket: s3.IBucket;
11
+ receiveFunctionArn: string;
12
+ };
13
+
14
+ /** EventBridge triggers live in the data stack with the receive Lambda. */
15
+ export class InboundEmailStack extends Construct {
16
+ constructor(scope: Construct, id: string, props: InboundEmailStackProps) {
17
+ super(scope, id);
18
+
19
+ const receiveFunction = lambda.Function.fromFunctionArn(
20
+ this,
21
+ "InboundReceiveFunction",
22
+ props.receiveFunctionArn,
23
+ );
24
+
25
+ const objectCreatedRule = new events.Rule(this, "PapyrusInboundEmailObjectCreated", {
26
+ description: "Process inbound SES MIME objects stored under inbound-email/",
27
+ eventPattern: {
28
+ source: ["aws.s3"],
29
+ detailType: ["Object Created"],
30
+ detail: {
31
+ bucket: { name: [props.storageBucket.bucketName] },
32
+ object: { key: [{ prefix: "inbound-email/" }] },
33
+ },
34
+ },
35
+ targets: [new eventTargets.LambdaFunction(receiveFunction)],
36
+ });
37
+
38
+ // fromFunctionArn does not create invoke permissions; EventBridge needs an explicit grant.
39
+ receiveFunction.addPermission("AllowInboundEmailEventBridgeInvoke", {
40
+ principal: new iam.ServicePrincipal("events.amazonaws.com"),
41
+ sourceArn: objectCreatedRule.ruleArn,
42
+ });
43
+
44
+ const retrySweepRule = new events.Rule(this, "PapyrusInboundEmailRetrySweep", {
45
+ description: "Re-process inbound MIME objects left in inbound-email/ after failures",
46
+ schedule: events.Schedule.rate(Duration.minutes(15)),
47
+ targets: [
48
+ new eventTargets.LambdaFunction(receiveFunction, {
49
+ event: events.RuleTargetInput.fromObject({
50
+ source: "papyrus.inbound-email",
51
+ action: "retry-pending",
52
+ }),
53
+ }),
54
+ ],
55
+ });
56
+ receiveFunction.addPermission("AllowInboundEmailRetrySweepInvoke", {
57
+ principal: new iam.ServicePrincipal("events.amazonaws.com"),
58
+ sourceArn: retrySweepRule.ruleArn,
59
+ });
60
+ }
61
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Per-site backend configuration: the argument of `defineSiteBackend(site)`.
3
+ * Config is passed in; the environment is only a fallback so Papyrus's own
4
+ * `amplify/backend.ts` keeps its current behaviour.
5
+ */
6
+ export type PapyrusAuthConfig = {
7
+ /** OAuth callback/logout URLs for Google sign-in (ignored when Google is disabled). */
8
+ redirectUrls?: string[];
9
+ disableGoogleOAuth?: boolean;
10
+ /** Stable Cognito hosted-UI domain prefix. */
11
+ cognitoDomainPrefix?: string;
12
+ };
13
+
14
+ export type PapyrusSiteBackendConfig = {
15
+ /** Brand id; namespaces account-global resources (S3 Vectors index name). */
16
+ brandId?: string;
17
+ /**
18
+ * Amplify app id whose `main` branch is this site's production pipeline.
19
+ * Owns the account-global resources (SES rule set, backup vault, legacy
20
+ * S3 Vectors index name). Unset for sandbox/scratch/new sites.
21
+ */
22
+ productionAppId?: string;
23
+ auth?: PapyrusAuthConfig;
24
+ features?: {
25
+ consoleResponder?: boolean;
26
+ inboundEmail?: boolean;
27
+ slack?: boolean;
28
+ storageBackups?: boolean;
29
+ };
30
+ };