@agent-native/core 0.139.0 → 0.141.0
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/actions/query-agent-native-analytics.ts +1 -1
- package/corpus/templates/analytics/changelog/2026-08-05-historical-analytics-rollups-stay-complete-and-atomic.md +6 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +0 -2
- package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +11 -6
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +8 -18
- package/corpus/templates/analytics/server/plugins/db.ts +174 -0
- package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +38 -38
- package/corpus/templates/calendar/changelog/2026-08-05-out-of-office-blocks-now-open-from-their-scheduled-time-rang.md +6 -0
- package/corpus/templates/calendar/server/lib/booking-emails.ts +70 -154
- package/corpus/templates/calendar/server/lib/event-guest-notifications.ts +28 -64
- package/corpus/templates/clips/actions/get-recording-player-data.ts +1 -0
- package/corpus/templates/clips/actions/invite-member.ts +16 -39
- package/corpus/templates/clips/app/components/player/comments-panel.tsx +2 -3
- package/corpus/templates/clips/app/components/player/reaction-emojis.ts +10 -0
- package/corpus/templates/clips/app/components/player/reactions-tray.tsx +2 -2
- package/corpus/templates/clips/app/components/player/video-player.tsx +20 -6
- package/corpus/templates/clips/app/routes/embed.$shareId.tsx +33 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +5 -1
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +27 -1
- package/corpus/templates/clips/changelog/2026-08-05-comment-and-video-reaction-pickers-now-include-eyes-and-mind.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-05-fresh-recordings-now-open-with-the-complete-video.md +6 -0
- package/corpus/templates/clips/server/lib/transactional-email-templates.ts +0 -24
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +1 -0
- package/corpus/templates/content/server/lib/comment-notifications.ts +26 -57
- package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +16 -3
- package/corpus/templates/forms/server/lib/response-email.ts +0 -2
- package/corpus/templates/plan/actions/request-plan-access.ts +12 -33
- package/corpus/templates/plan/server/lib/comment-notifications.ts +27 -53
- package/corpus/templates/slides/server/lib/comment-notifications.ts +21 -44
- package/dist/a2a/handlers.js +9 -10
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.js +8 -8
- package/dist/db/migrations.js +39 -19
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/localization/default-messages.d.ts +0 -45
- package/dist/localization/default-messages.js +0 -45
- package/dist/notifications/routes.d.ts +5 -5
- package/dist/observability/routes.d.ts +3 -3
- package/dist/org/handlers.js +1 -8
- package/dist/provider-api/actions/custom-provider-registration.d.ts +8 -8
- package/dist/provider-api/actions/provider-api.d.ts +12 -12
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-discovery.js +0 -22
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.js +3 -1
- package/dist/server/better-auth-instance.js +2 -17
- package/dist/server/email.d.ts +0 -9
- package/dist/server/email.js +3 -44
- package/dist/server/index.d.ts +0 -1
- package/dist/server/index.js +0 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +3 -2
- package/docs/content/authentication.mdx +25 -19
- package/package.json +2 -1
- package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +3 -2
- package/corpus/templates/analytics/server/lib/emails.ts +0 -51
- package/corpus/templates/analytics/server/plugins/transactional-emails.ts +0 -5
- package/corpus/templates/calendar/server/lib/emails.ts +0 -167
- package/corpus/templates/calendar/server/plugins/transactional-emails.ts +0 -5
- package/corpus/templates/clips/server/lib/emails.ts +0 -256
- package/corpus/templates/clips/server/plugins/transactional-email-catalog.ts +0 -5
- package/corpus/templates/content/server/lib/emails.ts +0 -70
- package/corpus/templates/content/server/plugins/transactional-emails.ts +0 -5
- package/corpus/templates/dispatch/app/routes/transactional-email.$appId.$id.tsx +0 -4
- package/corpus/templates/dispatch/app/routes/transactional-email.tsx +0 -4
- package/corpus/templates/forms/server/lib/emails.ts +0 -52
- package/corpus/templates/forms/server/plugins/transactional-emails.ts +0 -5
- package/corpus/templates/plan/server/lib/emails.ts +0 -67
- package/corpus/templates/plan/server/plugins/transactional-emails.ts +0 -5
- package/corpus/templates/slides/server/lib/emails.ts +0 -40
- package/corpus/templates/slides/server/plugins/transactional-emails.ts +0 -5
- package/dist/email-catalog/actions/list-email-activity.d.ts +0 -6
- package/dist/email-catalog/actions/list-email-activity.js +0 -15
- package/dist/email-catalog/actions/list-email-engagement.d.ts +0 -6
- package/dist/email-catalog/actions/list-email-engagement.js +0 -15
- package/dist/email-catalog/actions/list-email-log.d.ts +0 -8
- package/dist/email-catalog/actions/list-email-log.js +0 -22
- package/dist/email-catalog/actions/list-transactional-emails.d.ts +0 -23
- package/dist/email-catalog/actions/list-transactional-emails.js +0 -61
- package/dist/email-catalog/actions/render-transactional-email-preview.d.ts +0 -10
- package/dist/email-catalog/actions/render-transactional-email-preview.js +0 -24
- package/dist/email-catalog/authorize.d.ts +0 -2
- package/dist/email-catalog/authorize.js +0 -10
- package/dist/email-catalog/log.d.ts +0 -57
- package/dist/email-catalog/log.js +0 -117
- package/dist/email-catalog/provider-metrics.d.ts +0 -34
- package/dist/email-catalog/provider-metrics.js +0 -165
- package/dist/email-catalog/registry.d.ts +0 -79
- package/dist/email-catalog/registry.js +0 -71
- package/dist/email-catalog/schema.d.ts +0 -212
- package/dist/email-catalog/schema.js +0 -49
- package/dist/email-catalog/system-emails.d.ts +0 -14
- package/dist/email-catalog/system-emails.js +0 -68
|
@@ -293,6 +293,8 @@ const CLIPS_AGENT_DOCS_URL =
|
|
|
293
293
|
"https://www.agent-native.com/docs/template-clips#agent-readable-clips";
|
|
294
294
|
const UPLOAD_STUCK_TIMEOUT_MS = 5 * 60 * 1000;
|
|
295
295
|
const PROCESSING_STUCK_TIMEOUT_MS = 12 * 60 * 1000;
|
|
296
|
+
const READY_MEDIA_SETTLE_POLL_MS = 20 * 1000;
|
|
297
|
+
const READY_MEDIA_SETTLE_POLL_INTERVAL_MS = 1000;
|
|
296
298
|
|
|
297
299
|
type ViewerPlatform = "mac" | "windows" | "linux";
|
|
298
300
|
|
|
@@ -404,6 +406,7 @@ export default function ShareRoute() {
|
|
|
404
406
|
}, [recordingId, attribution.ref, attribution.via]);
|
|
405
407
|
|
|
406
408
|
const playerRef = useRef<VideoPlayerHandle | null>(null);
|
|
409
|
+
const readyMediaPollRef = useRef<{ key: string; until: number } | null>(null);
|
|
407
410
|
const [password, setPassword] = useState<string | null>(() => {
|
|
408
411
|
if (typeof window === "undefined" || !shareId) return null;
|
|
409
412
|
try {
|
|
@@ -463,7 +466,26 @@ export default function ShareRoute() {
|
|
|
463
466
|
// page auto-upgrades from "Processing" to the real player the moment
|
|
464
467
|
// the server flips status to 'ready' and writes videoUrl. Mirrors
|
|
465
468
|
// r.$recordingId.tsx's playerDataQ.refetchInterval.
|
|
466
|
-
if (rec.status !== "ready" || !rec.videoUrl)
|
|
469
|
+
if (rec.status !== "ready" || !rec.videoUrl) {
|
|
470
|
+
readyMediaPollRef.current = null;
|
|
471
|
+
return 2000;
|
|
472
|
+
}
|
|
473
|
+
const mediaKey = [
|
|
474
|
+
rec.id,
|
|
475
|
+
rec.durationMs ?? "",
|
|
476
|
+
rec.videoSizeBytes ?? "",
|
|
477
|
+
rec.videoFormat ?? "",
|
|
478
|
+
].join(":");
|
|
479
|
+
const now = Date.now();
|
|
480
|
+
if (readyMediaPollRef.current?.key !== mediaKey) {
|
|
481
|
+
readyMediaPollRef.current = {
|
|
482
|
+
key: mediaKey,
|
|
483
|
+
until: now + READY_MEDIA_SETTLE_POLL_MS,
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
if (now < readyMediaPollRef.current.until) {
|
|
487
|
+
return READY_MEDIA_SETTLE_POLL_INTERVAL_MS;
|
|
488
|
+
}
|
|
467
489
|
// Also keep polling while a transcript is pending so "Transcribing…"
|
|
468
490
|
// auto-flips to the ready transcript (or to the failure card). The
|
|
469
491
|
// public payload has no transcript.cleanup field (that's authenticated
|
|
@@ -985,6 +1007,10 @@ export default function ShareRoute() {
|
|
|
985
1007
|
onVideoElementChange={setTrackedVideoEl}
|
|
986
1008
|
recordingId={recording.id}
|
|
987
1009
|
videoUrl={recording.videoUrl}
|
|
1010
|
+
mediaVersion={[
|
|
1011
|
+
recording.videoSizeBytes ?? "",
|
|
1012
|
+
recording.updatedAt ?? "",
|
|
1013
|
+
].join(":")}
|
|
988
1014
|
videoFormat={recording.videoFormat}
|
|
989
1015
|
embedProvider={isLoomEmbedBacked ? "loom" : null}
|
|
990
1016
|
durationMs={recording.durationMs}
|
|
@@ -86,29 +86,6 @@ 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
|
-
|
|
112
89
|
export interface RecapTopClipInput {
|
|
113
90
|
recordingId: string;
|
|
114
91
|
title?: string | null;
|
|
@@ -636,7 +613,6 @@ export async function sendClipsTransactionalEmail(
|
|
|
636
613
|
? (validReplyTo(input.senderEmail) ?? FRIENDLY_REPLY_TO)
|
|
637
614
|
: FRIENDLY_REPLY_TO,
|
|
638
615
|
timeoutMs: EMAIL_SEND_TIMEOUT_MS,
|
|
639
|
-
templateId: CLIPS_EMAIL_ID_BY_KIND[input.kind],
|
|
640
616
|
});
|
|
641
617
|
}
|
|
642
618
|
|
|
@@ -453,6 +453,7 @@ export default defineEventHandler(async (event) => {
|
|
|
453
453
|
editsJson: rec.editsJson,
|
|
454
454
|
videoUrl: playbackVideoUrl,
|
|
455
455
|
videoFormat: rec.videoFormat,
|
|
456
|
+
videoSizeBytes: rec.videoSizeBytes ?? null,
|
|
456
457
|
width: rec.width,
|
|
457
458
|
height: rec.height,
|
|
458
459
|
hasAudio: Boolean(rec.hasAudio),
|
|
@@ -21,10 +21,6 @@ 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";
|
|
28
24
|
|
|
29
25
|
export type DocumentCommentNotificationResult = ActivityNotificationResult;
|
|
30
26
|
|
|
@@ -74,48 +70,6 @@ export interface DocumentCommentNotificationInput {
|
|
|
74
70
|
isReply: boolean;
|
|
75
71
|
}
|
|
76
72
|
|
|
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
|
-
|
|
119
73
|
export async function notifyDocumentComment(
|
|
120
74
|
input: DocumentCommentNotificationInput,
|
|
121
75
|
): Promise<DocumentCommentNotificationResult> {
|
|
@@ -158,19 +112,34 @@ async function deliverDocumentCommentEmails(
|
|
|
158
112
|
logLabel: LOG_LABEL,
|
|
159
113
|
send: async (to) => {
|
|
160
114
|
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
|
+
|
|
161
134
|
await sendEmail({
|
|
162
|
-
...renderDocumentCommentEmail({
|
|
163
|
-
actor,
|
|
164
|
-
title,
|
|
165
|
-
url,
|
|
166
|
-
content: input.content,
|
|
167
|
-
isReply: input.isReply,
|
|
168
|
-
wasMentioned,
|
|
169
|
-
}),
|
|
170
135
|
to,
|
|
171
|
-
|
|
172
|
-
?
|
|
173
|
-
:
|
|
136
|
+
subject: wasMentioned
|
|
137
|
+
? `${actor} mentioned you on "${title}"`
|
|
138
|
+
: input.isReply
|
|
139
|
+
? `${actor} replied to a comment on "${title}"`
|
|
140
|
+
: `${actor} commented on "${title}"`,
|
|
141
|
+
html,
|
|
142
|
+
text,
|
|
174
143
|
});
|
|
175
144
|
},
|
|
176
145
|
});
|
|
@@ -162,7 +162,11 @@ function StrokeLayerControl({
|
|
|
162
162
|
<div className="min-w-0 flex-1">
|
|
163
163
|
<ColorInput
|
|
164
164
|
label=""
|
|
165
|
-
value={cssColorOrFallback(
|
|
165
|
+
value={cssColorOrFallback(
|
|
166
|
+
color,
|
|
167
|
+
/* guard:allow-raw-color - ColorInput needs a concrete swatch fallback. */
|
|
168
|
+
"#000000",
|
|
169
|
+
)}
|
|
166
170
|
onChange={(value, meta) =>
|
|
167
171
|
onStyleChange(`${prefix}Color`, value, meta)
|
|
168
172
|
}
|
|
@@ -449,7 +453,11 @@ export function StrokeProperties({
|
|
|
449
453
|
<StrokeLayerControl
|
|
450
454
|
kind="border"
|
|
451
455
|
visible={borderVisible}
|
|
452
|
-
color={
|
|
456
|
+
color={
|
|
457
|
+
styles.borderColor ||
|
|
458
|
+
/* guard:allow-raw-color - a new stroke needs a concrete color. */
|
|
459
|
+
"#000000"
|
|
460
|
+
}
|
|
453
461
|
width={styles.borderWidth || "0px"}
|
|
454
462
|
styleValue={styles.borderStyle || "none"}
|
|
455
463
|
onStyleChange={onStyleChange}
|
|
@@ -470,7 +478,12 @@ export function StrokeProperties({
|
|
|
470
478
|
<StrokeLayerControl
|
|
471
479
|
kind="outline"
|
|
472
480
|
visible={outlineVisible}
|
|
473
|
-
color={
|
|
481
|
+
color={
|
|
482
|
+
styles.outlineColor ||
|
|
483
|
+
styles.borderColor ||
|
|
484
|
+
/* guard:allow-raw-color - a new stroke needs a concrete color. */
|
|
485
|
+
"#000000"
|
|
486
|
+
}
|
|
474
487
|
width={styles.outlineWidth || "0px"}
|
|
475
488
|
styleValue={styles.outlineStyle || "solid"}
|
|
476
489
|
outlineOffset={styles.outlineOffset || "0px"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { emailStrong, renderEmail, sendEmail } from "@agent-native/core/server";
|
|
2
2
|
|
|
3
3
|
import type { FormField } from "../../shared/types.js";
|
|
4
|
-
import { FORMS_NEW_RESPONSE_EMAIL_ID } from "./emails.js";
|
|
5
4
|
|
|
6
5
|
export interface NewResponseEmailArgs {
|
|
7
6
|
to: string;
|
|
@@ -62,6 +61,5 @@ export async function sendNewResponseEmail(
|
|
|
62
61
|
await sendEmail({
|
|
63
62
|
to: args.to,
|
|
64
63
|
...renderNewResponseEmail(args),
|
|
65
|
-
templateId: FORMS_NEW_RESPONSE_EMAIL_ID,
|
|
66
64
|
});
|
|
67
65
|
}
|
|
@@ -22,8 +22,6 @@ import {
|
|
|
22
22
|
} from "../server/lib/local-identity.js";
|
|
23
23
|
import { newId, nowIso, planPath, writeEvent } from "../server/plans.js";
|
|
24
24
|
|
|
25
|
-
export const PLAN_ACCESS_REQUEST_EMAIL_ID = "plan.access-request";
|
|
26
|
-
|
|
27
25
|
function httpError(message: string, statusCode: number): Error {
|
|
28
26
|
return Object.assign(new Error(message), { statusCode });
|
|
29
27
|
}
|
|
@@ -51,28 +49,6 @@ function absolutePlanUrl(planId: string, kind: "plan" | "recap"): string {
|
|
|
51
49
|
}
|
|
52
50
|
}
|
|
53
51
|
|
|
54
|
-
export function renderPlanAccessRequestEmail(input: {
|
|
55
|
-
requesterName: string;
|
|
56
|
-
requesterEmail: string;
|
|
57
|
-
planTitle: string;
|
|
58
|
-
url: string;
|
|
59
|
-
}) {
|
|
60
|
-
const subject = `${input.requesterName} requested access to "${input.planTitle}"`;
|
|
61
|
-
return {
|
|
62
|
-
subject,
|
|
63
|
-
...renderEmail({
|
|
64
|
-
preheader: subject,
|
|
65
|
-
heading: "Access request",
|
|
66
|
-
paragraphs: [
|
|
67
|
-
`${emailStrong(input.requesterName)} (${emailStrong(input.requesterEmail)}) requested access to ${emailStrong(input.planTitle)}.`,
|
|
68
|
-
"Open the plan and use Share to grant access if this request should be approved.",
|
|
69
|
-
],
|
|
70
|
-
cta: { label: "Open plan", url: input.url },
|
|
71
|
-
footer: "You received this because you own this Agent-Native Plan.",
|
|
72
|
-
}),
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
52
|
async function notifyOwner(input: {
|
|
77
53
|
planId: string;
|
|
78
54
|
planKind: "plan" | "recap";
|
|
@@ -86,16 +62,19 @@ async function notifyOwner(input: {
|
|
|
86
62
|
return false;
|
|
87
63
|
}
|
|
88
64
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
65
|
+
const url = absolutePlanUrl(input.planId, input.planKind);
|
|
66
|
+
const subject = `${input.requesterName} requested access to "${input.planTitle}"`;
|
|
67
|
+
const { html, text } = renderEmail({
|
|
68
|
+
preheader: subject,
|
|
69
|
+
heading: "Access request",
|
|
70
|
+
paragraphs: [
|
|
71
|
+
`${emailStrong(input.requesterName)} (${emailStrong(input.requesterEmail)}) requested access to ${emailStrong(input.planTitle)}.`,
|
|
72
|
+
"Open the plan and use Share to grant access if this request should be approved.",
|
|
73
|
+
],
|
|
74
|
+
cta: { label: "Open plan", url },
|
|
75
|
+
footer: "You received this because you own this Agent-Native Plan.",
|
|
98
76
|
});
|
|
77
|
+
await sendEmail({ to: input.ownerEmail, subject, html, text });
|
|
99
78
|
return true;
|
|
100
79
|
}
|
|
101
80
|
|
|
@@ -14,8 +14,6 @@ import {
|
|
|
14
14
|
import type { PlanBundle, PlanComment } from "../../shared/types.js";
|
|
15
15
|
import { getDb, schema } from "../db/index.js";
|
|
16
16
|
|
|
17
|
-
export const PLAN_COMMENT_EMAIL_ID = "plan.plan-comment";
|
|
18
|
-
|
|
19
17
|
type CommentNotificationInput = {
|
|
20
18
|
bundle: PlanBundle;
|
|
21
19
|
insertedCommentIds: string[];
|
|
@@ -207,64 +205,40 @@ export function planCommentNotificationRecipients(input: {
|
|
|
207
205
|
return Array.from(recipients.values());
|
|
208
206
|
}
|
|
209
207
|
|
|
210
|
-
export function renderPlanCommentEmail(input: {
|
|
211
|
-
actor: string;
|
|
212
|
-
app: string;
|
|
213
|
-
planTitle: string;
|
|
214
|
-
planUrl: string;
|
|
215
|
-
message: string;
|
|
216
|
-
isReply: boolean;
|
|
217
|
-
reason: NotificationRecipient["reason"];
|
|
218
|
-
}) {
|
|
219
|
-
const actionText =
|
|
220
|
-
input.reason === "mention"
|
|
221
|
-
? "mentioned you in a comment on"
|
|
222
|
-
: input.reason === "plan-owner"
|
|
223
|
-
? "left a comment on your plan"
|
|
224
|
-
: "replied in a comment thread you participated in";
|
|
225
|
-
return {
|
|
226
|
-
subject: input.isReply
|
|
227
|
-
? `${input.actor} replied to a comment on "${input.planTitle}"`
|
|
228
|
-
: `${input.actor} commented on "${input.planTitle}"`,
|
|
229
|
-
...renderEmail({
|
|
230
|
-
preheader: `${input.actor} ${actionText} on ${input.app}.`,
|
|
231
|
-
heading: input.isReply
|
|
232
|
-
? "New reply on your plan"
|
|
233
|
-
: "New comment on your plan",
|
|
234
|
-
paragraphs: [
|
|
235
|
-
`${emailStrong(input.actor)} ${actionText} ${emailStrong(input.planTitle)}.`,
|
|
236
|
-
`Comment: "${commentExcerpt(input.message)}"`,
|
|
237
|
-
],
|
|
238
|
-
cta: { label: "Open plan", url: input.planUrl },
|
|
239
|
-
footer:
|
|
240
|
-
input.reason === "plan-owner"
|
|
241
|
-
? "You received this because you own this plan."
|
|
242
|
-
: input.reason === "mention"
|
|
243
|
-
? "You received this because you were mentioned in this comment."
|
|
244
|
-
: "You received this because you participated in this comment thread.",
|
|
245
|
-
}),
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
|
|
249
208
|
async function sendPlanCommentNotification(input: {
|
|
250
209
|
recipient: NotificationRecipient;
|
|
251
210
|
comment: PlanComment;
|
|
252
211
|
planTitle: string;
|
|
253
212
|
planId: string;
|
|
254
213
|
}) {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
214
|
+
const actor = actorName(input.comment);
|
|
215
|
+
const app = appName();
|
|
216
|
+
const isReply = Boolean(input.comment.parentCommentId);
|
|
217
|
+
const subject = isReply
|
|
218
|
+
? `${actor} replied to a comment on "${input.planTitle}"`
|
|
219
|
+
: `${actor} commented on "${input.planTitle}"`;
|
|
220
|
+
const actionText =
|
|
221
|
+
input.recipient.reason === "mention"
|
|
222
|
+
? "mentioned you in a comment on"
|
|
223
|
+
: input.recipient.reason === "plan-owner"
|
|
224
|
+
? "left a comment on your plan"
|
|
225
|
+
: "replied in a comment thread you participated in";
|
|
226
|
+
const { html, text } = renderEmail({
|
|
227
|
+
preheader: `${actor} ${actionText} on ${app}.`,
|
|
228
|
+
heading: isReply ? "New reply on your plan" : "New comment on your plan",
|
|
229
|
+
paragraphs: [
|
|
230
|
+
`${emailStrong(actor)} ${actionText} ${emailStrong(input.planTitle)}.`,
|
|
231
|
+
`Comment: "${commentExcerpt(input.comment.message)}"`,
|
|
232
|
+
],
|
|
233
|
+
cta: { label: "Open plan", url: planUrl(input.planId) },
|
|
234
|
+
footer:
|
|
235
|
+
input.recipient.reason === "plan-owner"
|
|
236
|
+
? "You received this because you own this plan."
|
|
237
|
+
: input.recipient.reason === "mention"
|
|
238
|
+
? "You received this because you were mentioned in this comment."
|
|
239
|
+
: "You received this because you participated in this comment thread.",
|
|
267
240
|
});
|
|
241
|
+
await sendEmail({ to: input.recipient.email, subject, html, text });
|
|
268
242
|
}
|
|
269
243
|
|
|
270
244
|
export async function notifyPlanCommentRecipients({
|
|
@@ -21,7 +21,6 @@ import { and, eq } from "drizzle-orm";
|
|
|
21
21
|
import { getDeckUrl } from "../../actions/_app-url.js";
|
|
22
22
|
import { SLIDES_USER_PREFS_KEY } from "../../shared/slides-user-prefs.js";
|
|
23
23
|
import { getDb, schema } from "../db/index.js";
|
|
24
|
-
import { SLIDES_DECK_COMMENT_EMAIL_ID } from "./emails.js";
|
|
25
24
|
|
|
26
25
|
/**
|
|
27
26
|
* `deck-missing` stays distinct from `no-recipients`: one means the deck could
|
|
@@ -100,41 +99,6 @@ async function threadParticipants(
|
|
|
100
99
|
return rows.map((row) => row.authorEmail);
|
|
101
100
|
}
|
|
102
101
|
|
|
103
|
-
export function renderDeckCommentEmail({
|
|
104
|
-
actor,
|
|
105
|
-
title,
|
|
106
|
-
url,
|
|
107
|
-
content,
|
|
108
|
-
isReply,
|
|
109
|
-
}: {
|
|
110
|
-
actor: string;
|
|
111
|
-
title: string;
|
|
112
|
-
url: string;
|
|
113
|
-
content: string;
|
|
114
|
-
isReply: boolean;
|
|
115
|
-
}) {
|
|
116
|
-
return {
|
|
117
|
-
subject: isReply
|
|
118
|
-
? `${actor} replied to a comment on "${title}"`
|
|
119
|
-
: `${actor} commented on "${title}"`,
|
|
120
|
-
...renderEmail({
|
|
121
|
-
preheader: isReply
|
|
122
|
-
? `${actor} replied to a comment on ${title}.`
|
|
123
|
-
: `${actor} commented on ${title}.`,
|
|
124
|
-
heading: isReply ? "New reply on your deck" : "New comment",
|
|
125
|
-
paragraphs: [
|
|
126
|
-
isReply
|
|
127
|
-
? `${emailStrong(actor)} replied in a comment thread on ${emailStrong(title)}.`
|
|
128
|
-
: `${emailStrong(actor)} commented on ${emailStrong(title)}.`,
|
|
129
|
-
`"${excerpt(content)}"`,
|
|
130
|
-
],
|
|
131
|
-
cta: { label: "Open deck", url },
|
|
132
|
-
footer:
|
|
133
|
-
"You received this because you own or participated in this thread. Turn these off in Slides settings.",
|
|
134
|
-
}),
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
102
|
export async function notifyDeckComment(input: {
|
|
139
103
|
deckId: string;
|
|
140
104
|
slideId: string;
|
|
@@ -189,16 +153,29 @@ async function deliverDeckCommentEmails(input: {
|
|
|
189
153
|
preferenceKey: SLIDES_USER_PREFS_KEY,
|
|
190
154
|
logLabel: LOG_LABEL,
|
|
191
155
|
send: async (to) => {
|
|
156
|
+
const { html, text } = renderEmail({
|
|
157
|
+
preheader: input.isReply
|
|
158
|
+
? `${actor} replied to a comment on ${deck.title}.`
|
|
159
|
+
: `${actor} commented on ${deck.title}.`,
|
|
160
|
+
heading: input.isReply ? "New reply on your deck" : "New comment",
|
|
161
|
+
paragraphs: [
|
|
162
|
+
input.isReply
|
|
163
|
+
? `${emailStrong(actor)} replied in a comment thread on ${emailStrong(deck.title)}.`
|
|
164
|
+
: `${emailStrong(actor)} commented on ${emailStrong(deck.title)}.`,
|
|
165
|
+
`"${excerpt(input.content)}"`,
|
|
166
|
+
],
|
|
167
|
+
cta: { label: "Open deck", url },
|
|
168
|
+
footer:
|
|
169
|
+
"You received this because you own or participated in this thread. Turn these off in Slides settings.",
|
|
170
|
+
});
|
|
171
|
+
|
|
192
172
|
await sendEmail({
|
|
193
|
-
...renderDeckCommentEmail({
|
|
194
|
-
actor,
|
|
195
|
-
title: deck.title,
|
|
196
|
-
url,
|
|
197
|
-
content: input.content,
|
|
198
|
-
isReply: input.isReply,
|
|
199
|
-
}),
|
|
200
173
|
to,
|
|
201
|
-
|
|
174
|
+
subject: input.isReply
|
|
175
|
+
? `${actor} replied to a comment on "${deck.title}"`
|
|
176
|
+
: `${actor} commented on "${deck.title}"`,
|
|
177
|
+
html,
|
|
178
|
+
text,
|
|
202
179
|
});
|
|
203
180
|
},
|
|
204
181
|
});
|
package/dist/a2a/handlers.js
CHANGED
|
@@ -583,17 +583,16 @@ async function handleSend(params, config, event) {
|
|
|
583
583
|
if (!asyncMode)
|
|
584
584
|
idempotencyKey = undefined;
|
|
585
585
|
if (asyncMode) {
|
|
586
|
-
// Refuse async mode entirely
|
|
587
|
-
// The async dispatch path self-fires the `_process-task` route, which
|
|
588
|
-
//
|
|
589
|
-
//
|
|
590
|
-
//
|
|
591
|
-
//
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
if (isA2AProductionRuntime() && !hasA2ASecret && !hasApiKey) {
|
|
586
|
+
// Refuse async mode entirely without the shared secret in production.
|
|
587
|
+
// The async dispatch path self-fires the `_process-task` route, which is
|
|
588
|
+
// authenticated with an internal HMAC signed by A2A_SECRET. A legacy
|
|
589
|
+
// apiKeyEnv can authenticate the JSON-RPC request, but it cannot
|
|
590
|
+
// authenticate that internal handoff. Accepting it here would create a
|
|
591
|
+
// working task that can never start and leaves the caller polling until
|
|
592
|
+
// its timeout.
|
|
593
|
+
if (isA2AProductionRuntime() && !hasConfiguredA2ASecret()) {
|
|
595
594
|
return {
|
|
596
|
-
...jsonRpcError(0, -32001, "A2A async mode
|
|
595
|
+
...jsonRpcError(0, -32001, "A2A async mode requires A2A_SECRET for internal processor dispatch; apiKeyEnv only supports synchronous A2A."),
|
|
597
596
|
_id: 0,
|
|
598
597
|
};
|
|
599
598
|
}
|
package/dist/collab/routes.d.ts
CHANGED
|
@@ -26,8 +26,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
|
|
|
26
26
|
* Body: { update: string (base64), requestSource?: string }
|
|
27
27
|
*/
|
|
28
28
|
export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
29
|
-
error: string;
|
|
30
29
|
ok?: undefined;
|
|
30
|
+
error: string;
|
|
31
31
|
} | {
|
|
32
32
|
error?: undefined;
|
|
33
33
|
ok: boolean;
|
package/dist/db/client.js
CHANGED
|
@@ -804,19 +804,19 @@ export function pgPoolOptions(url) {
|
|
|
804
804
|
export function neonPoolMax() {
|
|
805
805
|
if (!isServerlessRuntime())
|
|
806
806
|
return 20;
|
|
807
|
-
//
|
|
808
|
-
//
|
|
809
|
-
//
|
|
807
|
+
// Netlify can run several background workers concurrently; a background
|
|
808
|
+
// worker is not a global singleton and must not hold a larger pool than the
|
|
809
|
+
// foreground path. The agent's
|
|
810
810
|
// pre-send setup fires ~6 concurrent DB reads in parallel; with only 2
|
|
811
811
|
// connections that burst exhausts the pool and a single stalled connection
|
|
812
812
|
// freezes the worker before it can claim — observed on analytics' heavier
|
|
813
813
|
// action surface, where the worker froze right after `model_done` and never
|
|
814
|
-
// recorded `env_config`/`presend`, while the foreground
|
|
815
|
-
//
|
|
816
|
-
// burst
|
|
817
|
-
// connections reached" across many warm instances.
|
|
814
|
+
// recorded `env_config`/`presend`, while the foreground path ran the
|
|
815
|
+
// identical code in ~2s. Four connections preserve the concurrent
|
|
816
|
+
// read burst while keeping foreground and background caps aligned, avoiding
|
|
817
|
+
// "Max client connections reached" across many warm instances.
|
|
818
818
|
if (isBackgroundFunctionPoolContext())
|
|
819
|
-
return
|
|
819
|
+
return 4;
|
|
820
820
|
return 4;
|
|
821
821
|
}
|
|
822
822
|
/**
|