@agent-native/core 0.98.5 → 0.98.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +18 -0
- package/corpus/core/docs/content/external-agents.mdx +66 -7
- package/corpus/core/docs/content/locales/ar-SA/external-agents.mdx +40 -7
- package/corpus/core/docs/content/locales/de-DE/external-agents.mdx +41 -7
- package/corpus/core/docs/content/locales/es-ES/external-agents.mdx +41 -7
- package/corpus/core/docs/content/locales/fr-FR/external-agents.mdx +41 -7
- package/corpus/core/docs/content/locales/hi-IN/external-agents.mdx +40 -7
- package/corpus/core/docs/content/locales/ja-JP/external-agents.mdx +40 -7
- package/corpus/core/docs/content/locales/ko-KR/external-agents.mdx +40 -7
- package/corpus/core/docs/content/locales/pt-BR/external-agents.mdx +41 -7
- package/corpus/core/docs/content/locales/zh-CN/external-agents.mdx +39 -7
- package/corpus/core/docs/content/locales/zh-TW/external-agents.mdx +39 -7
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/handlers.ts +101 -26
- package/corpus/core/src/a2a/task-store.ts +128 -3
- package/corpus/core/src/client/session-replay.ts +67 -5
- package/corpus/core/src/integrations/adapters/slack.ts +207 -17
- package/corpus/core/src/integrations/identity-links-store.ts +210 -0
- package/corpus/core/src/integrations/identity.ts +196 -0
- package/corpus/core/src/integrations/index.ts +6 -0
- package/corpus/core/src/integrations/plugin.ts +130 -1
- package/corpus/core/src/integrations/types.ts +37 -0
- package/corpus/core/src/integrations/webhook-handler.ts +1 -0
- package/corpus/core/src/mcp/build-server.ts +127 -22
- package/corpus/core/src/mcp/builtin-tools.ts +5 -2
- package/corpus/core/src/mcp/external-agent-policy.ts +18 -0
- package/corpus/core/src/mcp/index.ts +1 -0
- package/corpus/core/src/server/agent-chat/plugin-options.ts +12 -1
- package/corpus/core/src/server/agent-chat/script-entries.ts +20 -3
- package/corpus/core/src/server/agent-chat-plugin.ts +3 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +34 -5
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +4 -1
- package/corpus/templates/analytics/AGENTS.md +20 -5
- package/corpus/templates/analytics/actions/create-session-replay-agent-link.ts +0 -1
- package/corpus/templates/analytics/actions/get-session-replay-events.ts +0 -1
- package/corpus/templates/analytics/actions/get-session-replay-timeline.ts +40 -0
- package/corpus/templates/analytics/actions/list-error-issues.ts +12 -0
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +4 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +48 -17
- package/corpus/templates/analytics/changelog/2026-07-12-analytics-uses-the-full-in-app-agent-for-multi-step-incident.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-connected-external-agents-can-now-look-up-sessions-error-iss.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-session-identities-stay-visible-when-demo-mode-is-off.md +6 -0
- package/corpus/templates/analytics/server/handlers/session-replay.ts +15 -9
- package/corpus/templates/analytics/server/lib/analytics-connector-catalog.ts +2 -2
- package/corpus/templates/analytics/server/lib/error-capture.ts +47 -5
- package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +72 -22
- package/corpus/templates/analytics/server/lib/session-replay.ts +41 -11
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +7 -0
- package/corpus/templates/analytics/server/plugins/db.ts +15 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/agent-diagnostics.json.get.ts +8 -5
- package/corpus/templates/analytics/server/routes/api/session-replay/agent-events.json.get.ts +8 -3
- package/corpus/templates/calendar/app/components/calendar/DeleteEventDialog.tsx +12 -2
- package/corpus/templates/forms/app/global.css +54 -8
- package/corpus/templates/forms/app/pages/AskPage.tsx +35 -13
- package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +11 -17
- package/corpus/templates/forms/app/pages/ResponsesPage.tsx +8 -5
- package/corpus/templates/forms/changelog/2026-07-12-ask-forms-suggestions-now-sit-beneath-the-composer-for-a-tig.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-12-builder-response-tables-now-fill-the-view-without-a-redundan.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-12-response-tables-now-fill-the-available-pane-with-a-flexible-.md +6 -0
- package/corpus/templates/mail/AGENTS.md +3 -1
- package/corpus/templates/mail/server/lib/mail-integrations.ts +3 -0
- package/dist/a2a/handlers.d.ts.map +1 -1
- package/dist/a2a/handlers.js +89 -20
- package/dist/a2a/handlers.js.map +1 -1
- package/dist/a2a/task-store.d.ts +32 -1
- package/dist/a2a/task-store.d.ts.map +1 -1
- package/dist/a2a/task-store.js +111 -4
- package/dist/a2a/task-store.js.map +1 -1
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +53 -8
- package/dist/client/session-replay.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/integrations/adapters/slack.d.ts.map +1 -1
- package/dist/integrations/adapters/slack.js +176 -17
- package/dist/integrations/adapters/slack.js.map +1 -1
- package/dist/integrations/identity-links-store.d.ts +23 -0
- package/dist/integrations/identity-links-store.d.ts.map +1 -0
- package/dist/integrations/identity-links-store.js +158 -0
- package/dist/integrations/identity-links-store.js.map +1 -0
- package/dist/integrations/identity.d.ts +28 -0
- package/dist/integrations/identity.d.ts.map +1 -0
- package/dist/integrations/identity.js +142 -0
- package/dist/integrations/identity.js.map +1 -0
- package/dist/integrations/index.d.ts +2 -0
- package/dist/integrations/index.d.ts.map +1 -1
- package/dist/integrations/index.js +2 -0
- package/dist/integrations/index.js.map +1 -1
- package/dist/integrations/plugin.d.ts.map +1 -1
- package/dist/integrations/plugin.js +93 -1
- package/dist/integrations/plugin.js.map +1 -1
- package/dist/integrations/types.d.ts +31 -0
- package/dist/integrations/types.d.ts.map +1 -1
- package/dist/integrations/types.js.map +1 -1
- package/dist/integrations/webhook-handler.js +1 -0
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/mcp/build-server.d.ts +9 -2
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +97 -20
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/builtin-tools.d.ts.map +1 -1
- package/dist/mcp/builtin-tools.js +5 -2
- package/dist/mcp/builtin-tools.js.map +1 -1
- package/dist/mcp/external-agent-policy.d.ts +19 -0
- package/dist/mcp/external-agent-policy.d.ts.map +1 -0
- package/dist/mcp/external-agent-policy.js +2 -0
- package/dist/mcp/external-agent-policy.js.map +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/server/agent-chat/plugin-options.d.ts +11 -1
- package/dist/server/agent-chat/plugin-options.d.ts.map +1 -1
- package/dist/server/agent-chat/plugin-options.js.map +1 -1
- package/dist/server/agent-chat/script-entries.d.ts.map +1 -1
- package/dist/server/agent-chat/script-entries.js +16 -2
- package/dist/server/agent-chat/script-entries.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +3 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +34 -5
- package/docs/content/external-agents.mdx +66 -7
- package/docs/content/locales/ar-SA/external-agents.mdx +40 -7
- package/docs/content/locales/de-DE/external-agents.mdx +41 -7
- package/docs/content/locales/es-ES/external-agents.mdx +41 -7
- package/docs/content/locales/fr-FR/external-agents.mdx +41 -7
- package/docs/content/locales/hi-IN/external-agents.mdx +40 -7
- package/docs/content/locales/ja-JP/external-agents.mdx +40 -7
- package/docs/content/locales/ko-KR/external-agents.mdx +40 -7
- package/docs/content/locales/pt-BR/external-agents.mdx +41 -7
- package/docs/content/locales/zh-CN/external-agents.mdx +39 -7
- package/docs/content/locales/zh-TW/external-agents.mdx +39 -7
- package/package.json +1 -1
- package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +34 -5
|
@@ -181,7 +181,7 @@
|
|
|
181
181
|
|
|
182
182
|
.forms-ask-chat-panel [data-agent-empty-state="centered"] {
|
|
183
183
|
justify-content: center;
|
|
184
|
-
padding-bottom: clamp(
|
|
184
|
+
padding-bottom: clamp(2rem, 6vh, 4rem);
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
.forms-ask-chat-panel [data-agent-empty-state="centered"] .agent-chat-scroll {
|
|
@@ -269,12 +269,28 @@
|
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
.forms-ask-chat-panel [data-agent-empty-state="centered"] .forms-chat-intro {
|
|
272
|
-
display:
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
272
|
+
display: contents;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.forms-ask-chat-panel [data-agent-empty-state="centered"] .forms-chat-intro h1,
|
|
276
|
+
.forms-ask-chat-panel [data-agent-empty-state="centered"] .forms-chat-intro p {
|
|
277
|
+
width: 100%;
|
|
277
278
|
text-align: center;
|
|
279
|
+
order: 1;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.forms-ask-chat-panel [data-agent-empty-state="centered"] .forms-chat-intro h1 {
|
|
283
|
+
margin: 0 0 0.5rem;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.forms-ask-chat-panel [data-agent-empty-state="centered"] .forms-chat-intro p {
|
|
287
|
+
margin: 0 auto 1rem;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.forms-ask-chat-panel
|
|
291
|
+
[data-agent-empty-state="centered"]
|
|
292
|
+
.agent-composer-stack {
|
|
293
|
+
order: 2;
|
|
278
294
|
}
|
|
279
295
|
|
|
280
296
|
.forms-chat-intro h1 {
|
|
@@ -297,10 +313,11 @@
|
|
|
297
313
|
flex-wrap: wrap;
|
|
298
314
|
justify-content: center;
|
|
299
315
|
gap: 0.5rem;
|
|
300
|
-
|
|
316
|
+
order: 3;
|
|
317
|
+
margin: 0.75rem auto 0;
|
|
301
318
|
}
|
|
302
319
|
|
|
303
|
-
.forms-chat-pill-row
|
|
320
|
+
.forms-chat-pill-row button {
|
|
304
321
|
display: inline-flex;
|
|
305
322
|
align-items: center;
|
|
306
323
|
gap: 0.375rem;
|
|
@@ -310,8 +327,37 @@
|
|
|
310
327
|
box-shadow: var(--forms-shadow-control);
|
|
311
328
|
padding: 0.375rem 0.625rem;
|
|
312
329
|
color: hsl(var(--muted-foreground));
|
|
330
|
+
cursor: pointer;
|
|
313
331
|
font-size: 0.75rem;
|
|
314
332
|
font-weight: 600;
|
|
333
|
+
transition-property: background-color, border-color, color, scale;
|
|
334
|
+
transition-duration: 150ms;
|
|
335
|
+
transition-timing-function: ease-out;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.forms-chat-pill-row button:hover {
|
|
339
|
+
border-color: hsl(var(--foreground) / 0.2);
|
|
340
|
+
background: hsl(var(--background));
|
|
341
|
+
color: hsl(var(--foreground));
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.forms-chat-pill-row button:focus-visible {
|
|
345
|
+
outline: none;
|
|
346
|
+
box-shadow: var(--forms-shadow-focus);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.forms-chat-pill-row button:active {
|
|
350
|
+
scale: 0.96;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
@media (prefers-reduced-motion: reduce) {
|
|
354
|
+
.forms-chat-pill-row button {
|
|
355
|
+
transition: none;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.forms-chat-pill-row button:active {
|
|
359
|
+
scale: 1;
|
|
360
|
+
}
|
|
315
361
|
}
|
|
316
362
|
|
|
317
363
|
@media (min-width: 640px) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AgentChatSurface,
|
|
3
3
|
markAgentChatHomeHandoff,
|
|
4
|
+
sendToAgentChat,
|
|
4
5
|
useT,
|
|
5
6
|
} from "@agent-native/core/client";
|
|
6
7
|
import { IconChartBar, IconDatabase, IconSettings } from "@tabler/icons-react";
|
|
@@ -26,6 +27,28 @@ export function AskPage() {
|
|
|
26
27
|
};
|
|
27
28
|
}, []);
|
|
28
29
|
|
|
30
|
+
function prefillSuggestion(message: string) {
|
|
31
|
+
sendToAgentChat({ message, submit: false, chatTarget: "local" });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const suggestions = [
|
|
35
|
+
{
|
|
36
|
+
label: t("home.pillForms"),
|
|
37
|
+
prompt: "@forms",
|
|
38
|
+
icon: IconDatabase,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
label: t("home.pillAnalytics"),
|
|
42
|
+
prompt: "analytics",
|
|
43
|
+
icon: IconChartBar,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
label: t("home.pillConfiguration"),
|
|
47
|
+
prompt: "configuration",
|
|
48
|
+
icon: IconSettings,
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
|
|
29
52
|
return (
|
|
30
53
|
<div className="flex h-full min-h-0 flex-col bg-background">
|
|
31
54
|
<AgentChatSurface
|
|
@@ -48,19 +71,18 @@ export function AskPage() {
|
|
|
48
71
|
<div className="forms-chat-intro">
|
|
49
72
|
<h1>{t("home.heading")}</h1>
|
|
50
73
|
<p>{t("home.description")}</p>
|
|
51
|
-
<div className="forms-chat-pill-row"
|
|
52
|
-
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
</span>
|
|
74
|
+
<div className="forms-chat-pill-row">
|
|
75
|
+
{suggestions.map(({ icon: Icon, label, prompt }) => (
|
|
76
|
+
<button
|
|
77
|
+
key={prompt}
|
|
78
|
+
type="button"
|
|
79
|
+
className="forms-chat-pill"
|
|
80
|
+
onClick={() => prefillSuggestion(prompt)}
|
|
81
|
+
>
|
|
82
|
+
<Icon className="size-3.5" />
|
|
83
|
+
{label}
|
|
84
|
+
</button>
|
|
85
|
+
))}
|
|
64
86
|
</div>
|
|
65
87
|
</div>
|
|
66
88
|
}
|
|
@@ -1031,7 +1031,7 @@ function BuilderContent({
|
|
|
1031
1031
|
)
|
|
1032
1032
|
}
|
|
1033
1033
|
className={cn(
|
|
1034
|
-
"group relative cursor-pointer rounded-lg border
|
|
1034
|
+
"group relative -mx-3 cursor-pointer rounded-lg border px-3 py-4 transition-[background-color,border-color,box-shadow,opacity] duration-150 ease-out sm:-mx-4 sm:px-4",
|
|
1035
1035
|
selectedFieldId === field.id
|
|
1036
1036
|
? "border-primary bg-card shadow-[0_1px_3px_-2px_hsl(var(--foreground)/0.16)] ring-1 ring-primary/20"
|
|
1037
1037
|
: "border-transparent bg-transparent shadow-none hover:border-border/80 hover:bg-card/70 hover:shadow-[0_1px_3px_-2px_hsl(var(--foreground)/0.12)]",
|
|
@@ -1075,7 +1075,7 @@ function BuilderContent({
|
|
|
1075
1075
|
) : (
|
|
1076
1076
|
<div
|
|
1077
1077
|
key={field.id}
|
|
1078
|
-
className="relative rounded-lg border border-transparent bg-transparent
|
|
1078
|
+
className="relative -mx-3 rounded-lg border border-transparent bg-transparent px-3 py-4 transition-[background-color,border-color] duration-150 hover:border-border/70 hover:bg-card/70 sm:-mx-4 sm:px-4"
|
|
1079
1079
|
>
|
|
1080
1080
|
<FieldRenderer field={field} preview />
|
|
1081
1081
|
</div>
|
|
@@ -1233,7 +1233,6 @@ function ResultsContent({ formId, form }: { formId: string; form: any }) {
|
|
|
1233
1233
|
|
|
1234
1234
|
const allResponses = data?.responses || [];
|
|
1235
1235
|
const fields: FormField[] = data?.fields || form?.fields || [];
|
|
1236
|
-
const total = data?.total ?? 0;
|
|
1237
1236
|
const hasSubmitterEmail = allResponses.some((r: any) =>
|
|
1238
1237
|
responseValueAsString(r.submitterEmail).trim(),
|
|
1239
1238
|
);
|
|
@@ -1358,13 +1357,7 @@ function ResultsContent({ formId, form }: { formId: string; form: any }) {
|
|
|
1358
1357
|
return (
|
|
1359
1358
|
<div className="flex flex-col flex-1 overflow-hidden">
|
|
1360
1359
|
<div className="flex flex-wrap items-center justify-between gap-2 px-3 sm:px-4 py-2 border-b border-border">
|
|
1361
|
-
<div className="flex items-center gap-2">
|
|
1362
|
-
<Badge variant="secondary" className="text-xs">
|
|
1363
|
-
{t("builder.results.responseCount", {
|
|
1364
|
-
count: total,
|
|
1365
|
-
formattedCount: formatNumber(total),
|
|
1366
|
-
})}
|
|
1367
|
-
</Badge>
|
|
1360
|
+
<div className="ms-auto flex items-center gap-2">
|
|
1368
1361
|
{search.trim() && filtered.length !== allResponses.length && (
|
|
1369
1362
|
<span className="text-xs text-muted-foreground">
|
|
1370
1363
|
{t("builder.results.matchCount", {
|
|
@@ -1373,8 +1366,6 @@ function ResultsContent({ formId, form }: { formId: string; form: any }) {
|
|
|
1373
1366
|
})}
|
|
1374
1367
|
</span>
|
|
1375
1368
|
)}
|
|
1376
|
-
</div>
|
|
1377
|
-
<div className="flex items-center gap-2">
|
|
1378
1369
|
<div className="relative">
|
|
1379
1370
|
<IconSearch className="absolute start-2 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground pointer-events-none" />
|
|
1380
1371
|
<Input
|
|
@@ -1397,17 +1388,20 @@ function ResultsContent({ formId, form }: { formId: string; form: any }) {
|
|
|
1397
1388
|
</div>
|
|
1398
1389
|
</div>
|
|
1399
1390
|
<div className="flex-1 min-w-0 overflow-auto overscroll-x-contain">
|
|
1400
|
-
<div className="min-w-full">
|
|
1391
|
+
<div className="w-full min-w-full">
|
|
1401
1392
|
<table
|
|
1402
|
-
className="min-w-full table-fixed text-sm"
|
|
1403
|
-
style={{ minWidth: responseTableMinWidth }}
|
|
1393
|
+
className="w-full min-w-full table-fixed text-sm"
|
|
1394
|
+
style={{ width: "100%", minWidth: responseTableMinWidth }}
|
|
1404
1395
|
>
|
|
1405
1396
|
<colgroup>
|
|
1406
1397
|
<col className="w-16" />
|
|
1407
1398
|
<col className="w-40" />
|
|
1408
1399
|
{hasSubmitterEmail ? <col className="w-56" /> : null}
|
|
1409
|
-
{fields.map((f) => (
|
|
1410
|
-
<col
|
|
1400
|
+
{fields.map((f, index) => (
|
|
1401
|
+
<col
|
|
1402
|
+
key={f.id}
|
|
1403
|
+
className={index === fields.length - 1 ? "w-auto" : "w-80"}
|
|
1404
|
+
/>
|
|
1411
1405
|
))}
|
|
1412
1406
|
</colgroup>
|
|
1413
1407
|
<thead>
|
|
@@ -355,10 +355,10 @@ export function ResponsesPage() {
|
|
|
355
355
|
</div>
|
|
356
356
|
) : (
|
|
357
357
|
<div className="flex-1 min-w-0 overflow-auto overscroll-x-contain">
|
|
358
|
-
<div className="min-w-full">
|
|
358
|
+
<div className="w-full min-w-full">
|
|
359
359
|
<table
|
|
360
|
-
className="min-w-full table-fixed text-sm"
|
|
361
|
-
style={{ minWidth: responseTableMinWidth }}
|
|
360
|
+
className="w-full min-w-full table-fixed text-sm"
|
|
361
|
+
style={{ width: "100%", minWidth: responseTableMinWidth }}
|
|
362
362
|
>
|
|
363
363
|
<colgroup>
|
|
364
364
|
<col className="w-16" />
|
|
@@ -366,8 +366,11 @@ export function ResponsesPage() {
|
|
|
366
366
|
{hasSubmitterEmail ? <col className="w-56" /> : null}
|
|
367
367
|
{hasPageUrl ? <col className="w-64" /> : null}
|
|
368
368
|
{hasClientSurface ? <col className="w-40" /> : null}
|
|
369
|
-
{fields.map((f) => (
|
|
370
|
-
<col
|
|
369
|
+
{fields.map((f, index) => (
|
|
370
|
+
<col
|
|
371
|
+
key={f.id}
|
|
372
|
+
className={index === fields.length - 1 ? "w-auto" : "w-80"}
|
|
373
|
+
/>
|
|
371
374
|
))}
|
|
372
375
|
</colgroup>
|
|
373
376
|
<thead>
|
|
@@ -61,7 +61,9 @@ Detailed draft, queue, and contact-resolution patterns live in
|
|
|
61
61
|
(`draft-queue` skill), not `send-email` directly. The requester and reviewer
|
|
62
62
|
must both be members of the active organization; Slack intake resolves the
|
|
63
63
|
sender's real email via `users.info` (requires `users:read.email` bot scope)
|
|
64
|
-
before it will queue anything.
|
|
64
|
+
before it will queue anything. Managed installs request that scope through
|
|
65
|
+
the generated OAuth manifest; reconnect an existing Slack install after
|
|
66
|
+
granting it. Legacy bot-token installs must add the scope in Slack manually.
|
|
65
67
|
- Never edit the email store to change a draft the user is currently composing;
|
|
66
68
|
use `manage-draft` or the `compose-{id}` application-state key described in
|
|
67
69
|
`email-drafts`.
|
|
@@ -91,6 +91,9 @@ async function resolveSlackSenderProfile(
|
|
|
91
91
|
async function resolveIncomingEmail(
|
|
92
92
|
incoming: IncomingMessage,
|
|
93
93
|
): Promise<string | null> {
|
|
94
|
+
if (incoming.senderVerified === true && incoming.senderEmail?.trim()) {
|
|
95
|
+
return incoming.senderEmail.trim().toLowerCase();
|
|
96
|
+
}
|
|
94
97
|
if (incoming.platform === "slack") {
|
|
95
98
|
return (await resolveSlackSenderProfile(incoming)).email;
|
|
96
99
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/a2a/handlers.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/a2a/handlers.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EACV,SAAS,EAIT,eAAe,EAGhB,MAAM,YAAY,CAAC;AA4EpB;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,SAAS,EACjB,KAAK,CAAC,EAAE,GAAG,GACV,OAAO,CAAC,IAAI,CAAC,CAwEf;AAsqBD;;;GAGG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,GAAG,EACT,KAAK,EAAE,GAAG,EACV,MAAM,EAAE,SAAS,GAChB,OAAO,CAAC,eAAe,CAAC,CAyC1B"}
|
package/dist/a2a/handlers.js
CHANGED
|
@@ -4,7 +4,7 @@ import { withConfiguredAppBasePath } from "../server/app-base-path.js";
|
|
|
4
4
|
import { fireInternalDispatch } from "../server/self-dispatch.js";
|
|
5
5
|
import { agentChat } from "../shared/agent-chat.js";
|
|
6
6
|
import { hasConfiguredA2ASecret, isA2AProductionRuntime, } from "./auth-policy.js";
|
|
7
|
-
import { createTask, getTask, getTaskOwner, updateTask, claimA2ATaskForProcessing, getA2ATaskDispatchState, failStuckA2ATask, touchQueuedA2ATaskDispatch, touchProcessingA2ATask, } from "./task-store.js";
|
|
7
|
+
import { createTask, getTask, getTaskOwner, updateTask, claimA2ATaskForProcessing, getA2ATaskDispatchState, failStuckA2ATask, failStuckQueuedA2ATask, settleProcessingA2ATask, touchQueuedA2ATaskDispatch, touchProcessingA2ATask, } from "./task-store.js";
|
|
8
8
|
// Inlined to avoid pulling the entire core-routes-plugin (and its h3
|
|
9
9
|
// transitive deps) into the a2a/handlers test boundary. Must stay in sync
|
|
10
10
|
// with FRAMEWORK_ROUTE_PREFIX in `server/core-routes-plugin.ts`.
|
|
@@ -12,12 +12,40 @@ const A2A_PROCESS_TASK_PATH = "/_agent-native/a2a/_process-task";
|
|
|
12
12
|
const A2A_QUEUED_DISPATCH_STUCK_AFTER_MS = 10_000;
|
|
13
13
|
const A2A_PROCESSING_STUCK_AFTER_MS = 5 * 60 * 1000;
|
|
14
14
|
const A2A_PROCESSING_HEARTBEAT_MS = 30_000;
|
|
15
|
+
/**
|
|
16
|
+
* Hard cap on how long a task may sit in submitted/working (never reaching
|
|
17
|
+
* `processing`) before the dispatch-retry loop in
|
|
18
|
+
* `refireStuckAsyncTaskIfNeeded` gives up and fails it. Without this, a
|
|
19
|
+
* persistently failing dispatch (missing background function, bad A2A
|
|
20
|
+
* secret, 404) throttles-and-retries forever — the queued bucket otherwise
|
|
21
|
+
* has no terminal state. Override with A2A_QUEUED_LIFETIME_MAX_MS.
|
|
22
|
+
*/
|
|
23
|
+
function a2aQueuedLifetimeMaxMs() {
|
|
24
|
+
const raw = Number(process.env.A2A_QUEUED_LIFETIME_MAX_MS);
|
|
25
|
+
if (Number.isFinite(raw) && raw > 0)
|
|
26
|
+
return raw;
|
|
27
|
+
return 3 * 60 * 1000;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Hard cap on total time a task may spend in `processing`, independent of
|
|
31
|
+
* the liveness heartbeat. `A2A_PROCESSING_STUCK_AFTER_MS` alone only catches
|
|
32
|
+
* a dead process — a hung await inside a still-alive process keeps
|
|
33
|
+
* `updated_at` fresh via the heartbeat forever. This bounds that case
|
|
34
|
+
* without cutting off legitimately long runs under it. Override with
|
|
35
|
+
* A2A_PROCESSING_LIFETIME_MAX_MS.
|
|
36
|
+
*/
|
|
37
|
+
function a2aProcessingLifetimeMaxMs() {
|
|
38
|
+
const raw = Number(process.env.A2A_PROCESSING_LIFETIME_MAX_MS);
|
|
39
|
+
if (Number.isFinite(raw) && raw > 0)
|
|
40
|
+
return raw;
|
|
41
|
+
return 30 * 60 * 1000;
|
|
42
|
+
}
|
|
15
43
|
/**
|
|
16
44
|
* Dispatch an async A2A task to a fresh function execution. Apps that opted
|
|
17
45
|
* into durable background runs reuse the emitted Netlify 15-minute worker;
|
|
18
46
|
* other hosts and apps retain the normal portable self-webhook route.
|
|
19
47
|
*/
|
|
20
|
-
async function fireProcessTaskDispatch(event, taskId, config) {
|
|
48
|
+
async function fireProcessTaskDispatch(event, taskId, config, options) {
|
|
21
49
|
const backgroundPath = resolveAgentChatProcessRunDispatchPath();
|
|
22
50
|
const useBackgroundWorker = isAgentChatDurableBackgroundEnabled({
|
|
23
51
|
appOptIn: config.durableBackgroundRuns === true,
|
|
@@ -33,6 +61,13 @@ async function fireProcessTaskDispatch(event, taskId, config) {
|
|
|
33
61
|
},
|
|
34
62
|
}
|
|
35
63
|
: {}),
|
|
64
|
+
// Only Netlify's background-function URL acknowledges enqueue with 202.
|
|
65
|
+
// The portable framework route keeps its HTTP response open until the
|
|
66
|
+
// processor completes, so awaiting that response would turn async
|
|
67
|
+
// message/send into a long synchronous request (or a 15s timeout).
|
|
68
|
+
...(options?.awaitResponse && useBackgroundWorker
|
|
69
|
+
? { awaitResponse: true }
|
|
70
|
+
: {}),
|
|
36
71
|
});
|
|
37
72
|
}
|
|
38
73
|
/**
|
|
@@ -52,7 +87,7 @@ export async function processA2ATaskFromQueue(taskId, config, event) {
|
|
|
52
87
|
}
|
|
53
88
|
const message = claimed.history?.[0];
|
|
54
89
|
if (!message) {
|
|
55
|
-
await
|
|
90
|
+
await settleProcessingA2ATask(taskId, {
|
|
56
91
|
state: "failed",
|
|
57
92
|
message: {
|
|
58
93
|
role: "agent",
|
|
@@ -78,7 +113,7 @@ export async function processA2ATaskFromQueue(taskId, config, event) {
|
|
|
78
113
|
}
|
|
79
114
|
catch (err) {
|
|
80
115
|
try {
|
|
81
|
-
await
|
|
116
|
+
await settleProcessingA2ATask(taskId, {
|
|
82
117
|
state: "failed",
|
|
83
118
|
message: {
|
|
84
119
|
role: "agent",
|
|
@@ -242,7 +277,7 @@ async function runHandlerAndPersist(taskId, message, config, contextId, metadata
|
|
|
242
277
|
for await (const msg of result) {
|
|
243
278
|
lastMessage = msg;
|
|
244
279
|
}
|
|
245
|
-
await
|
|
280
|
+
await settleProcessingA2ATask(taskId, {
|
|
246
281
|
state: "completed",
|
|
247
282
|
message: lastMessage,
|
|
248
283
|
artifacts: artifacts.length > 0 ? artifacts : undefined,
|
|
@@ -251,14 +286,14 @@ async function runHandlerAndPersist(taskId, message, config, contextId, metadata
|
|
|
251
286
|
}
|
|
252
287
|
const handlerResult = await result;
|
|
253
288
|
const allArtifacts = [...artifacts, ...(handlerResult.artifacts ?? [])];
|
|
254
|
-
await
|
|
289
|
+
await settleProcessingA2ATask(taskId, {
|
|
255
290
|
state: "completed",
|
|
256
291
|
message: handlerResult.message,
|
|
257
292
|
artifacts: allArtifacts.length > 0 ? allArtifacts : undefined,
|
|
258
293
|
});
|
|
259
294
|
}
|
|
260
295
|
catch (err) {
|
|
261
|
-
await
|
|
296
|
+
await settleProcessingA2ATask(taskId, {
|
|
262
297
|
state: "failed",
|
|
263
298
|
message: {
|
|
264
299
|
role: "agent",
|
|
@@ -330,9 +365,23 @@ async function handleSend(params, config, event) {
|
|
|
330
365
|
};
|
|
331
366
|
const task = await createTask(message, contextId, taskMetadata, ownerEmailForTask);
|
|
332
367
|
const working = await updateTask(task.id, { state: "working" });
|
|
333
|
-
|
|
368
|
+
// Awaited, not fire-and-forget: this handler is about to return, and a
|
|
369
|
+
// detached dispatch fetch racing only a short settle timer can be killed
|
|
370
|
+
// mid-flight when the serverless response is flushed WITHOUT rejecting —
|
|
371
|
+
// see the `awaitResponse` doc on `fireInternalDispatch` in
|
|
372
|
+
// server/self-dispatch.ts. `awaitResponse: true` requests the stronger
|
|
373
|
+
// guarantee; `fireProcessTaskDispatch` only honors it for the Netlify
|
|
374
|
+
// background-worker path (fast 202 ack) and falls back to the settle
|
|
375
|
+
// race for the portable route, which holds its response open until the
|
|
376
|
+
// handler finishes.
|
|
377
|
+
try {
|
|
378
|
+
await fireProcessTaskDispatch(event, task.id, config, {
|
|
379
|
+
awaitResponse: true,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
catch (err) {
|
|
334
383
|
console.error("[a2a] Failed to dispatch process-task:", err);
|
|
335
|
-
}
|
|
384
|
+
}
|
|
336
385
|
return { ...jsonRpcResult(0, working ?? task), _id: 0 };
|
|
337
386
|
}
|
|
338
387
|
return withA2ARequestContext(metadata, event, async () => {
|
|
@@ -535,21 +584,41 @@ async function refireStuckAsyncTaskIfNeeded(taskId, event, config) {
|
|
|
535
584
|
if (!state.metadata?.__a2a_processor)
|
|
536
585
|
return false;
|
|
537
586
|
const now = Date.now();
|
|
538
|
-
if (
|
|
539
|
-
|
|
540
|
-
if (
|
|
541
|
-
|
|
587
|
+
if (state.statusState === "submitted" || state.statusState === "working") {
|
|
588
|
+
const queuedLifetimeCutoff = now - a2aQueuedLifetimeMaxMs();
|
|
589
|
+
if (state.createdAt <= queuedLifetimeCutoff) {
|
|
590
|
+
// Dispatch has kept failing (or was never delivered) long enough that
|
|
591
|
+
// retrying further would just repeat the same failure forever — stop
|
|
592
|
+
// refiring and surface a terminal error instead of throttling forever.
|
|
593
|
+
return failStuckQueuedA2ATask(taskId, queuedLifetimeCutoff, "The async A2A task could not be started because dispatch kept failing. Please retry the request.");
|
|
594
|
+
}
|
|
595
|
+
if (state.updatedAt <= now - A2A_QUEUED_DISPATCH_STUCK_AFTER_MS) {
|
|
596
|
+
if (!(await touchQueuedA2ATaskDispatch(taskId)))
|
|
597
|
+
return false;
|
|
598
|
+
try {
|
|
599
|
+
await fireProcessTaskDispatch(event, taskId, config);
|
|
600
|
+
}
|
|
601
|
+
catch (err) {
|
|
602
|
+
console.error("[a2a] Failed to refire stuck queued task dispatch:", err);
|
|
603
|
+
return false;
|
|
604
|
+
}
|
|
542
605
|
return true;
|
|
543
606
|
}
|
|
544
607
|
return false;
|
|
545
608
|
}
|
|
546
|
-
if (state.statusState === "processing"
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
609
|
+
if (state.statusState === "processing") {
|
|
610
|
+
const processingStuckCutoff = now - A2A_PROCESSING_STUCK_AFTER_MS;
|
|
611
|
+
const processingLifetimeCutoff = now - a2aProcessingLifetimeMaxMs();
|
|
612
|
+
const isStale = state.updatedAt <= processingStuckCutoff;
|
|
613
|
+
const isOverLifetime = state.createdAt <= processingLifetimeCutoff;
|
|
614
|
+
if (isStale || isOverLifetime) {
|
|
615
|
+
// A processor that died mid-handler may have already performed
|
|
616
|
+
// side-effectful work. Retrying from the top can duplicate artifacts, so
|
|
617
|
+
// fail deterministically and let the caller issue an intentional retry.
|
|
618
|
+
return failStuckA2ATask(taskId, processingStuckCutoff, isStale
|
|
619
|
+
? "The async A2A processor timed out before completing. Please retry the request."
|
|
620
|
+
: "The async A2A processor exceeded its maximum run time. Please retry the request.", processingLifetimeCutoff);
|
|
621
|
+
}
|
|
553
622
|
}
|
|
554
623
|
return false;
|
|
555
624
|
}
|