@agent-native/dispatch 0.17.3 → 0.17.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/actions/create-workspace-resource.d.ts +13 -13
  2. package/dist/actions/delete-workspace-resource.d.ts +13 -13
  3. package/dist/actions/provider-api-register.d.ts +10 -10
  4. package/dist/client/transactional-emails.d.ts +57 -0
  5. package/dist/client/transactional-emails.d.ts.map +1 -0
  6. package/dist/client/transactional-emails.js +116 -0
  7. package/dist/client/transactional-emails.js.map +1 -0
  8. package/dist/components/layout/Layout.d.ts.map +1 -1
  9. package/dist/components/layout/Layout.js +8 -1
  10. package/dist/components/layout/Layout.js.map +1 -1
  11. package/dist/components/transactional-email-activity.d.ts +24 -0
  12. package/dist/components/transactional-email-activity.d.ts.map +1 -0
  13. package/dist/components/transactional-email-activity.js +31 -0
  14. package/dist/components/transactional-email-activity.js.map +1 -0
  15. package/dist/components/transactional-email-metrics.d.ts +40 -0
  16. package/dist/components/transactional-email-metrics.d.ts.map +1 -0
  17. package/dist/components/transactional-email-metrics.js +44 -0
  18. package/dist/components/transactional-email-metrics.js.map +1 -0
  19. package/dist/components/transactional-email-preview.d.ts +7 -0
  20. package/dist/components/transactional-email-preview.d.ts.map +1 -0
  21. package/dist/components/transactional-email-preview.js +41 -0
  22. package/dist/components/transactional-email-preview.js.map +1 -0
  23. package/dist/routes/index.d.ts.map +1 -1
  24. package/dist/routes/index.js +2 -0
  25. package/dist/routes/index.js.map +1 -1
  26. package/dist/routes/pages/transactional-email.$appId.$id.d.ts +5 -0
  27. package/dist/routes/pages/transactional-email.$appId.$id.d.ts.map +1 -0
  28. package/dist/routes/pages/transactional-email.$appId.$id.js +144 -0
  29. package/dist/routes/pages/transactional-email.$appId.$id.js.map +1 -0
  30. package/dist/routes/pages/transactional-email.d.ts +5 -0
  31. package/dist/routes/pages/transactional-email.d.ts.map +1 -0
  32. package/dist/routes/pages/transactional-email.js +139 -0
  33. package/dist/routes/pages/transactional-email.js.map +1 -0
  34. package/dist/server/lib/thread-debug-store.d.ts.map +1 -1
  35. package/dist/server/lib/thread-debug-store.js +2 -1
  36. package/dist/server/lib/thread-debug-store.js.map +1 -1
  37. package/package.json +2 -2
  38. package/src/client/transactional-emails.spec.ts +91 -0
  39. package/src/client/transactional-emails.ts +201 -0
  40. package/src/components/layout/Layout.tsx +8 -0
  41. package/src/components/transactional-email-activity.tsx +102 -0
  42. package/src/components/transactional-email-metrics.tsx +127 -0
  43. package/src/components/transactional-email-preview.tsx +88 -0
  44. package/src/routes/index.ts +5 -0
  45. package/src/routes/pages/transactional-email.$appId.$id.tsx +362 -0
  46. package/src/routes/pages/transactional-email.tsx +571 -0
  47. package/src/server/lib/thread-debug-store.spec.ts +7 -3
  48. package/src/server/lib/thread-debug-store.ts +2 -1
