@agent-native/core 0.141.3 → 0.141.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.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/analysis-workspace/SKILL.md +1 -1
- package/corpus/templates/analytics/actions/gong-calls.ts +56 -18
- package/corpus/templates/analytics/changelog/2026-08-05-analytics-queries-keep-every-table-tenant-scoped.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-05-fixed-sign-in-and-dashboard-requests-failing-when-background.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-05-historical-rollups-stay-complete-while-live-events-arrive.md +6 -0
- package/corpus/templates/analytics/server/db/schema.ts +1 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +2 -0
- package/corpus/templates/analytics/server/lib/emails.ts +51 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-health.ts +8 -12
- package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +9 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +262 -13
- package/corpus/templates/analytics/server/lib/gong.ts +132 -33
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/analytics/server/plugins/auth.ts +21 -2
- package/corpus/templates/analytics/server/plugins/db.ts +11 -3
- package/corpus/templates/analytics/server/plugins/transactional-emails.ts +5 -0
- package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +17 -7
- package/corpus/templates/calendar/server/lib/booking-emails.ts +154 -70
- package/corpus/templates/calendar/server/lib/emails.ts +167 -0
- package/corpus/templates/calendar/server/lib/event-guest-notifications.ts +64 -28
- package/corpus/templates/calendar/server/plugins/transactional-emails.ts +5 -0
- package/corpus/templates/clips/actions/invite-member.ts +39 -16
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +73 -1
- package/corpus/templates/clips/app/i18n/en-US.ts +3 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +10 -25
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +2 -0
- package/corpus/templates/clips/changelog/2026-08-05-activity-is-now-the-default-clips-recording-tab-and-ai-setup.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-05-clips-can-copy-a-linked-email-preview-for-public-recordings.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-05-preparing-recording-now-uses-a-clean-shadow-free-status-card.md +6 -0
- package/corpus/templates/clips/desktop/src/styles.css +0 -3
- package/corpus/templates/clips/server/lib/emails.ts +256 -0
- package/corpus/templates/clips/server/lib/transactional-email-templates.ts +24 -0
- package/corpus/templates/clips/server/plugins/transactional-email-catalog.ts +5 -0
- package/corpus/templates/clips/server/routes/api/thumbnail/[recordingId].get.ts +4 -1
- package/corpus/templates/clips/shared/email-preview.ts +61 -0
- package/corpus/templates/content/server/lib/comment-notifications.ts +57 -26
- package/corpus/templates/content/server/lib/emails.ts +70 -0
- package/corpus/templates/content/server/plugins/transactional-emails.ts +5 -0
- package/corpus/templates/design/actions/import-design-tokens.ts +11 -8
- package/corpus/templates/design/actions/index-design-tokens.ts +13 -6
- package/corpus/templates/design/changelog/2026-08-05-design-token-imports-classify-typography-dimensions-correctly.md +6 -0
- package/corpus/templates/dispatch/app/routes/transactional-email.$appId.$id.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/transactional-email.tsx +4 -0
- package/corpus/templates/dispatch/changelog/2026-08-05-transactional-email-catalogs-and-delivery-activity-are-available-again.md +6 -0
- package/corpus/templates/factory/AGENTS.md +18 -17
- package/corpus/templates/factory/actions/get-factory-automation-health.ts +50 -0
- package/corpus/templates/factory/actions/get-triage-config.ts +8 -0
- package/corpus/templates/factory/actions/save-triage-config.ts +26 -0
- package/corpus/templates/factory/app/i18n/en-US.ts +27 -0
- package/corpus/templates/factory/app/routes/factory.tsx +376 -206
- package/corpus/templates/factory/changelog/2026-08-05-factory-now-keeps-production-automations-running-shows-sched.md +6 -0
- package/corpus/templates/factory/server/db/schema.ts +6 -0
- package/corpus/templates/factory/server/plugins/factory-migrations.ts +10 -0
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +141 -2
- package/corpus/templates/forms/app/pages/FormsListPage.tsx +41 -21
- package/corpus/templates/forms/changelog/2026-08-05-form-links-can-now-be-cmd-clicked-to-open-in-a-new-tab.md +6 -0
- package/corpus/templates/forms/server/lib/emails.ts +52 -0
- package/corpus/templates/forms/server/lib/response-email.ts +2 -0
- package/corpus/templates/forms/server/plugins/transactional-emails.ts +5 -0
- package/corpus/templates/plan/actions/request-plan-access.ts +33 -12
- package/corpus/templates/plan/server/lib/comment-notifications.ts +53 -27
- package/corpus/templates/plan/server/lib/emails.ts +67 -0
- package/corpus/templates/plan/server/plugins/transactional-emails.ts +5 -0
- package/corpus/templates/slides/actions/import-from-url.ts +1 -0
- package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +492 -381
- package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +8 -7
- package/corpus/templates/slides/app/components/layout/Layout.tsx +27 -23
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +313 -0
- package/corpus/templates/slides/app/components/ui/command.tsx +1 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +8 -2
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +305 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +12 -12
- package/corpus/templates/slides/app/pages/Index.tsx +127 -137
- package/corpus/templates/slides/app/root.tsx +8 -0
- package/corpus/templates/slides/changelog/2026-08-05-deck-browsing-keeps-all-and-mine-in-the-header-with-a-tighte.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-05-new-deck-creation-now-flows-through-a-clean-full-screen-refe.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-05-website-imports-now-include-linked-stylesheets-for-more-accu.md +6 -0
- package/corpus/templates/slides/server/lib/comment-notifications.ts +44 -21
- package/corpus/templates/slides/server/lib/emails.ts +40 -0
- package/corpus/templates/slides/server/plugins/transactional-emails.ts +5 -0
- package/dist/brand-kit/index.d.ts +1 -0
- package/dist/brand-kit/index.js +1 -0
- package/dist/brand-kit/tokens.d.ts +91 -0
- package/dist/brand-kit/tokens.js +279 -0
- package/dist/brand-kit/types.d.ts +25 -0
- package/dist/client/AssistantChat.js +7 -7
- package/dist/client/onboarding/FirstRunOnboarding.js +26 -2
- package/dist/client/onboarding/first-run-registry.d.ts +25 -0
- package/dist/client/onboarding/first-run-registry.js +18 -0
- package/dist/client/onboarding/index.d.ts +2 -0
- package/dist/client/onboarding/index.js +1 -0
- package/dist/collab/routes.d.ts +1 -1
- package/dist/deploy/build.d.ts +8 -0
- package/dist/deploy/build.js +92 -3
- package/dist/deploy/workspace-deploy.js +86 -3
- package/dist/email-catalog/actions/list-email-activity.d.ts +5 -0
- package/dist/email-catalog/actions/list-email-activity.js +19 -0
- package/dist/email-catalog/actions/list-email-engagement.d.ts +5 -0
- package/dist/email-catalog/actions/list-email-engagement.js +15 -0
- package/dist/email-catalog/actions/list-email-log.d.ts +7 -0
- package/dist/email-catalog/actions/list-email-log.js +23 -0
- package/dist/email-catalog/actions/list-transactional-emails.d.ts +22 -0
- package/dist/email-catalog/actions/list-transactional-emails.js +67 -0
- package/dist/email-catalog/actions/render-transactional-email-preview.d.ts +9 -0
- package/dist/email-catalog/actions/render-transactional-email-preview.js +25 -0
- package/dist/email-catalog/authorize.d.ts +1 -0
- package/dist/email-catalog/authorize.js +9 -0
- package/dist/email-catalog/log.d.ts +60 -0
- package/dist/email-catalog/log.js +142 -0
- package/dist/email-catalog/provider-metrics.d.ts +34 -0
- package/dist/email-catalog/provider-metrics.js +182 -0
- package/dist/email-catalog/registry.d.ts +78 -0
- package/dist/email-catalog/registry.js +70 -0
- package/dist/email-catalog/schema.d.ts +231 -0
- package/dist/email-catalog/schema.js +53 -0
- package/dist/email-catalog/system-emails.d.ts +13 -0
- package/dist/email-catalog/system-emails.js +67 -0
- package/dist/jobs/index.d.ts +2 -0
- package/dist/jobs/index.js +2 -0
- package/dist/jobs/run-history.js +48 -1
- package/dist/jobs/scheduler-dispatch.d.ts +7 -0
- package/dist/jobs/scheduler-dispatch.js +7 -0
- package/dist/jobs/scheduler-health.d.ts +22 -0
- package/dist/jobs/scheduler-health.js +172 -0
- package/dist/jobs/scheduler.js +67 -2
- package/dist/localization/default-messages.d.ts +45 -0
- package/dist/localization/default-messages.js +45 -0
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +5 -5
- package/dist/org/handlers.js +9 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +10 -10
- package/dist/provider-api/actions/provider-api.d.ts +8 -8
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-discovery.js +22 -0
- package/dist/server/agent-chat/recurring-jobs-runtime.d.ts +7 -1
- package/dist/server/agent-chat/recurring-jobs-runtime.js +12 -0
- package/dist/server/agent-chat-plugin.d.ts +2 -1
- package/dist/server/agent-chat-plugin.js +39 -1
- package/dist/server/better-auth-instance.js +17 -2
- package/dist/server/design-token-utils.d.ts +6 -1
- package/dist/server/design-token-utils.js +252 -59
- package/dist/server/email.d.ts +11 -0
- package/dist/server/email.js +54 -3
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/vite/client.js +20 -14
- package/package.json +2 -1
|
@@ -86,6 +86,29 @@ export type ClipsTransactionalEmailInput =
|
|
|
86
86
|
videoTimestampMs?: number | null;
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
+
export const CLIPS_FIRST_VIEW_EMAIL_ID = "clips.first-view";
|
|
90
|
+
export const CLIPS_UNVIEWED_REMINDER_EMAIL_ID = "clips.unviewed-reminder";
|
|
91
|
+
export const CLIPS_FIRST_AGENT_VIEW_EMAIL_ID = "clips.first-agent-view";
|
|
92
|
+
export const CLIPS_FIRST_IMPORT_EMAIL_ID = "clips.first-import";
|
|
93
|
+
export const CLIPS_MONTHLY_RECAP_EMAIL_ID = "clips.monthly-recap";
|
|
94
|
+
export const CLIPS_TWO_CLIPS_EMAIL_ID = "clips.two-clips";
|
|
95
|
+
export const CLIPS_ACTIVITY_COMMENT_EMAIL_ID = "clips.activity-comment";
|
|
96
|
+
export const CLIPS_ACTIVITY_REACTION_EMAIL_ID = "clips.activity-reaction";
|
|
97
|
+
|
|
98
|
+
export const CLIPS_EMAIL_ID_BY_KIND: Record<
|
|
99
|
+
ClipsTransactionalEmailInput["kind"],
|
|
100
|
+
string
|
|
101
|
+
> = {
|
|
102
|
+
"first-view": CLIPS_FIRST_VIEW_EMAIL_ID,
|
|
103
|
+
"unviewed-reminder": CLIPS_UNVIEWED_REMINDER_EMAIL_ID,
|
|
104
|
+
"first-agent-view": CLIPS_FIRST_AGENT_VIEW_EMAIL_ID,
|
|
105
|
+
"first-import": CLIPS_FIRST_IMPORT_EMAIL_ID,
|
|
106
|
+
"monthly-recap": CLIPS_MONTHLY_RECAP_EMAIL_ID,
|
|
107
|
+
"two-clips": CLIPS_TWO_CLIPS_EMAIL_ID,
|
|
108
|
+
"activity-comment": CLIPS_ACTIVITY_COMMENT_EMAIL_ID,
|
|
109
|
+
"activity-reaction": CLIPS_ACTIVITY_REACTION_EMAIL_ID,
|
|
110
|
+
};
|
|
111
|
+
|
|
89
112
|
export interface RecapTopClipInput {
|
|
90
113
|
recordingId: string;
|
|
91
114
|
title?: string | null;
|
|
@@ -613,6 +636,7 @@ export async function sendClipsTransactionalEmail(
|
|
|
613
636
|
? (validReplyTo(input.senderEmail) ?? FRIENDLY_REPLY_TO)
|
|
614
637
|
: FRIENDLY_REPLY_TO,
|
|
615
638
|
timeoutMs: EMAIL_SEND_TIMEOUT_MS,
|
|
639
|
+
templateId: CLIPS_EMAIL_ID_BY_KIND[input.kind],
|
|
616
640
|
});
|
|
617
641
|
}
|
|
618
642
|
|
|
@@ -269,6 +269,7 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
269
269
|
const query = getQuery(event) as {
|
|
270
270
|
password?: unknown;
|
|
271
271
|
t?: unknown;
|
|
272
|
+
animated?: unknown;
|
|
272
273
|
};
|
|
273
274
|
if (recording.password && loaded.role !== "owner") {
|
|
274
275
|
const queryToken = typeof query.t === "string" ? query.t : "";
|
|
@@ -287,7 +288,9 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
287
288
|
}
|
|
288
289
|
|
|
289
290
|
const sourceUrl =
|
|
290
|
-
|
|
291
|
+
query.animated === "1"
|
|
292
|
+
? recording.animatedThumbnailUrl || recording.thumbnailUrl
|
|
293
|
+
: recording.thumbnailUrl || recording.animatedThumbnailUrl;
|
|
291
294
|
if (!sourceUrl) {
|
|
292
295
|
setResponseStatus(event, 404);
|
|
293
296
|
return { error: "Thumbnail not found" };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export interface EmailPreviewMarkup {
|
|
2
|
+
html: string;
|
|
3
|
+
plainText: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface EmailPreviewOptions {
|
|
7
|
+
title: string;
|
|
8
|
+
shareUrl: string;
|
|
9
|
+
thumbnailUrl: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const HTML_ESCAPES: Record<string, string> = {
|
|
13
|
+
"&": "&",
|
|
14
|
+
"<": "<",
|
|
15
|
+
">": ">",
|
|
16
|
+
'"': """,
|
|
17
|
+
"'": "'",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function escapeHtml(value: string): string {
|
|
21
|
+
return value.replace(/[&<>"']/g, (character) => HTML_ESCAPES[character]);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function requireHttpUrl(value: string, field: string): string {
|
|
25
|
+
let parsed: URL;
|
|
26
|
+
try {
|
|
27
|
+
parsed = new URL(value);
|
|
28
|
+
} catch {
|
|
29
|
+
throw new Error(`${field} must be an absolute HTTP URL`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
33
|
+
throw new Error(`${field} must be an absolute HTTP URL`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return parsed.toString();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function buildEmailPreviewMarkup(
|
|
40
|
+
options: EmailPreviewOptions,
|
|
41
|
+
): EmailPreviewMarkup {
|
|
42
|
+
const title = options.title.trim();
|
|
43
|
+
if (!title) throw new Error("Email preview title cannot be empty");
|
|
44
|
+
|
|
45
|
+
const shareUrl = requireHttpUrl(options.shareUrl, "shareUrl");
|
|
46
|
+
const thumbnailUrl = requireHttpUrl(options.thumbnailUrl, "thumbnailUrl");
|
|
47
|
+
const escapedTitle = escapeHtml(title);
|
|
48
|
+
const escapedShareUrl = escapeHtml(shareUrl);
|
|
49
|
+
const escapedThumbnailUrl = escapeHtml(thumbnailUrl);
|
|
50
|
+
const escapedAlt = escapeHtml(`Play ${title}`);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
plainText: `${title}\n${shareUrl}`,
|
|
54
|
+
html: [
|
|
55
|
+
'<div style="max-width:640px">',
|
|
56
|
+
`<p style="margin:0 0 12px;font-size:16px;font-weight:600"><a href="${escapedShareUrl}" style="text-decoration:none">${escapedTitle}</a></p>`,
|
|
57
|
+
`<a href="${escapedShareUrl}" style="display:block;text-decoration:none"><img src="${escapedThumbnailUrl}" alt="${escapedAlt}" width="640" style="display:block;width:100%;max-width:640px;height:auto;border:0" /></a>`,
|
|
58
|
+
"</div>",
|
|
59
|
+
].join(""),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -21,6 +21,10 @@ import { and, eq } from "drizzle-orm";
|
|
|
21
21
|
|
|
22
22
|
import { CONTENT_USER_PREFS_KEY } from "../../shared/content-user-prefs.js";
|
|
23
23
|
import { getDb, schema } from "../db/index.js";
|
|
24
|
+
import {
|
|
25
|
+
CONTENT_DOCUMENT_COMMENT_EMAIL_ID,
|
|
26
|
+
CONTENT_DOCUMENT_MENTION_EMAIL_ID,
|
|
27
|
+
} from "./emails.js";
|
|
24
28
|
|
|
25
29
|
export type DocumentCommentNotificationResult = ActivityNotificationResult;
|
|
26
30
|
|
|
@@ -70,6 +74,48 @@ export interface DocumentCommentNotificationInput {
|
|
|
70
74
|
isReply: boolean;
|
|
71
75
|
}
|
|
72
76
|
|
|
77
|
+
export function renderDocumentCommentEmail({
|
|
78
|
+
actor,
|
|
79
|
+
title,
|
|
80
|
+
url,
|
|
81
|
+
content,
|
|
82
|
+
isReply,
|
|
83
|
+
wasMentioned,
|
|
84
|
+
}: {
|
|
85
|
+
actor: string;
|
|
86
|
+
title: string;
|
|
87
|
+
url: string;
|
|
88
|
+
content: string;
|
|
89
|
+
isReply: boolean;
|
|
90
|
+
wasMentioned: boolean;
|
|
91
|
+
}) {
|
|
92
|
+
const lead = wasMentioned
|
|
93
|
+
? `${emailStrong(actor)} mentioned you in a comment on ${emailStrong(title)}.`
|
|
94
|
+
: isReply
|
|
95
|
+
? `${emailStrong(actor)} replied in a comment thread on ${emailStrong(title)}.`
|
|
96
|
+
: `${emailStrong(actor)} commented on ${emailStrong(title)}.`;
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
subject: wasMentioned
|
|
100
|
+
? `${actor} mentioned you on "${title}"`
|
|
101
|
+
: isReply
|
|
102
|
+
? `${actor} replied to a comment on "${title}"`
|
|
103
|
+
: `${actor} commented on "${title}"`,
|
|
104
|
+
...renderEmail({
|
|
105
|
+
preheader: `${actor} commented on ${title}.`,
|
|
106
|
+
heading: wasMentioned
|
|
107
|
+
? "You were mentioned"
|
|
108
|
+
: isReply
|
|
109
|
+
? "New reply on your document"
|
|
110
|
+
: "New comment",
|
|
111
|
+
paragraphs: [lead, `"${excerpt(content)}"`],
|
|
112
|
+
cta: { label: "Open document", url },
|
|
113
|
+
footer:
|
|
114
|
+
"You received this because you own, were mentioned in, or participated in this thread. Turn these off in Documents settings.",
|
|
115
|
+
}),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
73
119
|
export async function notifyDocumentComment(
|
|
74
120
|
input: DocumentCommentNotificationInput,
|
|
75
121
|
): Promise<DocumentCommentNotificationResult> {
|
|
@@ -112,34 +158,19 @@ async function deliverDocumentCommentEmails(
|
|
|
112
158
|
logLabel: LOG_LABEL,
|
|
113
159
|
send: async (to) => {
|
|
114
160
|
const wasMentioned = mentioned.has(to);
|
|
115
|
-
const lead = wasMentioned
|
|
116
|
-
? `${emailStrong(actor)} mentioned you in a comment on ${emailStrong(title)}.`
|
|
117
|
-
: input.isReply
|
|
118
|
-
? `${emailStrong(actor)} replied in a comment thread on ${emailStrong(title)}.`
|
|
119
|
-
: `${emailStrong(actor)} commented on ${emailStrong(title)}.`;
|
|
120
|
-
|
|
121
|
-
const { html, text } = renderEmail({
|
|
122
|
-
preheader: `${actor} commented on ${title}.`,
|
|
123
|
-
heading: wasMentioned
|
|
124
|
-
? "You were mentioned"
|
|
125
|
-
: input.isReply
|
|
126
|
-
? "New reply on your document"
|
|
127
|
-
: "New comment",
|
|
128
|
-
paragraphs: [lead, `"${excerpt(input.content)}"`],
|
|
129
|
-
cta: { label: "Open document", url },
|
|
130
|
-
footer:
|
|
131
|
-
"You received this because you own, were mentioned in, or participated in this thread. Turn these off in Documents settings.",
|
|
132
|
-
});
|
|
133
|
-
|
|
134
161
|
await sendEmail({
|
|
162
|
+
...renderDocumentCommentEmail({
|
|
163
|
+
actor,
|
|
164
|
+
title,
|
|
165
|
+
url,
|
|
166
|
+
content: input.content,
|
|
167
|
+
isReply: input.isReply,
|
|
168
|
+
wasMentioned,
|
|
169
|
+
}),
|
|
135
170
|
to,
|
|
136
|
-
|
|
137
|
-
?
|
|
138
|
-
:
|
|
139
|
-
? `${actor} replied to a comment on "${title}"`
|
|
140
|
-
: `${actor} commented on "${title}"`,
|
|
141
|
-
html,
|
|
142
|
-
text,
|
|
171
|
+
templateId: wasMentioned
|
|
172
|
+
? CONTENT_DOCUMENT_MENTION_EMAIL_ID
|
|
173
|
+
: CONTENT_DOCUMENT_COMMENT_EMAIL_ID,
|
|
143
174
|
});
|
|
144
175
|
},
|
|
145
176
|
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalog entries for the transactional emails Documents sends.
|
|
3
|
+
*
|
|
4
|
+
* Registered from `server/plugins/transactional-emails.ts` so Dispatch can list
|
|
5
|
+
* and preview them without the app having sent anything yet.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { defineTransactionalEmail } from "@agent-native/core/email-catalog";
|
|
9
|
+
|
|
10
|
+
import { renderDocumentCommentEmail } from "./comment-notifications.js";
|
|
11
|
+
|
|
12
|
+
/** Obviously-fake sample data — these render in a preview pane, never send. */
|
|
13
|
+
const SAMPLE_TITLE = "Q3 launch brief";
|
|
14
|
+
const SAMPLE_URL = "https://example.com/page/doc_sample";
|
|
15
|
+
const SAMPLE_COMMENT =
|
|
16
|
+
"Can we tighten the second paragraph? It repeats the intro.";
|
|
17
|
+
|
|
18
|
+
export const CONTENT_DOCUMENT_COMMENT_EMAIL_ID = "content.document-comment";
|
|
19
|
+
export const CONTENT_DOCUMENT_MENTION_EMAIL_ID = "content.document-mention";
|
|
20
|
+
|
|
21
|
+
let registered = false;
|
|
22
|
+
|
|
23
|
+
export function registerContentEmails(): void {
|
|
24
|
+
if (registered) return;
|
|
25
|
+
registered = true;
|
|
26
|
+
|
|
27
|
+
defineTransactionalEmail({
|
|
28
|
+
id: CONTENT_DOCUMENT_COMMENT_EMAIL_ID,
|
|
29
|
+
name: "Document comment",
|
|
30
|
+
trigger:
|
|
31
|
+
"Someone posts a comment or a thread reply on a document. Sent to recipients who were not mentioned in it; the copy and subject differ slightly for a reply.",
|
|
32
|
+
recipientLabel: "Owner and thread authors",
|
|
33
|
+
recipient:
|
|
34
|
+
"The document owner, plus every prior author in the thread when the new comment is a reply. The list is re-checked against the document's live ACL and filtered by each user's `emailNotifications` preference; the comment's own author never receives it.",
|
|
35
|
+
senderLabel: "Default sender",
|
|
36
|
+
sender:
|
|
37
|
+
"The configured default sender. This call site sets no `from`, `fromName`, `replyTo`, or `appSender`.",
|
|
38
|
+
preview: () =>
|
|
39
|
+
renderDocumentCommentEmail({
|
|
40
|
+
actor: "Sam Rivera",
|
|
41
|
+
title: SAMPLE_TITLE,
|
|
42
|
+
url: SAMPLE_URL,
|
|
43
|
+
content: SAMPLE_COMMENT,
|
|
44
|
+
isReply: false,
|
|
45
|
+
wasMentioned: false,
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
defineTransactionalEmail({
|
|
50
|
+
id: CONTENT_DOCUMENT_MENTION_EMAIL_ID,
|
|
51
|
+
name: "Document mention",
|
|
52
|
+
trigger:
|
|
53
|
+
"A comment or reply mentions someone by email. Sent instead of the plain comment notification to each mentioned recipient.",
|
|
54
|
+
recipientLabel: "Mentioned users",
|
|
55
|
+
recipient:
|
|
56
|
+
"The mentioned addresses supplied by the caller, re-checked against the document's live ACL and filtered by each user's `emailNotifications` preference.",
|
|
57
|
+
senderLabel: "Default sender",
|
|
58
|
+
sender:
|
|
59
|
+
"The configured default sender. This call site sets no `from`, `fromName`, `replyTo`, or `appSender`.",
|
|
60
|
+
preview: () =>
|
|
61
|
+
renderDocumentCommentEmail({
|
|
62
|
+
actor: "Sam Rivera",
|
|
63
|
+
title: SAMPLE_TITLE,
|
|
64
|
+
url: SAMPLE_URL,
|
|
65
|
+
content: SAMPLE_COMMENT,
|
|
66
|
+
isReply: false,
|
|
67
|
+
wasMentioned: true,
|
|
68
|
+
}),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
@@ -118,20 +118,23 @@ function designDeepLink(designId: string): string {
|
|
|
118
118
|
|
|
119
119
|
function classifyVar(name: string, value: string): ImportedTokenType {
|
|
120
120
|
const n = name.toLowerCase();
|
|
121
|
+
if (isColorValue(value)) return "color";
|
|
122
|
+
if (/radius|rounded/i.test(n)) return "radius";
|
|
123
|
+
if (/font|size|leading|tracking|weight|heading|body|type/i.test(n)) {
|
|
124
|
+
return "typography";
|
|
125
|
+
}
|
|
126
|
+
if (/spacing|gap|padding|margin|space/i.test(n)) return "spacing";
|
|
127
|
+
if (/shadow|blur|drop/i.test(n)) return "shadow";
|
|
121
128
|
if (
|
|
122
129
|
/color|bg|background|text|border|accent|primary|secondary|surface|muted|foreground|fill|stroke/i.test(
|
|
123
130
|
n,
|
|
124
|
-
)
|
|
125
|
-
|
|
131
|
+
) &&
|
|
132
|
+
!/^-?\d*\.?\d+(px|rem|em|ex|ch|%|vh|vw|vmin|vmax|pt|pc|cm|mm|in|s|ms|deg|fr)?$/i.test(
|
|
133
|
+
value.trim(),
|
|
134
|
+
)
|
|
126
135
|
) {
|
|
127
136
|
return "color";
|
|
128
137
|
}
|
|
129
|
-
if (/font|size|leading|tracking|weight|heading|body|type/i.test(n)) {
|
|
130
|
-
return "typography";
|
|
131
|
-
}
|
|
132
|
-
if (/radius|rounded/i.test(n)) return "radius";
|
|
133
|
-
if (/spacing|gap|padding|margin|space/i.test(n)) return "spacing";
|
|
134
|
-
if (/shadow|blur|drop/i.test(n)) return "shadow";
|
|
135
138
|
return "other";
|
|
136
139
|
}
|
|
137
140
|
|
|
@@ -22,25 +22,32 @@ function classifyVar(
|
|
|
22
22
|
): "color" | "typography" | "spacing" | "radius" | "shadow" | "other" {
|
|
23
23
|
const n = name.toLowerCase();
|
|
24
24
|
if (
|
|
25
|
-
/color|bg|background|text|border|accent|primary|secondary|surface|muted|foreground|fill|stroke/i.test(
|
|
26
|
-
n,
|
|
27
|
-
) ||
|
|
28
25
|
/^(#[0-9a-fA-F]{3,8}|rgba?\(|hsla?\(|oklch\(|color\()/.test(value.trim())
|
|
29
26
|
) {
|
|
30
27
|
return "color";
|
|
31
28
|
}
|
|
32
|
-
if (/font|size|leading|tracking|weight|heading|body|type/i.test(n)) {
|
|
33
|
-
return "typography";
|
|
34
|
-
}
|
|
35
29
|
if (/radius|rounded/i.test(n)) {
|
|
36
30
|
return "radius";
|
|
37
31
|
}
|
|
32
|
+
if (/font|size|leading|tracking|weight|heading|body|type/i.test(n)) {
|
|
33
|
+
return "typography";
|
|
34
|
+
}
|
|
38
35
|
if (/spacing|gap|padding|margin|space/i.test(n)) {
|
|
39
36
|
return "spacing";
|
|
40
37
|
}
|
|
41
38
|
if (/shadow|blur|drop/i.test(n)) {
|
|
42
39
|
return "shadow";
|
|
43
40
|
}
|
|
41
|
+
if (
|
|
42
|
+
/color|bg|background|text|border|accent|primary|secondary|surface|muted|foreground|fill|stroke/i.test(
|
|
43
|
+
n,
|
|
44
|
+
) &&
|
|
45
|
+
!/^-?\d*\.?\d+(px|rem|em|ex|ch|%|vh|vw|vmin|vmax|pt|pc|cm|mm|in|s|ms|deg|fr)?$/i.test(
|
|
46
|
+
value.trim(),
|
|
47
|
+
)
|
|
48
|
+
) {
|
|
49
|
+
return "color";
|
|
50
|
+
}
|
|
44
51
|
return "other";
|
|
45
52
|
}
|
|
46
53
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Factory is the visual workspace for building agent factories from incoming work
|
|
4
4
|
to governed delivery. The map is the source of truth; Dispatch owns the shared
|
|
5
5
|
inbox and routing, while Factory owns graph versions, queue state, rules,
|
|
6
|
-
decisions, feedback, agent runs, and provider
|
|
6
|
+
decisions, feedback, agent runs, and provider audit records.
|
|
7
7
|
|
|
8
8
|
Before building common workspace or agent UI, read `agent-native-toolkit`; use
|
|
9
9
|
`customizing-agent-native` for the configure → compose → eject → propose
|
|
@@ -12,7 +12,7 @@ ladder.
|
|
|
12
12
|
## Core rules
|
|
13
13
|
|
|
14
14
|
- Keep app state in SQL via Drizzle and scope every read/write by org and
|
|
15
|
-
|
|
15
|
+
member. Use actions as the UI, agent, CLI, MCP, and A2A surface.
|
|
16
16
|
- Keep migrations additive and portable. These tables intentionally use explicit
|
|
17
17
|
`ownerEmail`/`orgId` columns for org-visible data, not `ownableColumns()`;
|
|
18
18
|
do not call `accessFilter` on them without adding deliberate visibility data.
|
|
@@ -31,23 +31,23 @@ ladder.
|
|
|
31
31
|
automations add 👀 and tag `@builderio` in the source thread; GitHub and Sentry
|
|
32
32
|
clear bugs use the Builder run API. Clips, Design, and Content are always
|
|
33
33
|
owner-managed and never enter autonomous dispatch or PR governance.
|
|
34
|
-
- PR governance requires verified
|
|
35
|
-
|
|
34
|
+
- PR governance requires verified BuilderIO membership, a clear bug, passing
|
|
35
|
+
CI, and handled review feedback. Product or UX implications stay
|
|
36
36
|
manual. Auto-merge additionally requires a verified Factory Builder run.
|
|
37
37
|
- Reuse the existing ai-services GitHub read and Builder execution APIs. Do not
|
|
38
38
|
duplicate GitHub installation/webhook infrastructure in this template.
|
|
39
39
|
- Do not add CRUD routes under `server/routes/api/`; actions are the domain
|
|
40
40
|
surface. Provider callbacks are the only exception and must verify signatures.
|
|
41
|
-
- Factory graph edits create immutable blueprint versions. AI proposes a
|
|
42
|
-
|
|
41
|
+
- Factory graph edits create immutable blueprint versions. AI proposes a graph
|
|
42
|
+
with `source=ai`; a person reviews and publishes it through
|
|
43
43
|
the same action surface as manual edits.
|
|
44
44
|
|
|
45
45
|
## Application state
|
|
46
46
|
|
|
47
|
-
- `navigation.view`: `factory` when the
|
|
47
|
+
- `navigation.view`: `factory` when the workspace is open.
|
|
48
48
|
- `navigation.factoryId`: selected Factory id when present.
|
|
49
49
|
- `navigation.factoryTab`: `map` | `inbox` | `rules` | `automations` | `settings`.
|
|
50
|
-
- `navigation.factoryNodeId` / `navigation.factoryEdgeId`: selected graph
|
|
50
|
+
- `navigation.factoryNodeId` / `navigation.factoryEdgeId`: selected graph item.
|
|
51
51
|
- A selected graph node or edge is part of `navigation` context. Read
|
|
52
52
|
`view-screen` before answering why a route exists or changing the selected
|
|
53
53
|
Factory.
|
|
@@ -56,18 +56,19 @@ ladder.
|
|
|
56
56
|
|
|
57
57
|
| Action | Purpose |
|
|
58
58
|
| --- | --- |
|
|
59
|
-
| `list-triage-items` / `get-triage-item` | Inspect
|
|
59
|
+
| `list-triage-items` / `get-triage-item` | Inspect queue and evidence. |
|
|
60
60
|
| `poll-slack-channel` | Observe Slack history; never writes to Slack. |
|
|
61
61
|
| `get-slack-feedback-context` | Read the bounded full Slack thread before classification. |
|
|
62
62
|
| `poll-github-sources` / `poll-sentry-errors` | Observe bounded GitHub and Sentry source queues. |
|
|
63
63
|
| `ingest-github-observation` | Store read-only PR evidence. |
|
|
64
64
|
| `list-triage-rules` / `save-triage-rule` | Tune prompt rules and guards. |
|
|
65
|
-
| `evaluate-triage-item` | Append a
|
|
65
|
+
| `evaluate-triage-item` | Append a decision. |
|
|
66
66
|
| `record-triage-feedback` | Capture human correction for learning. |
|
|
67
67
|
| `approve-factory-item` | Explicitly authorize one bounded run. |
|
|
68
68
|
| `start-builder-for-item` | Govern clear-bug dispatch; Slack tags Builder in-thread, other sources use Builder API. |
|
|
69
69
|
| `govern-agent-native-pull-request` | Apply CI, review, internal-author, product, and owner gates to PR approval/merge. |
|
|
70
|
-
| `list-factory-automations` / `save-factory-automation` / `run-factory-automation` | Inspect and edit
|
|
70
|
+
| `list-factory-automations` / `save-factory-automation` / `run-factory-automation` | Inspect and edit org-owned Factory prompts, models, schedules, and runs. |
|
|
71
|
+
| `get-factory-automation-health` | Inspect the durable scheduler heartbeat and last scheduler error when runs appear stale. |
|
|
71
72
|
| `suggest-factory-rules` | Mine feedback and fast approvals into proposals. |
|
|
72
73
|
| `reconcile-triage-run` | Persist callback/provider reconciliation. |
|
|
73
74
|
| `list-factories` / `get-factory-graph` | Inspect Factory definitions, graph versions, and live evidence metrics. |
|
|
@@ -77,7 +78,7 @@ ladder.
|
|
|
77
78
|
Rules start in shadow mode; hard guards always apply. Editable organization
|
|
78
79
|
automations execute stored prompts; every external mutation needs a durable run,
|
|
79
80
|
idempotency key, and provider confirmation. The legacy observer is disabled
|
|
80
|
-
|
|
81
|
+
once organization automations are seeded.
|
|
81
82
|
|
|
82
83
|
Use the visual editor for direct blueprint changes. Use the agent chat for
|
|
83
84
|
natural language design, explanations, and proposals; it must preserve a
|
|
@@ -87,14 +88,14 @@ actions, never through graph JSON.
|
|
|
87
88
|
|
|
88
89
|
## Scheduler identity
|
|
89
90
|
|
|
90
|
-
`WORKSPACE_OWNER_EMAIL` is read only at startup to find the deployment
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
`WORKSPACE_OWNER_EMAIL` is read only at startup to find the deployment org and
|
|
92
|
+
stamp seeded automation `createdBy`; it is never caller identity and must not
|
|
93
|
+
enter request authorization or credential resolution.
|
|
93
94
|
|
|
94
95
|
## Hosting
|
|
95
96
|
|
|
96
97
|
Production needs `DATABASE_URL`, `WORKSPACE_OWNER_EMAIL`, and
|
|
97
|
-
`FACTORY_PUBLIC_URL`. Builder execution additionally needs the service URL
|
|
98
|
-
project ID
|
|
98
|
+
`FACTORY_PUBLIC_URL`. Builder execution additionally needs the service URL,
|
|
99
|
+
project ID, and workspace-resolved Builder credentials. GitHub and Sentry
|
|
99
100
|
polling use workspace-resolved provider credentials. Provider callbacks and
|
|
100
101
|
external writes must remain auditable and fail closed when evidence is partial.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core/action";
|
|
2
|
+
import { getAutomationSchedulerHealth } from "@agent-native/core/jobs";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
requireWorkspaceMember,
|
|
7
|
+
workspaceMemberIdentityFromContext,
|
|
8
|
+
} from "../server/lib/require-workspace-member.js";
|
|
9
|
+
|
|
10
|
+
const STALE_AFTER_MS = 3 * 60_000;
|
|
11
|
+
|
|
12
|
+
export default defineAction({
|
|
13
|
+
description:
|
|
14
|
+
"Inspect the Factory recurring-automation scheduler heartbeat and last error. Use this when an automation appears not to be running.",
|
|
15
|
+
schema: z.object({}),
|
|
16
|
+
http: { method: "GET" },
|
|
17
|
+
readOnly: true,
|
|
18
|
+
run: async (_, context) => {
|
|
19
|
+
const { orgId } = await requireWorkspaceMember(
|
|
20
|
+
workspaceMemberIdentityFromContext(context),
|
|
21
|
+
);
|
|
22
|
+
const health = await getAutomationSchedulerHealth({
|
|
23
|
+
appId: "factory",
|
|
24
|
+
orgId,
|
|
25
|
+
});
|
|
26
|
+
if (!health?.lastCheckedAt) {
|
|
27
|
+
return {
|
|
28
|
+
status: "no-data" as const,
|
|
29
|
+
lastCheckedAt: null,
|
|
30
|
+
lastDispatchedAt: health?.lastDispatchedAt ?? null,
|
|
31
|
+
lastError: health?.lastError ?? null,
|
|
32
|
+
runtime: health?.runtime ?? null,
|
|
33
|
+
staleAfterMs: STALE_AFTER_MS,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const stale = Date.now() - health.lastCheckedAt > STALE_AFTER_MS;
|
|
37
|
+
return {
|
|
38
|
+
status: health.lastError
|
|
39
|
+
? ("error" as const)
|
|
40
|
+
: stale
|
|
41
|
+
? ("stale" as const)
|
|
42
|
+
: ("healthy" as const),
|
|
43
|
+
lastCheckedAt: health.lastCheckedAt,
|
|
44
|
+
lastDispatchedAt: health.lastDispatchedAt,
|
|
45
|
+
lastError: health.lastError,
|
|
46
|
+
runtime: health.runtime,
|
|
47
|
+
staleAfterMs: STALE_AFTER_MS,
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
|
+
import { getEmailReadiness } from "@agent-native/core/server";
|
|
2
3
|
import { and, eq } from "drizzle-orm";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
|
|
@@ -19,6 +20,7 @@ export default defineAction({
|
|
|
19
20
|
const { orgId } = await requireWorkspaceMember(
|
|
20
21
|
workspaceMemberIdentityFromContext(context),
|
|
21
22
|
);
|
|
23
|
+
const emailReadiness = await getEmailReadiness();
|
|
22
24
|
const row = (
|
|
23
25
|
await getDb()
|
|
24
26
|
.select()
|
|
@@ -41,6 +43,9 @@ export default defineAction({
|
|
|
41
43
|
sentryProjectSlug: null,
|
|
42
44
|
sentryEnvironment: null,
|
|
43
45
|
lastSentrySeenAt: null,
|
|
46
|
+
automationFailureAlertsEnabled: true,
|
|
47
|
+
automationFailureAlertEmail: null,
|
|
48
|
+
emailReadiness,
|
|
44
49
|
};
|
|
45
50
|
}
|
|
46
51
|
return {
|
|
@@ -48,6 +53,9 @@ export default defineAction({
|
|
|
48
53
|
pollingEnabled: row.pollingEnabled === 1,
|
|
49
54
|
githubPollingEnabled: row.githubPollingEnabled === 1,
|
|
50
55
|
sentryPollingEnabled: row.sentryPollingEnabled === 1,
|
|
56
|
+
automationFailureAlertsEnabled: row.automationFailureAlertsEnabled === 1,
|
|
57
|
+
automationFailureAlertEmail: row.automationFailureAlertEmail,
|
|
58
|
+
emailReadiness,
|
|
51
59
|
};
|
|
52
60
|
},
|
|
53
61
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
|
+
import { and, eq } from "drizzle-orm";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
|
|
4
5
|
import { getDb } from "../server/db/index.js";
|
|
@@ -24,6 +25,8 @@ export default defineAction({
|
|
|
24
25
|
sentryProjectSlug: z.string().trim().max(200).optional(),
|
|
25
26
|
sentryEnvironment: z.string().trim().max(200).optional(),
|
|
26
27
|
repository: z.string().trim().max(256).optional(),
|
|
28
|
+
automationFailureAlertsEnabled: z.boolean().default(true),
|
|
29
|
+
automationFailureAlertEmail: z.string().trim().email().optional(),
|
|
27
30
|
}),
|
|
28
31
|
http: { method: "POST" },
|
|
29
32
|
run: async (
|
|
@@ -38,6 +41,8 @@ export default defineAction({
|
|
|
38
41
|
sentryProjectSlug,
|
|
39
42
|
sentryEnvironment,
|
|
40
43
|
repository,
|
|
44
|
+
automationFailureAlertsEnabled,
|
|
45
|
+
automationFailureAlertEmail,
|
|
41
46
|
},
|
|
42
47
|
context,
|
|
43
48
|
) => {
|
|
@@ -46,6 +51,19 @@ export default defineAction({
|
|
|
46
51
|
);
|
|
47
52
|
const now = new Date().toISOString();
|
|
48
53
|
const db = getDb();
|
|
54
|
+
const existing = (
|
|
55
|
+
await db
|
|
56
|
+
.select({
|
|
57
|
+
automationFailureAlertEmail: triageConfig.automationFailureAlertEmail,
|
|
58
|
+
})
|
|
59
|
+
.from(triageConfig)
|
|
60
|
+
.where(and(eq(triageConfig.id, orgId), eq(triageConfig.orgId, orgId)))
|
|
61
|
+
.limit(1)
|
|
62
|
+
)[0];
|
|
63
|
+
const persistedAutomationFailureAlertEmail =
|
|
64
|
+
automationFailureAlertEmail ??
|
|
65
|
+
existing?.automationFailureAlertEmail ??
|
|
66
|
+
null;
|
|
49
67
|
await db
|
|
50
68
|
.insert(triageConfig)
|
|
51
69
|
.values({
|
|
@@ -60,6 +78,8 @@ export default defineAction({
|
|
|
60
78
|
sentryProjectSlug: sentryProjectSlug ?? null,
|
|
61
79
|
sentryEnvironment: sentryEnvironment ?? null,
|
|
62
80
|
repository: repository ?? null,
|
|
81
|
+
automationFailureAlertsEnabled: automationFailureAlertsEnabled ? 1 : 0,
|
|
82
|
+
automationFailureAlertEmail: persistedAutomationFailureAlertEmail,
|
|
63
83
|
createdAt: now,
|
|
64
84
|
updatedAt: now,
|
|
65
85
|
ownerEmail: userEmail,
|
|
@@ -78,6 +98,10 @@ export default defineAction({
|
|
|
78
98
|
sentryProjectSlug: sentryProjectSlug ?? null,
|
|
79
99
|
sentryEnvironment: sentryEnvironment ?? null,
|
|
80
100
|
repository: repository ?? null,
|
|
101
|
+
automationFailureAlertsEnabled: automationFailureAlertsEnabled
|
|
102
|
+
? 1
|
|
103
|
+
: 0,
|
|
104
|
+
automationFailureAlertEmail: persistedAutomationFailureAlertEmail,
|
|
81
105
|
updatedAt: now,
|
|
82
106
|
ownerEmail: userEmail,
|
|
83
107
|
},
|
|
@@ -88,6 +112,8 @@ export default defineAction({
|
|
|
88
112
|
githubPollingEnabled,
|
|
89
113
|
sentryPollingEnabled,
|
|
90
114
|
slackChannelId: slackChannelId ?? null,
|
|
115
|
+
automationFailureAlertsEnabled,
|
|
116
|
+
automationFailureAlertEmail: persistedAutomationFailureAlertEmail,
|
|
91
117
|
};
|
|
92
118
|
},
|
|
93
119
|
});
|