@agent-native/dispatch 0.8.20 → 0.8.24

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 (37) hide show
  1. package/dist/components/messaging-setup-panel.d.ts.map +1 -1
  2. package/dist/components/messaging-setup-panel.js +2 -3
  3. package/dist/components/messaging-setup-panel.js.map +1 -1
  4. package/dist/routes/pages/chat.d.ts +21 -2
  5. package/dist/routes/pages/chat.d.ts.map +1 -1
  6. package/dist/routes/pages/chat.js +12 -3
  7. package/dist/routes/pages/chat.js.map +1 -1
  8. package/dist/routes/pages/overview.d.ts +21 -2
  9. package/dist/routes/pages/overview.d.ts.map +1 -1
  10. package/dist/routes/pages/overview.js +13 -4
  11. package/dist/routes/pages/overview.js.map +1 -1
  12. package/dist/server/lib/dispatch-integrations.d.ts.map +1 -1
  13. package/dist/server/lib/dispatch-integrations.js +27 -3
  14. package/dist/server/lib/dispatch-integrations.js.map +1 -1
  15. package/dist/server/lib/mcp-gateway.d.ts.map +1 -1
  16. package/dist/server/lib/mcp-gateway.js +0 -6
  17. package/dist/server/lib/mcp-gateway.js.map +1 -1
  18. package/dist/server/lib/thread-link-preview.d.ts +24 -0
  19. package/dist/server/lib/thread-link-preview.d.ts.map +1 -0
  20. package/dist/server/lib/thread-link-preview.js +176 -0
  21. package/dist/server/lib/thread-link-preview.js.map +1 -0
  22. package/dist/server/lib/vault-store.d.ts +1 -0
  23. package/dist/server/lib/vault-store.d.ts.map +1 -1
  24. package/dist/server/lib/vault-store.js +67 -20
  25. package/dist/server/lib/vault-store.js.map +1 -1
  26. package/package.json +1 -1
  27. package/src/components/messaging-setup-panel.tsx +2 -3
  28. package/src/routes/pages/chat.tsx +20 -3
  29. package/src/routes/pages/overview.tsx +21 -8
  30. package/src/server/lib/dispatch-integrations.spec.ts +69 -0
  31. package/src/server/lib/dispatch-integrations.ts +26 -3
  32. package/src/server/lib/mcp-gateway.ts +0 -6
  33. package/src/server/lib/thread-link-preview.spec.ts +129 -0
  34. package/src/server/lib/thread-link-preview.ts +187 -0
  35. package/src/server/lib/vault-store.spec.ts +25 -0
  36. package/src/server/lib/vault-store.ts +75 -20
  37. package/src/server/lib/workspace-resource-approval-lifecycle.spec.ts +1 -1