@@ -6,19 +6,6 @@ declare const _default: import("@agent-native/core").ActionDefinition<{
6
6
  content: string;
7
7
  scope: "all" | "selected";
8
8
  }, {
9
- id: string;
10
- ownerEmail: string;
11
- orgId: string | null;
12
- kind: string;
13
- name: string;
14
- description: string | null;
15
- path: string;
16
- content: string;
17
- scope: string;
18
- createdBy: string;
19
- createdAt: number;
20
- updatedAt: number;
21
- } | {
22
9
  id: string;
23
10
  ownerEmail: string;
24
11
  orgId: string | null;
@@ -35,6 +22,19 @@ declare const _default: import("@agent-native/core").ActionDefinition<{
35
22
  reviewedAt: number | null;
36
23
  createdAt: number;
37
24
  updatedAt: number;
25
+ } | {
26
+ id: string;
27
+ ownerEmail: string;
28
+ orgId: string | null;
29
+ kind: string;
30
+ name: string;
31
+ description: string | null;
32
+ path: string;
33
+ content: string;
34
+ scope: string;
35
+ createdBy: string;
36
+ createdAt: number;
37
+ updatedAt: number;
38
38
  }>;
39
39
  export default _default;
40
40
  //# sourceMappingURL=create-workspace-resource.d.ts.map
@@ -1,19 +1,6 @@
1
1
  declare const _default: import("@agent-native/core").ActionDefinition<{
2
2
  id: string;
3
3
  }, {
4
- id: string;
5
- ownerEmail: string;
6
- orgId: string | null;
7
- kind: string;
8
- name: string;
9
- description: string | null;
10
- path: string;
11
- content: string;
12
- scope: string;
13
- createdBy: string;
14
- createdAt: number;
15
- updatedAt: number;
16
- } | {
17
4
  id: string;
18
5
  ownerEmail: string;
19
6
  orgId: string | null;
@@ -30,6 +17,19 @@ declare const _default: import("@agent-native/core").ActionDefinition<{
30
17
  reviewedAt: number | null;
31
18
  createdAt: number;
32
19
  updatedAt: number;
20
+ } | {
21
+ id: string;
22
+ ownerEmail: string;
23
+ orgId: string | null;
24
+ kind: string;
25
+ name: string;
26
+ description: string | null;
27
+ path: string;
28
+ content: string;
29
+ scope: string;
30
+ createdBy: string;
31
+ createdAt: number;
32
+ updatedAt: number;
33
33
  }>;
34
34
  export default _default;
35
35
  //# sourceMappingURL=delete-workspace-resource.d.ts.map
@@ -47,10 +47,9 @@ declare const _default: import("node_modules/@agent-native/core/dist").ActionDef
47
47
  notes?: string | undefined;
48
48
  scope?: "org" | "user" | undefined;
49
49
  }, {
50
- id?: undefined;
51
- provider?: undefined;
52
50
  deleted?: undefined;
53
51
  found?: undefined;
52
+ message?: undefined;
54
53
  providers: {
55
54
  id: string;
56
55
  label: string;
@@ -61,45 +60,46 @@ declare const _default: import("node_modules/@agent-native/core/dist").ActionDef
61
60
  updatedAt: number;
62
61
  }[];
63
62
  count: number;
63
+ provider?: undefined;
64
64
  registered?: undefined;
65
+ id?: undefined;
65
66
  label?: undefined;
66
- message?: undefined;
67
67
  } | {
68
- id?: undefined;
69
68
  deleted?: undefined;
69
+ message?: undefined;
70
70
  providers?: undefined;
71
71
  count?: undefined;
72
72
  found: boolean;
73
73
  provider: import("node_modules/@agent-native/core/dist/provider-api").CustomProviderConfig;
74
74
  registered?: undefined;
75
+ id?: undefined;
75
76
  label?: undefined;
76
- message?: undefined;
77
77
  } | {
78
- provider?: undefined;
79
78
  deleted?: undefined;
79
+ message?: undefined;
80
80
  providers?: undefined;
81
81
  count?: undefined;
82
+ provider?: undefined;
82
83
  found: boolean;
83
84
  id: string;
84
85
  registered?: undefined;
85
86
  label?: undefined;
86
- message?: undefined;
87
87
  } | {
88
- provider?: undefined;
89
88
  found?: undefined;
89
+ message?: undefined;
90
90
  providers?: undefined;
91
91
  count?: undefined;
92
+ provider?: undefined;
92
93
  deleted: boolean;
93
94
  id: string;
94
95
  registered?: undefined;
95
96
  label?: undefined;
96
- message?: undefined;
97
97
  } | {
98
- provider?: undefined;
99
98
  deleted?: undefined;
100
99
  found?: undefined;
101
100
  providers?: undefined;
102
101
  count?: undefined;
102
+ provider?: undefined;
103
103
  registered: boolean;
104
104
  id: string;
105
105
  label: string;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Reads each mounted app's transactional email catalog from the browser.
3
+ *
4
+ * Workspace apps are path-mounted on a single origin, so the browser can call
5
+ * another app's action endpoint directly and its own session cookie carries the
6
+ * caller's identity. Fanning out here rather than server-side keeps every app's
7
+ * existing access checks in force — Dispatch never sees a catalog the signed-in
8
+ * user could not have loaded themselves.
9
+ */
10
+ export interface AppTransactionalEmail {
11
+ id: string;
12
+ app: string;
13
+ name: string;
14
+ trigger: string;
15
+ recipient: string;
16
+ recipientLabel: string;
17
+ sender: string;
18
+ senderLabel: string;
19
+ /** null when the send log could not be read — not the same as zero sends. */
20
+ sent: number | null;
21
+ failed: number | null;
22
+ lastSentAt: number | null;
23
+ }
24
+ export interface AppEmailCatalog {
25
+ appId: string;
26
+ appName: string;
27
+ appPath: string;
28
+ emails: AppTransactionalEmail[];
29
+ /** Set when this app's catalog could not be read at all. */
30
+ error: string | null;
31
+ /** Set when the catalog loaded but its send counts did not. */
32
+ statsError: string | null;
33
+ }
34
+ export interface LocalTransactionalEmailCatalog {
35
+ app: string;
36
+ statsAvailable: boolean;
37
+ statsError: string | null;
38
+ emails: AppTransactionalEmail[];
39
+ }
40
+ export declare function aggregateSharedEmails(local: LocalTransactionalEmailCatalog, appCatalogs: AppEmailCatalog[]): {
41
+ emails: AppTransactionalEmail[];
42
+ statsError: string | null;
43
+ };
44
+ export declare function callAppAction<T>(appPath: string, action: string, params: Record<string, unknown>, method: "GET" | "POST"): Promise<T>;
45
+ export declare function fetchAppEmailCatalog(app: {
46
+ id: string;
47
+ name: string;
48
+ path: string;
49
+ }, windowDays: number): Promise<AppEmailCatalog>;
50
+ export interface EmailPreview {
51
+ subject: string;
52
+ html: string;
53
+ text: string;
54
+ }
55
+ /** Render one email's preview with dummy data, from the owning app. */
56
+ export declare function fetchEmailPreview(appPath: string, id: string): Promise<EmailPreview>;
57
+ //# sourceMappingURL=transactional-emails.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transactional-emails.d.ts","sourceRoot":"","sources":["../../src/client/transactional-emails.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,qBAAqB,EAAE,CAAC;IAChC,4DAA4D;IAC5D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,+DAA+D;IAC/D,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,8BAA8B;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,cAAc,EAAE,OAAO,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,MAAM,EAAE,qBAAqB,EAAE,CAAC;CACjC;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,8BAA8B,EACrC,WAAW,EAAE,eAAe,EAAE,GAC7B;IAAE,MAAM,EAAE,qBAAqB,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CA4ChE;AAOD,wBAAsB,aAAa,CAAC,CAAC,EACnC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,MAAM,EAAE,KAAK,GAAG,MAAM,GACrB,OAAO,CAAC,CAAC,CAAC,CAmBZ;AASD,wBAAsB,oBAAoB,CACxC,GAAG,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAC/C,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,eAAe,CAAC,CA0C1B;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,uEAAuE;AACvE,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,YAAY,CAAC,CAavB"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Reads each mounted app's transactional email catalog from the browser.
3
+ *
4
+ * Workspace apps are path-mounted on a single origin, so the browser can call
5
+ * another app's action endpoint directly and its own session cookie carries the
6
+ * caller's identity. Fanning out here rather than server-side keeps every app's
7
+ * existing access checks in force — Dispatch never sees a catalog the signed-in
8
+ * user could not have loaded themselves.
9
+ */
10
+ export function aggregateSharedEmails(local, appCatalogs) {
11
+ const catalogs = [
12
+ {
13
+ appId: local.app,
14
+ appName: local.app,
15
+ appPath: "",
16
+ emails: local.emails,
17
+ error: null,
18
+ statsError: local.statsAvailable ? null : local.statsError,
19
+ },
20
+ ...appCatalogs.filter((catalog) => catalog.appId !== local.app),
21
+ ];
22
+ const unreadable = catalogs.find((catalog) => catalog.error || catalog.statsError);
23
+ const statsAvailable = !unreadable;
24
+ return {
25
+ statsError: unreadable?.error ?? unreadable?.statsError ?? null,
26
+ emails: local.emails
27
+ .filter((email) => email.app === "core")
28
+ .map((definition) => {
29
+ if (!statsAvailable) {
30
+ return { ...definition, sent: null, failed: null, lastSentAt: null };
31
+ }
32
+ let sent = 0;
33
+ let failed = 0;
34
+ let lastSentAt = null;
35
+ for (const catalog of catalogs) {
36
+ const email = catalog.emails.find((candidate) => candidate.id === definition.id);
37
+ sent += email?.sent ?? 0;
38
+ failed += email?.failed ?? 0;
39
+ if (email?.lastSentAt != null &&
40
+ (lastSentAt === null || email.lastSentAt > lastSentAt)) {
41
+ lastSentAt = email.lastSentAt;
42
+ }
43
+ }
44
+ return { ...definition, sent, failed, lastSentAt };
45
+ }),
46
+ };
47
+ }
48
+ function actionUrl(appPath, action, query = "") {
49
+ const base = appPath.replace(/\/$/, "");
50
+ return `${base}/_agent-native/actions/${action}${query}`;
51
+ }
52
+ export async function callAppAction(appPath, action, params, method) {
53
+ const query = method === "GET"
54
+ ? `?${new URLSearchParams(Object.entries(params).map(([key, value]) => [key, String(value)]))}`
55
+ : "";
56
+ const res = await fetch(actionUrl(appPath, action, query), {
57
+ method,
58
+ credentials: "include",
59
+ headers: {
60
+ Accept: "application/json",
61
+ "Content-Type": "application/json",
62
+ "X-Agent-Native-Frontend": "1",
63
+ },
64
+ ...(method === "POST" ? { body: JSON.stringify(params) } : {}),
65
+ });
66
+ if (!res.ok)
67
+ throw new Error(`${action} failed: HTTP ${res.status}`);
68
+ return (await res.json());
69
+ }
70
+ /**
71
+ * Load one app's catalog. Never throws — a single unreachable app must not
72
+ * blank the whole screen, but its failure is returned rather than swallowed so
73
+ * the UI can say "couldn't read" instead of showing it as having no emails.
74
+ */
75
+ const CATALOG_TIMEOUT_MS = 10_000;
76
+ export async function fetchAppEmailCatalog(app, windowDays) {
77
+ const base = {
78
+ appId: app.id,
79
+ appName: app.name,
80
+ appPath: app.path,
81
+ emails: [],
82
+ error: null,
83
+ statsError: null,
84
+ };
85
+ try {
86
+ const res = await fetch(actionUrl(app.path, "list-transactional-emails", `?windowDays=${windowDays}`), {
87
+ credentials: "include",
88
+ headers: { Accept: "application/json" },
89
+ signal: AbortSignal.timeout(CATALOG_TIMEOUT_MS),
90
+ });
91
+ if (!res.ok) {
92
+ return { ...base, error: `HTTP ${res.status}` };
93
+ }
94
+ const body = (await res.json());
95
+ return {
96
+ ...base,
97
+ emails: body.emails ?? [],
98
+ statsError: body.statsAvailable === false ? (body.statsError ?? "unknown") : null,
99
+ };
100
+ }
101
+ catch (error) {
102
+ return {
103
+ ...base,
104
+ error: error instanceof Error ? error.message : String(error),
105
+ };
106
+ }
107
+ }
108
+ /** Render one email's preview with dummy data, from the owning app. */
109
+ export async function fetchEmailPreview(appPath, id) {
110
+ const res = await fetch(actionUrl(appPath, "render-transactional-email-preview", `?id=${encodeURIComponent(id)}`), { credentials: "include", headers: { Accept: "application/json" } });
111
+ if (!res.ok) {
112
+ throw new Error(`Preview failed: HTTP ${res.status}`);
113
+ }
114
+ return (await res.json());
115
+ }
116
+ //# sourceMappingURL=transactional-emails.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transactional-emails.js","sourceRoot":"","sources":["../../src/client/transactional-emails.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Reads each mounted app's transactional email catalog from the browser.\n *\n * Workspace apps are path-mounted on a single origin, so the browser can call\n * another app's action endpoint directly and its own session cookie carries the\n * caller's identity. Fanning out here rather than server-side keeps every app's\n * existing access checks in force — Dispatch never sees a catalog the signed-in\n * user could not have loaded themselves.\n */\n\nexport interface AppTransactionalEmail {\n id: string;\n app: string;\n name: string;\n trigger: string;\n recipient: string;\n recipientLabel: string;\n sender: string;\n senderLabel: string;\n /** null when the send log could not be read — not the same as zero sends. */\n sent: number | null;\n failed: number | null;\n lastSentAt: number | null;\n}\n\nexport interface AppEmailCatalog {\n appId: string;\n appName: string;\n appPath: string;\n emails: AppTransactionalEmail[];\n /** Set when this app's catalog could not be read at all. */\n error: string | null;\n /** Set when the catalog loaded but its send counts did not. */\n statsError: string | null;\n}\n\nexport interface LocalTransactionalEmailCatalog {\n app: string;\n statsAvailable: boolean;\n statsError: string | null;\n emails: AppTransactionalEmail[];\n}\n\nexport function aggregateSharedEmails(\n local: LocalTransactionalEmailCatalog,\n appCatalogs: AppEmailCatalog[],\n): { emails: AppTransactionalEmail[]; statsError: string | null } {\n const catalogs = [\n {\n appId: local.app,\n appName: local.app,\n appPath: \"\",\n emails: local.emails,\n error: null,\n statsError: local.statsAvailable ? null : local.statsError,\n },\n ...appCatalogs.filter((catalog) => catalog.appId !== local.app),\n ];\n const unreadable = catalogs.find(\n (catalog) => catalog.error || catalog.statsError,\n );\n const statsAvailable = !unreadable;\n\n return {\n statsError: unreadable?.error ?? unreadable?.statsError ?? null,\n emails: local.emails\n .filter((email) => email.app === \"core\")\n .map((definition) => {\n if (!statsAvailable) {\n return { ...definition, sent: null, failed: null, lastSentAt: null };\n }\n let sent = 0;\n let failed = 0;\n let lastSentAt: number | null = null;\n for (const catalog of catalogs) {\n const email = catalog.emails.find(\n (candidate) => candidate.id === definition.id,\n );\n sent += email?.sent ?? 0;\n failed += email?.failed ?? 0;\n if (\n email?.lastSentAt != null &&\n (lastSentAt === null || email.lastSentAt > lastSentAt)\n ) {\n lastSentAt = email.lastSentAt;\n }\n }\n return { ...definition, sent, failed, lastSentAt };\n }),\n };\n}\n\nfunction actionUrl(appPath: string, action: string, query = \"\"): string {\n const base = appPath.replace(/\\/$/, \"\");\n return `${base}/_agent-native/actions/${action}${query}`;\n}\n\nexport async function callAppAction<T>(\n appPath: string,\n action: string,\n params: Record<string, unknown>,\n method: \"GET\" | \"POST\",\n): Promise<T> {\n const query =\n method === \"GET\"\n ? `?${new URLSearchParams(\n Object.entries(params).map(([key, value]) => [key, String(value)]),\n )}`\n : \"\";\n const res = await fetch(actionUrl(appPath, action, query), {\n method,\n credentials: \"include\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n \"X-Agent-Native-Frontend\": \"1\",\n },\n ...(method === \"POST\" ? { body: JSON.stringify(params) } : {}),\n });\n if (!res.ok) throw new Error(`${action} failed: HTTP ${res.status}`);\n return (await res.json()) as T;\n}\n\n/**\n * Load one app's catalog. Never throws — a single unreachable app must not\n * blank the whole screen, but its failure is returned rather than swallowed so\n * the UI can say \"couldn't read\" instead of showing it as having no emails.\n */\nconst CATALOG_TIMEOUT_MS = 10_000;\n\nexport async function fetchAppEmailCatalog(\n app: { id: string; name: string; path: string },\n windowDays: number,\n): Promise<AppEmailCatalog> {\n const base: AppEmailCatalog = {\n appId: app.id,\n appName: app.name,\n appPath: app.path,\n emails: [],\n error: null,\n statsError: null,\n };\n try {\n const res = await fetch(\n actionUrl(\n app.path,\n \"list-transactional-emails\",\n `?windowDays=${windowDays}`,\n ),\n {\n credentials: \"include\",\n headers: { Accept: \"application/json\" },\n signal: AbortSignal.timeout(CATALOG_TIMEOUT_MS),\n },\n );\n if (!res.ok) {\n return { ...base, error: `HTTP ${res.status}` };\n }\n const body = (await res.json()) as {\n emails?: AppTransactionalEmail[];\n statsAvailable?: boolean;\n statsError?: string | null;\n };\n return {\n ...base,\n emails: body.emails ?? [],\n statsError:\n body.statsAvailable === false ? (body.statsError ?? \"unknown\") : null,\n };\n } catch (error) {\n return {\n ...base,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\nexport interface EmailPreview {\n subject: string;\n html: string;\n text: string;\n}\n\n/** Render one email's preview with dummy data, from the owning app. */\nexport async function fetchEmailPreview(\n appPath: string,\n id: string,\n): Promise<EmailPreview> {\n const res = await fetch(\n actionUrl(\n appPath,\n \"render-transactional-email-preview\",\n `?id=${encodeURIComponent(id)}`,\n ),\n { credentials: \"include\", headers: { Accept: \"application/json\" } },\n );\n if (!res.ok) {\n throw new Error(`Preview failed: HTTP ${res.status}`);\n }\n return (await res.json()) as EmailPreview;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"Layout.d.ts","sourceRoot":"","sources":["../../../src/components/layout/Layout.tsx"],"names":[],"mappings":"AA8CA,OAAO,EAKL,KAAK,aAAa,EAClB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAgBf,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,YAAY,CAAC;AAE1D,MAAM,MAAM,eAAe,GAAG,aAAa,CAAC;IAC1C,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC,CAAC;AAEH,MAAM,WAAW,eAAe;IAC9B,uEAAuE;IACvE,EAAE,EAAE,MAAM,CAAC;IACX,iFAAiF;IACjF,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,mFAAmF;IACnF,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,gEAAgE;IAChE,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;CACvC;AAED,MAAM,WAAW,uBAAuB;IACtC,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACtC,kFAAkF;IAClF,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC/B;AA2ND,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,SAAa,UAWlE;AAmPD,wBAAgB,UAAU,CAAC,EACzB,UAAU,EACV,UAAU,EACV,SAAiB,EACjB,WAAmB,EACnB,iBAAiB,GAClB,EAAE;IACD,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,UAAU,CAAC,EAAE,uBAAuB,CAAC;IACrC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;CAClD,mDA8RA;AAED,wBAAgB,MAAM,CAAC,EACrB,QAAQ,EACR,UAAU,EACV,aAAa,GACd,EAAE;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,UAAU,CAAC,EAAE,uBAAuB,CAAC;IACrC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,mDA6IA"}
1
+ {"version":3,"file":"Layout.d.ts","sourceRoot":"","sources":["../../../src/components/layout/Layout.tsx"],"names":[],"mappings":"AA+CA,OAAO,EAKL,KAAK,aAAa,EAClB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAgBf,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,YAAY,CAAC;AAE1D,MAAM,MAAM,eAAe,GAAG,aAAa,CAAC;IAC1C,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC,CAAC;AAEH,MAAM,WAAW,eAAe;IAC9B,uEAAuE;IACvE,EAAE,EAAE,MAAM,CAAC;IACX,iFAAiF;IACjF,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,mFAAmF;IACnF,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,gEAAgE;IAChE,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;CACvC;AAED,MAAM,WAAW,uBAAuB;IACtC,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACtC,kFAAkF;IAClF,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC/B;AAkOD,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,SAAa,UAWlE;AAmPD,wBAAgB,UAAU,CAAC,EACzB,UAAU,EACV,UAAU,EACV,SAAiB,EACjB,WAAmB,EACnB,iBAAiB,GAClB,EAAE;IACD,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,UAAU,CAAC,EAAE,uBAAuB,CAAC;IACrC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;CAClD,mDA8RA;AAED,wBAAgB,MAAM,CAAC,EACrB,QAAQ,EACR,UAAU,EACV,aAAa,GACd,EAAE;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,UAAU,CAAC,EAAE,uBAAuB,CAAC;IACrC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,mDA6IA"}
@@ -7,7 +7,7 @@ import { InvitationBanner, OrgSwitcher } from "@agent-native/core/client/org";
7
7
  import { FeedbackButton } from "@agent-native/core/client/ui";
8
8
  import { SidebarFooterActions } from "@agent-native/toolkit/app-shell";
9
9
  import { ChatHistoryRail, } from "@agent-native/toolkit/chat-history";
10
- import { IconActivity, IconArrowUpRight, IconApps, IconBrain, IconBrandSlack, IconChartBar, IconBrandTelegram, IconKey, IconChevronDown, IconLayersSubtract, IconMessageQuestion, IconMessages, IconPlugConnected, IconBroadcast, IconFingerprint, IconHistory, IconLayoutSidebarLeftCollapse, IconLayoutSidebarLeftExpand, IconPuzzle, IconSettings, IconSettingsAutomation, IconShieldCheck, IconSearch, IconWorld, IconDeviceDesktop, } from "@tabler/icons-react";
10
+ import { IconActivity, IconArrowUpRight, IconApps, IconBrain, IconBrandSlack, IconChartBar, IconBrandTelegram, IconKey, IconChevronDown, IconLayersSubtract, IconMail, IconMessageQuestion, IconMessages, IconPlugConnected, IconBroadcast, IconFingerprint, IconHistory, IconLayoutSidebarLeftCollapse, IconLayoutSidebarLeftExpand, IconPuzzle, IconSettings, IconSettingsAutomation, IconShieldCheck, IconSearch, IconWorld, IconDeviceDesktop, } from "@tabler/icons-react";
11
11
  import { useEffect, useMemo, useRef, useState, } from "react";
12
12
  import { NavLink, useLocation, useNavigate } from "react-router";
13
13
  import { cn } from "../../lib/utils.js";
@@ -82,6 +82,13 @@ const OPERATIONS_NAV_ITEMS = [
82
82
  icon: IconPuzzle,
83
83
  section: "operations",
84
84
  },
85
+ {
86
+ id: "transactional-email",
87
+ to: "/transactional-email",
88
+ label: "Transactional email",
89
+ icon: IconMail,
90
+ section: "operations",
91
+ },
85
92
  {
86
93
  id: "vault",
87
94
  to: "/vault",