@@ -0,0 +1,176 @@
1
+ import { getRequestContext, getThread } from "@agent-native/core/server";
2
+ const IMAGE_URL_KEYS = new Set([
3
+ "previewUrl",
4
+ "thumbnailUrl",
5
+ "imageUrl",
6
+ "image",
7
+ "downloadUrl",
8
+ ]);
9
+ const GENERATION_TOOL_NAMES = new Set([
10
+ "generate-image",
11
+ "generate-image-batch",
12
+ "refine-image",
13
+ "rerun-generation-run",
14
+ ]);
15
+ function safeJsonParse(value) {
16
+ try {
17
+ return JSON.parse(value);
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ }
23
+ function cleanUrlCandidate(value) {
24
+ return value
25
+ .trim()
26
+ .replace(/[),.;\]}]+$/g, "")
27
+ .replace(/^["'(<]+/g, "");
28
+ }
29
+ function isAbsoluteHttpUrl(value) {
30
+ try {
31
+ const url = new URL(value);
32
+ return url.protocol === "https:" || url.protocol === "http:";
33
+ }
34
+ catch {
35
+ return false;
36
+ }
37
+ }
38
+ function isImageLikeUrl(value) {
39
+ try {
40
+ const url = new URL(value);
41
+ return (/\.(?:png|jpe?g|webp|gif|avif)(?:$|[?#])/i.test(url.pathname) ||
42
+ /\/api\/assets\/[^/]+\/content(?:$|[?#])/i.test(url.pathname));
43
+ }
44
+ catch {
45
+ return false;
46
+ }
47
+ }
48
+ function validPreviewImageUrl(value, key) {
49
+ if (typeof value !== "string")
50
+ return null;
51
+ const candidate = cleanUrlCandidate(value);
52
+ if (!isAbsoluteHttpUrl(candidate))
53
+ return null;
54
+ if (key && IMAGE_URL_KEYS.has(key))
55
+ return candidate;
56
+ return isImageLikeUrl(candidate) ? candidate : null;
57
+ }
58
+ function imageUrlFromStructuredValue(value) {
59
+ if (!value || typeof value !== "object")
60
+ return null;
61
+ if (Array.isArray(value)) {
62
+ for (let i = value.length - 1; i >= 0; i--) {
63
+ const found = imageUrlFromStructuredValue(value[i]);
64
+ if (found)
65
+ return found;
66
+ }
67
+ return null;
68
+ }
69
+ const record = value;
70
+ for (const key of IMAGE_URL_KEYS) {
71
+ const found = validPreviewImageUrl(record[key], key);
72
+ if (found)
73
+ return found;
74
+ }
75
+ for (const [key, child] of Object.entries(record).reverse()) {
76
+ const direct = validPreviewImageUrl(child, key);
77
+ if (direct)
78
+ return direct;
79
+ if (child && typeof child === "object") {
80
+ const nested = imageUrlFromStructuredValue(child);
81
+ if (nested)
82
+ return nested;
83
+ }
84
+ }
85
+ return null;
86
+ }
87
+ function imageUrlFromText(value) {
88
+ const matches = value.match(/https?:\/\/[^\s<>"']+/g);
89
+ if (!matches)
90
+ return null;
91
+ for (let i = matches.length - 1; i >= 0; i--) {
92
+ const candidate = validPreviewImageUrl(matches[i]);
93
+ if (candidate)
94
+ return candidate;
95
+ }
96
+ return null;
97
+ }
98
+ export function extractThreadPreviewImageUrl(threadData) {
99
+ const parsed = safeJsonParse(threadData);
100
+ if (!parsed || typeof parsed !== "object")
101
+ return null;
102
+ const messages = parsed.messages;
103
+ if (!Array.isArray(messages))
104
+ return null;
105
+ for (let messageIndex = messages.length - 1; messageIndex >= 0; messageIndex--) {
106
+ const entry = messages[messageIndex];
107
+ const message = entry?.message ?? entry;
108
+ const content = message?.content;
109
+ if (!Array.isArray(content))
110
+ continue;
111
+ for (let partIndex = content.length - 1; partIndex >= 0; partIndex--) {
112
+ const part = content[partIndex];
113
+ const result = typeof part.result === "string" ? part.result : "";
114
+ if (!result.trim())
115
+ continue;
116
+ const toolName = typeof part.toolName === "string" ? part.toolName : "";
117
+ const parsedResult = safeJsonParse(result);
118
+ if (parsedResult && GENERATION_TOOL_NAMES.has(toolName)) {
119
+ const structured = imageUrlFromStructuredValue(parsedResult);
120
+ if (structured)
121
+ return structured;
122
+ }
123
+ const fromText = imageUrlFromText(result);
124
+ if (fromText)
125
+ return fromText;
126
+ }
127
+ }
128
+ return null;
129
+ }
130
+ function previewDescription(thread) {
131
+ const preview = thread.preview.trim();
132
+ if (preview)
133
+ return preview.slice(0, 180);
134
+ return "Open this Agent-Native thread in Dispatch.";
135
+ }
136
+ export async function loadThreadLinkPreview(threadId) {
137
+ const id = threadId?.trim();
138
+ if (!id)
139
+ return null;
140
+ const viewerEmail = getRequestContext()?.userEmail?.trim();
141
+ if (!viewerEmail)
142
+ return null;
143
+ const thread = await getThread(id).catch(() => null);
144
+ if (!thread)
145
+ return null;
146
+ if (thread.ownerEmail !== viewerEmail)
147
+ return null;
148
+ const title = thread.title.trim() || "Agent-Native thread";
149
+ return {
150
+ title,
151
+ description: previewDescription(thread),
152
+ imageUrl: extractThreadPreviewImageUrl(thread.threadData),
153
+ };
154
+ }
155
+ export function buildThreadLinkPreviewMeta(preview) {
156
+ const title = preview?.title ? `${preview.title} - Dispatch` : "Dispatch";
157
+ const description = preview?.description ||
158
+ "Open this Agent-Native thread in the Dispatch workspace.";
159
+ const image = preview?.imageUrl ?? null;
160
+ return [
161
+ { title },
162
+ { name: "description", content: description },
163
+ { property: "og:title", content: title },
164
+ { property: "og:description", content: description },
165
+ { property: "og:type", content: "website" },
166
+ ...(image ? [{ property: "og:image", content: image }] : []),
167
+ {
168
+ name: "twitter:card",
169
+ content: image ? "summary_large_image" : "summary",
170
+ },
171
+ { name: "twitter:title", content: title },
172
+ { name: "twitter:description", content: description },
173
+ ...(image ? [{ name: "twitter:image", content: image }] : []),
174
+ ];
175
+ }
176
+ //# sourceMappingURL=thread-link-preview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thread-link-preview.js","sourceRoot":"","sources":["../../../src/server/lib/thread-link-preview.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ChatThread } from \"@agent-native/core/server\";\nimport { getRequestContext, getThread } from \"@agent-native/core/server\";\n\nexport interface ThreadLinkPreview {\n title: string;\n description: string;\n imageUrl: string | null;\n}\n\nconst IMAGE_URL_KEYS = new Set([\n \"previewUrl\",\n \"thumbnailUrl\",\n \"imageUrl\",\n \"image\",\n \"downloadUrl\",\n]);\n\nconst GENERATION_TOOL_NAMES = new Set([\n \"generate-image\",\n \"generate-image-batch\",\n \"refine-image\",\n \"rerun-generation-run\",\n]);\n\nfunction safeJsonParse(value: string): unknown {\n try {\n return JSON.parse(value);\n } catch {\n return null;\n }\n}\n\nfunction cleanUrlCandidate(value: string): string {\n return value\n .trim()\n .replace(/[),.;\\]}]+$/g, \"\")\n .replace(/^[\"'(<]+/g, \"\");\n}\n\nfunction isAbsoluteHttpUrl(value: string): boolean {\n try {\n const url = new URL(value);\n return url.protocol === \"https:\" || url.protocol === \"http:\";\n } catch {\n return false;\n }\n}\n\nfunction isImageLikeUrl(value: string): boolean {\n try {\n const url = new URL(value);\n return (\n /\\.(?:png|jpe?g|webp|gif|avif)(?:$|[?#])/i.test(url.pathname) ||\n /\\/api\\/assets\\/[^/]+\\/content(?:$|[?#])/i.test(url.pathname)\n );\n } catch {\n return false;\n }\n}\n\nfunction validPreviewImageUrl(value: unknown, key?: string): string | null {\n if (typeof value !== \"string\") return null;\n const candidate = cleanUrlCandidate(value);\n if (!isAbsoluteHttpUrl(candidate)) return null;\n if (key && IMAGE_URL_KEYS.has(key)) return candidate;\n return isImageLikeUrl(candidate) ? candidate : null;\n}\n\nfunction imageUrlFromStructuredValue(value: unknown): string | null {\n if (!value || typeof value !== \"object\") return null;\n if (Array.isArray(value)) {\n for (let i = value.length - 1; i >= 0; i--) {\n const found = imageUrlFromStructuredValue(value[i]);\n if (found) return found;\n }\n return null;\n }\n\n const record = value as Record<string, unknown>;\n for (const key of IMAGE_URL_KEYS) {\n const found = validPreviewImageUrl(record[key], key);\n if (found) return found;\n }\n for (const [key, child] of Object.entries(record).reverse()) {\n const direct = validPreviewImageUrl(child, key);\n if (direct) return direct;\n if (child && typeof child === \"object\") {\n const nested = imageUrlFromStructuredValue(child);\n if (nested) return nested;\n }\n }\n return null;\n}\n\nfunction imageUrlFromText(value: string): string | null {\n const matches = value.match(/https?:\\/\\/[^\\s<>\"']+/g);\n if (!matches) return null;\n for (let i = matches.length - 1; i >= 0; i--) {\n const candidate = validPreviewImageUrl(matches[i]);\n if (candidate) return candidate;\n }\n return null;\n}\n\nexport function extractThreadPreviewImageUrl(\n threadData: string,\n): string | null {\n const parsed = safeJsonParse(threadData);\n if (!parsed || typeof parsed !== \"object\") return null;\n const messages = (parsed as { messages?: unknown }).messages;\n if (!Array.isArray(messages)) return null;\n\n for (\n let messageIndex = messages.length - 1;\n messageIndex >= 0;\n messageIndex--\n ) {\n const entry = messages[messageIndex] as any;\n const message = entry?.message ?? entry;\n const content = message?.content;\n if (!Array.isArray(content)) continue;\n\n for (let partIndex = content.length - 1; partIndex >= 0; partIndex--) {\n const part = content[partIndex] as Record<string, unknown>;\n const result = typeof part.result === \"string\" ? part.result : \"\";\n if (!result.trim()) continue;\n\n const toolName = typeof part.toolName === \"string\" ? part.toolName : \"\";\n const parsedResult = safeJsonParse(result);\n if (parsedResult && GENERATION_TOOL_NAMES.has(toolName)) {\n const structured = imageUrlFromStructuredValue(parsedResult);\n if (structured) return structured;\n }\n\n const fromText = imageUrlFromText(result);\n if (fromText) return fromText;\n }\n }\n return null;\n}\n\nfunction previewDescription(thread: ChatThread): string {\n const preview = thread.preview.trim();\n if (preview) return preview.slice(0, 180);\n return \"Open this Agent-Native thread in Dispatch.\";\n}\n\nexport async function loadThreadLinkPreview(\n threadId: string | null | undefined,\n): Promise<ThreadLinkPreview | null> {\n const id = threadId?.trim();\n if (!id) return null;\n const viewerEmail = getRequestContext()?.userEmail?.trim();\n if (!viewerEmail) return null;\n const thread = await getThread(id).catch(() => null);\n if (!thread) return null;\n if (thread.ownerEmail !== viewerEmail) return null;\n const title = thread.title.trim() || \"Agent-Native thread\";\n return {\n title,\n description: previewDescription(thread),\n imageUrl: extractThreadPreviewImageUrl(thread.threadData),\n };\n}\n\nexport function buildThreadLinkPreviewMeta(preview: ThreadLinkPreview | null) {\n const title = preview?.title ? `${preview.title} - Dispatch` : \"Dispatch\";\n const description =\n preview?.description ||\n \"Open this Agent-Native thread in the Dispatch workspace.\";\n const image = preview?.imageUrl ?? null;\n return [\n { title },\n { name: \"description\", content: description },\n { property: \"og:title\", content: title },\n { property: \"og:description\", content: description },\n { property: \"og:type\", content: \"website\" },\n ...(image ? [{ property: \"og:image\", content: image }] : []),\n {\n name: \"twitter:card\",\n content: image ? \"summary_large_image\" : \"summary\",\n },\n { name: \"twitter:title\", content: title },\n { name: \"twitter:description\", content: description },\n ...(image ? [{ name: \"twitter:image\", content: image }] : []),\n ];\n}\n"]}
@@ -25,6 +25,7 @@ export interface VaultCtx {
25
25
  * leaked rows across tenants when a misconfigured environment skipped auth.
26
26
  */
27
27
  export declare function requireVaultCtx(): VaultCtx;
28
+ export declare function isTrustedEnvVarSyncAgentUrl(agentUrl: string): boolean;
28
29
  export declare function getVaultAccessSettings(): Promise<VaultAccessSettings>;
29
30
  export declare function setVaultAccessSettings(input: {
30
31
  mode: VaultAccessMode;
@@ -1 +1 @@
1
- {"version":3,"file":"vault-store.d.ts","sourceRoot":"","sources":["../../../src/server/lib/vault-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"vault-store.d.ts","sourceRoot":"","sources":["../../../src/server/lib/vault-store.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import crypto from "node:crypto";
2
2
  import { and, desc, eq, isNull, or } from "drizzle-orm";
3
3
  import { discoverAgents } from "@agent-native/core/server/agent-discovery";
4
+ import { ssrfSafeFetch } from "@agent-native/core/extensions/url-safety";
4
5
  import { deleteAppSecret, listAppSecretsForScope, writeAppSecret, } from "@agent-native/core/secrets";
5
6
  import { getOrgSetting, getUserSetting, putOrgSetting, putUserSetting, } from "@agent-native/core/settings";
6
7
  import { getDb, schema } from "../../db/index.js";
@@ -44,6 +45,43 @@ function now() {
44
45
  function safeJson(value) {
45
46
  return JSON.stringify(value ?? null);
46
47
  }
48
+ function workspaceBaseOrigins() {
49
+ const out = new Set();
50
+ for (const value of [
51
+ process.env.WORKSPACE_GATEWAY_URL,
52
+ process.env.APP_URL,
53
+ process.env.URL,
54
+ process.env.DEPLOY_URL,
55
+ process.env.BETTER_AUTH_URL,
56
+ ]) {
57
+ if (!value)
58
+ continue;
59
+ try {
60
+ out.add(new URL(value).origin);
61
+ }
62
+ catch {
63
+ // Ignore malformed deploy metadata.
64
+ }
65
+ }
66
+ return out;
67
+ }
68
+ export function isTrustedEnvVarSyncAgentUrl(agentUrl) {
69
+ let parsed;
70
+ try {
71
+ parsed = new URL(agentUrl);
72
+ }
73
+ catch {
74
+ return false;
75
+ }
76
+ const hostname = parsed.hostname.toLowerCase();
77
+ if (hostname === "localhost" ||
78
+ hostname === "127.0.0.1" ||
79
+ hostname === "::1" ||
80
+ hostname.endsWith(".localhost")) {
81
+ return true;
82
+ }
83
+ return workspaceBaseOrigins().has(parsed.origin);
84
+ }
47
85
  function scopedFilter(table) {
48
86
  return ctxScope(table, requireVaultCtx());
49
87
  }
@@ -517,27 +555,36 @@ export async function syncGrantsToApp(appId, ctx = requireVaultCtx()) {
517
555
  // still read process.env directly. Production/shared-DB apps intentionally
518
556
  // reject env writes; the encrypted app_secrets sync above is the canonical
519
557
  // path for request-scoped credentials.
520
- try {
521
- const res = await fetch(`${agent.url}/_agent-native/env-vars`, {
522
- method: "POST",
523
- headers: { "Content-Type": "application/json" },
524
- body: JSON.stringify({ vars }),
525
- });
526
- if (res.ok) {
527
- const result = await res.json();
528
- envVarSync = { status: "synced", keys: result.saved || [] };
529
- }
530
- else {
531
- const err = await res.text().catch(() => "Unknown error");
532
- envVarSync = { status: "skipped", reason: err };
533
- }
534
- }
535
- catch (err) {
558
+ if (!isTrustedEnvVarSyncAgentUrl(agent.url)) {
536
559
  envVarSync = {
537
- status: "failed",
538
- reason: err instanceof Error ? err.message : String(err),
560
+ status: "skipped",
561
+ reason: "env-var sync is limited to localhost or workspace-owned apps",
539
562
  };
540
563
  }
564
+ else {
565
+ try {
566
+ const res = await ssrfSafeFetch(`${agent.url}/_agent-native/env-vars`, {
567
+ method: "POST",
568
+ headers: { "Content-Type": "application/json" },
569
+ body: JSON.stringify({ vars }),
570
+ signal: AbortSignal.timeout(10_000),
571
+ }, { maxRedirects: 3 });
572
+ if (res.ok) {
573
+ const result = await res.json();
574
+ envVarSync = { status: "synced", keys: result.saved || [] };
575
+ }
576
+ else {
577
+ const err = await res.text().catch(() => "Unknown error");
578
+ envVarSync = { status: "skipped", reason: err };
579
+ }
580
+ }
581
+ catch (err) {
582
+ envVarSync = {
583
+ status: "failed",
584
+ reason: err instanceof Error ? err.message : String(err),
585
+ };
586
+ }
587
+ }
541
588
  const syncedKeys = credentialStoreSync.keys;
542
589
  const timestamp = now();
543
590
  // Update syncedAt on grants that were successfully pushed to the shared
@@ -712,9 +759,9 @@ export async function listIntegrationsCatalog() {
712
759
  const results = [];
713
760
  for (const agent of agents) {
714
761
  try {
715
- const res = await fetch(`${agent.url}/_agent-native/env-status`, {
762
+ const res = await ssrfSafeFetch(`${agent.url}/_agent-native/env-status`, {
716
763
  signal: AbortSignal.timeout(3000),
717
- });
764
+ }, { maxRedirects: 3 });
718
765
  if (!res.ok) {
719
766
  results.push({
720
767
  appId: agent.id,