@agent-native/core 0.122.0 → 0.122.1
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/README.md +4 -12
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +9 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/client.ts +57 -4
- package/corpus/core/src/client/settings/SettingsPanel.tsx +9 -9
- package/corpus/core/src/client/settings/SettingsTabsPage.tsx +12 -2
- package/corpus/core/src/extensions/url-safety.ts +67 -4
- package/corpus/core/src/templates/chat/app/components/layout/Sidebar.tsx +0 -7
- package/corpus/core/src/templates/default/app/root.tsx +1 -1
- package/corpus/core/src/templates/default/public/manifest.json +2 -2
- package/corpus/core/src/vite/client.ts +64 -3
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +1 -23
- package/corpus/templates/analytics/app/routes/_index.tsx +1 -1
- package/corpus/templates/analytics/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/analytics/package.json +1 -1
- package/corpus/templates/analytics/public/manifest.json +1 -1
- package/corpus/templates/analytics/server/plugins/auth.ts +1 -1
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +0 -2
- package/corpus/templates/assets/app/routes/_index.tsx +1 -1
- package/corpus/templates/assets/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/assets/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/assets/server/plugins/auth.ts +1 -1
- package/corpus/templates/brain/app/lib/brain.ts +0 -7
- package/corpus/templates/brain/app/routes/_index.tsx +1 -2
- package/corpus/templates/brain/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/brain/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/brain/package.json +1 -1
- package/corpus/templates/brain/public/manifest.json +1 -1
- package/corpus/templates/brain/server/plugins/auth.ts +1 -1
- package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +0 -6
- package/corpus/templates/calendar/app/routes/_app._index.tsx +1 -1
- package/corpus/templates/calendar/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/calendar/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/calendar/package.json +1 -1
- package/corpus/templates/calendar/public/manifest.json +1 -1
- package/corpus/templates/calendar/server/plugins/auth.ts +1 -1
- package/corpus/templates/chat/app/components/layout/Sidebar.tsx +0 -7
- package/corpus/templates/chat/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/clips/actions/import-loom-recording.ts +53 -98
- package/corpus/templates/clips/actions/lib/loom-import-job.ts +260 -0
- package/corpus/templates/clips/app/components/library/library-layout.tsx +0 -7
- package/corpus/templates/clips/app/routes/_app.library._index.tsx +1 -1
- package/corpus/templates/clips/app/routes/_index.tsx +1 -1
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +6 -0
- package/corpus/templates/clips/app/routes/record.tsx +5 -0
- package/corpus/templates/clips/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/clips/server/db/schema.ts +2 -0
- package/corpus/templates/clips/server/lib/post-finalize-dispatch.ts +2 -1
- package/corpus/templates/clips/server/plugins/auth.ts +1 -1
- package/corpus/templates/clips/server/plugins/db.ts +8 -0
- package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +56 -3
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +1 -39
- package/corpus/templates/content/app/routes/_app._index.tsx +1 -2
- package/corpus/templates/content/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/content/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/content/package.json +1 -1
- package/corpus/templates/content/public/manifest.json +1 -1
- package/corpus/templates/content/server/plugins/auth.ts +1 -1
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +0 -2
- package/corpus/templates/design/app/routes/_index.tsx +1 -1
- package/corpus/templates/design/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/design/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/design/server/plugins/auth.ts +1 -1
- package/corpus/templates/dispatch/app/dispatch-extensions.tsx +1 -10
- package/corpus/templates/dispatch/app/routes/overview.tsx +1 -1
- package/corpus/templates/dispatch/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/dispatch/package.json +1 -1
- package/corpus/templates/dispatch/public/manifest.json +1 -1
- package/corpus/templates/forms/app/components/layout/Sidebar.tsx +0 -35
- package/corpus/templates/forms/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/forms/package.json +1 -1
- package/corpus/templates/forms/public/manifest.json +1 -1
- package/corpus/templates/forms/server/plugins/auth.ts +1 -1
- package/corpus/templates/macros/app/components/layout/AppLayout.tsx +0 -2
- package/corpus/templates/macros/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/macros/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/macros/package.json +1 -1
- package/corpus/templates/macros/server/plugins/auth.ts +2 -2
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +0 -39
- package/corpus/templates/mail/app/routes/$view.$threadId.tsx +1 -1
- package/corpus/templates/mail/app/routes/$view.tsx +1 -1
- package/corpus/templates/mail/app/routes/_index.tsx +1 -1
- package/corpus/templates/mail/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/mail/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/mail/package.json +1 -1
- package/corpus/templates/mail/public/manifest.json +1 -1
- package/corpus/templates/mail/server/plugins/auth.ts +1 -1
- package/corpus/templates/plan/agent-native.app-skill.json +1 -1
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +4 -9
- package/corpus/templates/plan/app/lib/app-config.ts +1 -1
- package/corpus/templates/plan/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/plan/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/plan/package.json +1 -1
- package/corpus/templates/plan/public/manifest.json +1 -1
- package/corpus/templates/plan/server/plugins/auth.ts +1 -1
- package/corpus/templates/slides/app/components/layout/Sidebar.tsx +0 -2
- package/corpus/templates/slides/app/routes/_index.tsx +1 -1
- package/corpus/templates/slides/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/slides/package.json +1 -1
- package/corpus/templates/slides/public/manifest.json +1 -1
- package/corpus/templates/slides/server/plugins/auth.ts +1 -1
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +58 -4
- package/dist/a2a/client.js.map +1 -1
- package/dist/client/settings/SettingsPanel.js +9 -9
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/SettingsTabsPage.d.ts.map +1 -1
- package/dist/client/settings/SettingsTabsPage.js +7 -4
- package/dist/client/settings/SettingsTabsPage.js.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/extensions/url-safety.d.ts +9 -16
- package/dist/extensions/url-safety.d.ts.map +1 -1
- package/dist/extensions/url-safety.js +48 -4
- package/dist/extensions/url-safety.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +3 -3
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/chat/app/components/layout/Sidebar.tsx +0 -7
- package/dist/templates/chat/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/dist/templates/default/app/root.tsx +1 -1
- package/dist/templates/default/public/manifest.json +2 -2
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +64 -1
- package/dist/vite/client.js.map +1 -1
- package/package.json +1 -1
- package/src/a2a/client.ts +57 -4
- package/src/client/settings/SettingsPanel.tsx +9 -9
- package/src/client/settings/SettingsTabsPage.tsx +12 -2
- package/src/extensions/url-safety.ts +67 -4
- package/src/templates/chat/app/components/layout/Sidebar.tsx +0 -7
- package/src/templates/chat/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/src/templates/default/app/root.tsx +1 -1
- package/src/templates/default/public/manifest.json +2 -2
- package/src/vite/client.ts +64 -3
package/README.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Agent-Native
|
|
2
2
|
|
|
3
|
+
|
|
3
4
|
## The framework for agentic apps
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
Don't pick between apps or agents. Agent-Native apps are both.
|
|
6
7
|
|
|
7
8
|
```ts
|
|
8
9
|
// One action powers every app surface: UI, agent, HTTP, MCP, A2A, and CLI.
|
|
@@ -22,16 +23,7 @@ export default defineAction({
|
|
|
22
23
|
- **[Backend agnostic](https://agent-native.com/docs/database)**: Plug in any Drizzle-supported SQL database and Nitro-compatible host.
|
|
23
24
|
- **[Toolkits](https://agent-native.com/docs/agent-native-toolkit)**: Reusable building blocks for collaboration, sharing, settings, teams, and observability.
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
Agent-native apps are both
|
|
28
|
-
|
|
29
|
-
| | SaaS Tools | Raw AI Agents | Internal Tools | Agent-Native App |
|
|
30
|
-
| ----------------- | ------------------ | ----------------------- | -------------------------- | ----------------------- |
|
|
31
|
-
| **UI** | Polished but rigid | None | Mixed quality | Full UI, fork & go |
|
|
32
|
-
| **AI** | Bolted on | Powerful | Shallowly connected | Agent-first, integrated |
|
|
33
|
-
| **Customization** | Can't | Instructions and skills | Full, but high maintenance | Agent modifies the app |
|
|
34
|
-
| **Ownership** | Rented | Somewhat yours | You own the code | You own the code |
|
|
26
|
+
https://github.com/user-attachments/assets/ef51644b-6506-46d8-8083-0af7b7e5b65c
|
|
35
27
|
|
|
36
28
|
## Try an Agent-Native app
|
|
37
29
|
|
|
@@ -123,7 +115,7 @@ pnpm install
|
|
|
123
115
|
pnpm dev
|
|
124
116
|
```
|
|
125
117
|
|
|
126
|
-
Prefer flags? `create my-app --template
|
|
118
|
+
Prefer flags? `create my-app --template chat`, `--headless`, or `--standalone` skip the prompt.
|
|
127
119
|
|
|
128
120
|
See the full [getting started docs](https://agent-native.com/docs).
|
|
129
121
|
|
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.122.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c8a0bcf: Sign the A2A identity token's audience as the target's origin. Receivers derive their expected audience from `APP_URL`, which carries no app path, so a path-qualified audience could never match — every direct `actions/invoke` between workspace apps failed with "Invalid or expired A2A token".
|
|
8
|
+
- c8a0bcf: Allow agent-to-agent calls between apps in a locally running workspace. The gateway serves every app on loopback, so sibling A2A targets are private addresses by construction and `ssrfSafeFetch` rejected them as SSRF — making cross-app delegation fail locally with "refusing to fetch private/internal address". `ssrfSafeFetch` now accepts an `allowedPrivateOrigins` list and the A2A client passes the deployment's own gateway origin (never a request-supplied value).
|
|
9
|
+
- c8a0bcf: Bound Nitro dev-server startup recovery so a failed restart stops refreshing forever.
|
|
10
|
+
- c8a0bcf: Keep Manage agent as a dedicated linked destination at the bottom of settings navigation.
|
|
11
|
+
|
|
3
12
|
## 0.122.0
|
|
4
13
|
|
|
5
14
|
### Minor Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.122.
|
|
3
|
+
"version": "0.122.1",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -1,6 +1,50 @@
|
|
|
1
1
|
import * as jose from "jose";
|
|
2
2
|
|
|
3
3
|
import { ssrfSafeFetch } from "../extensions/url-safety.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A workspace serves every app from one gateway on loopback, so sibling A2A
|
|
7
|
+
* targets are private addresses by construction and the SSRF guard cannot tell
|
|
8
|
+
* them apart from an attack. Trust only origins this deployment configured for
|
|
9
|
+
* itself — never a value that arrived on a request.
|
|
10
|
+
*/
|
|
11
|
+
function workspacePrivateOrigins(): string[] {
|
|
12
|
+
const origins = [
|
|
13
|
+
process.env.WORKSPACE_GATEWAY_URL,
|
|
14
|
+
process.env.APP_URL,
|
|
15
|
+
process.env.BETTER_AUTH_URL,
|
|
16
|
+
// Escape hatch for contexts that never receive the gateway manifest (the
|
|
17
|
+
// action CLI, one-off scripts). Comma-separated origins.
|
|
18
|
+
...(process.env.AGENT_NATIVE_A2A_ALLOWED_ORIGINS ?? "").split(","),
|
|
19
|
+
]
|
|
20
|
+
.map((value) => value?.trim())
|
|
21
|
+
.filter((value): value is string => !!value);
|
|
22
|
+
|
|
23
|
+
// The gateway also hands each child the sibling manifest, and siblings are
|
|
24
|
+
// reached on their own loopback ports rather than through the gateway.
|
|
25
|
+
const raw = process.env.AGENT_NATIVE_WORKSPACE_APPS_JSON;
|
|
26
|
+
if (raw) {
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(raw);
|
|
29
|
+
const apps = Array.isArray(parsed?.apps)
|
|
30
|
+
? parsed.apps
|
|
31
|
+
: Array.isArray(parsed)
|
|
32
|
+
? parsed
|
|
33
|
+
: [];
|
|
34
|
+
for (const app of apps) {
|
|
35
|
+
const url = app?.url ?? app?.origin ?? app?.baseUrl;
|
|
36
|
+
if (typeof url === "string" && url) origins.push(url);
|
|
37
|
+
const port = app?.port;
|
|
38
|
+
if (typeof port === "number" && Number.isFinite(port)) {
|
|
39
|
+
origins.push(`http://127.0.0.1:${port}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
// A malformed manifest must not disable the SSRF guard.
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return origins;
|
|
47
|
+
}
|
|
4
48
|
import { sanitizeA2ACorrelationMetadata } from "./correlation.js";
|
|
5
49
|
import type {
|
|
6
50
|
A2AApprovedAction,
|
|
@@ -138,7 +182,7 @@ export class A2AClient {
|
|
|
138
182
|
const res = await ssrfSafeFetch(
|
|
139
183
|
endpoint,
|
|
140
184
|
{ method: "OPTIONS" },
|
|
141
|
-
{ maxRedirects: 3 },
|
|
185
|
+
{ maxRedirects: 3, allowedPrivateOrigins: workspacePrivateOrigins() },
|
|
142
186
|
);
|
|
143
187
|
if (res.status !== 404 && res.status !== 405) {
|
|
144
188
|
this.endpointCandidates = [endpoint];
|
|
@@ -237,7 +281,7 @@ export class A2AClient {
|
|
|
237
281
|
const res = await ssrfSafeFetch(
|
|
238
282
|
`${this.baseUrl}/.well-known/agent-card.json`,
|
|
239
283
|
{},
|
|
240
|
-
{ maxRedirects: 3 },
|
|
284
|
+
{ maxRedirects: 3, allowedPrivateOrigins: workspacePrivateOrigins() },
|
|
241
285
|
);
|
|
242
286
|
if (!res.ok) {
|
|
243
287
|
throw new Error(`Failed to fetch agent card (${res.status})`);
|
|
@@ -548,7 +592,7 @@ export class A2AClient {
|
|
|
548
592
|
body: JSON.stringify(body),
|
|
549
593
|
signal: controller?.signal,
|
|
550
594
|
},
|
|
551
|
-
{ maxRedirects: 3 },
|
|
595
|
+
{ maxRedirects: 3, allowedPrivateOrigins: workspacePrivateOrigins() },
|
|
552
596
|
);
|
|
553
597
|
} finally {
|
|
554
598
|
if (timer) clearTimeout(timer);
|
|
@@ -883,7 +927,16 @@ async function buildA2AApiKeyAttempts(
|
|
|
883
927
|
|
|
884
928
|
function normalizeA2AAudience(url: string): string {
|
|
885
929
|
const explicit = splitExplicitA2AEndpoint(url.replace(/\/$/, ""));
|
|
886
|
-
|
|
930
|
+
const base = (explicit?.baseUrl ?? url).replace(/\/$/, "");
|
|
931
|
+
// Receivers derive their expected audience from APP_URL, which carries no
|
|
932
|
+
// app path. In a workspace every app is mounted under one gateway origin, so
|
|
933
|
+
// signing the path-qualified URL yields an audience the receiver can never
|
|
934
|
+
// match. The origin is the identifier both sides agree on.
|
|
935
|
+
try {
|
|
936
|
+
return new URL(base).origin;
|
|
937
|
+
} catch {
|
|
938
|
+
return base;
|
|
939
|
+
}
|
|
887
940
|
}
|
|
888
941
|
|
|
889
942
|
function isA2AAuthRejection(err: unknown): boolean {
|
|
@@ -3001,18 +3001,10 @@ export function useAgentSettingsTabs(): SettingsTabItem[] {
|
|
|
3001
3001
|
const organization = searchTab("organization");
|
|
3002
3002
|
const workspace = searchTab("workspace");
|
|
3003
3003
|
return [
|
|
3004
|
-
{
|
|
3005
|
-
...agent,
|
|
3006
|
-
icon: IconHierarchy2,
|
|
3007
|
-
group: "agent",
|
|
3008
|
-
href: "/agent#settings",
|
|
3009
|
-
searchEntries: undefined,
|
|
3010
|
-
content: <Navigate to="/agent#settings" replace />,
|
|
3011
|
-
},
|
|
3012
3004
|
{
|
|
3013
3005
|
...connections,
|
|
3014
3006
|
icon: IconPlugConnected,
|
|
3015
|
-
group: "
|
|
3007
|
+
group: "workspace",
|
|
3016
3008
|
content: <ConnectionsSettingsContent settingsPanelProps={baseProps} />,
|
|
3017
3009
|
},
|
|
3018
3010
|
{
|
|
@@ -3047,6 +3039,14 @@ export function useAgentSettingsTabs(): SettingsTabItem[] {
|
|
|
3047
3039
|
keywords: "extensions widgets mini apps tools sandboxed apps",
|
|
3048
3040
|
content: <ExtensionsSettingsContent />,
|
|
3049
3041
|
},
|
|
3042
|
+
{
|
|
3043
|
+
...agent,
|
|
3044
|
+
icon: IconHierarchy2,
|
|
3045
|
+
group: "manage-agent",
|
|
3046
|
+
href: "/agent#settings",
|
|
3047
|
+
searchEntries: undefined,
|
|
3048
|
+
content: <Navigate to="/agent#settings" replace />,
|
|
3049
|
+
},
|
|
3050
3050
|
];
|
|
3051
3051
|
}, [baseProps]);
|
|
3052
3052
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Tabs, useDesignSystem } from "@agent-native/toolkit/design-system";
|
|
2
2
|
import {
|
|
3
|
+
IconArrowUpRight,
|
|
3
4
|
IconHistory,
|
|
4
5
|
IconSearch,
|
|
5
6
|
IconSettings,
|
|
@@ -216,6 +217,8 @@ export function SettingsTabsPage({
|
|
|
216
217
|
const hasOrganizationTab = extraTabs.some(
|
|
217
218
|
(tab) => tab.id === "organization",
|
|
218
219
|
);
|
|
220
|
+
const inlineTabs = extraTabs.filter((tab) => !tab.href);
|
|
221
|
+
const linkedTabs = extraTabs.filter((tab) => tab.href);
|
|
219
222
|
const next: SettingsTabItem[] = [
|
|
220
223
|
{
|
|
221
224
|
id: "general",
|
|
@@ -234,7 +237,7 @@ export function SettingsTabsPage({
|
|
|
234
237
|
keywords: "profile photo avatar identity signed in email name",
|
|
235
238
|
});
|
|
236
239
|
}
|
|
237
|
-
next.push(...
|
|
240
|
+
next.push(...inlineTabs);
|
|
238
241
|
if (team && !hasOrganizationTab) {
|
|
239
242
|
next.push({
|
|
240
243
|
id: "team",
|
|
@@ -249,10 +252,11 @@ export function SettingsTabsPage({
|
|
|
249
252
|
id: "whats-new",
|
|
250
253
|
label: whatsNewLabel,
|
|
251
254
|
icon: IconHistory,
|
|
252
|
-
group: "
|
|
255
|
+
group: next.at(-1)?.group ?? "app",
|
|
253
256
|
content: whatsNew,
|
|
254
257
|
});
|
|
255
258
|
}
|
|
259
|
+
next.push(...linkedTabs);
|
|
256
260
|
return next;
|
|
257
261
|
}, [
|
|
258
262
|
account,
|
|
@@ -615,6 +619,12 @@ export function SettingsTabsPage({
|
|
|
615
619
|
/>
|
|
616
620
|
) : null}
|
|
617
621
|
<span className="truncate">{tab.label}</span>
|
|
622
|
+
{tab.href ? (
|
|
623
|
+
<IconArrowUpRight
|
|
624
|
+
aria-hidden="true"
|
|
625
|
+
className="size-3.5 shrink-0 text-muted-foreground/80"
|
|
626
|
+
/>
|
|
627
|
+
) : null}
|
|
618
628
|
</>
|
|
619
629
|
);
|
|
620
630
|
if (tab.href) {
|
|
@@ -163,7 +163,32 @@ export async function isBlockedExtensionUrlWithDns(
|
|
|
163
163
|
* runtimes); the caller should fall back to the regular `fetch` path —
|
|
164
164
|
* `isBlockedExtensionUrlWithDns` will still have caught most rebinding cases.
|
|
165
165
|
*/
|
|
166
|
-
|
|
166
|
+
function normalizeLookupHostname(hostname: string): string {
|
|
167
|
+
return hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function normalizeAllowedPrivateOriginKeys(
|
|
171
|
+
origins: readonly string[],
|
|
172
|
+
): Set<string> {
|
|
173
|
+
const keys = new Set<string>();
|
|
174
|
+
for (const origin of origins) {
|
|
175
|
+
try {
|
|
176
|
+
const parsed = new URL(origin);
|
|
177
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const port = parsed.port || (parsed.protocol === "https:" ? "443" : "80");
|
|
181
|
+
keys.add(`${normalizeLookupHostname(parsed.hostname)}:${port}`);
|
|
182
|
+
} catch {
|
|
183
|
+
// Ignore malformed deployment configuration and retain the private-IP guard.
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return keys;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export async function createSsrfSafeDispatcher(
|
|
190
|
+
allowedPrivateOrigins: readonly string[] = [],
|
|
191
|
+
): Promise<unknown | null> {
|
|
167
192
|
// Keep the optional undici import opaque to Vite/Rolldown. A static
|
|
168
193
|
// `import("undici")` makes browser builds try to resolve and bundle undici
|
|
169
194
|
// even though this dispatcher is only useful in Node server runtimes.
|
|
@@ -182,6 +207,9 @@ export async function createSsrfSafeDispatcher(): Promise<unknown | null> {
|
|
|
182
207
|
|
|
183
208
|
const { Agent } = undici;
|
|
184
209
|
const { lookup } = dnsModule;
|
|
210
|
+
const allowedPrivateOriginKeys = normalizeAllowedPrivateOriginKeys(
|
|
211
|
+
allowedPrivateOrigins,
|
|
212
|
+
);
|
|
185
213
|
if (!Agent || !lookup) return null;
|
|
186
214
|
|
|
187
215
|
return new Agent({
|
|
@@ -209,7 +237,10 @@ export async function createSsrfSafeDispatcher(): Promise<unknown | null> {
|
|
|
209
237
|
? addresses
|
|
210
238
|
: [{ address: addresses, family: 4 }];
|
|
211
239
|
for (const record of list) {
|
|
212
|
-
|
|
240
|
+
const allowedOrigin = allowedPrivateOriginKeys.has(
|
|
241
|
+
`${normalizeLookupHostname(hostname)}:${String(options?.port ?? "")}`,
|
|
242
|
+
);
|
|
243
|
+
if (isPrivateHost(record.address) && !allowedOrigin) {
|
|
213
244
|
const e = new Error(
|
|
214
245
|
`Connect blocked: ${hostname} resolved to private address ${record.address}`,
|
|
215
246
|
) as NodeJS.ErrnoException;
|
|
@@ -264,10 +295,39 @@ export async function ssrfSafeFetch(
|
|
|
264
295
|
maxRedirects?: number;
|
|
265
296
|
httpsOnly?: boolean;
|
|
266
297
|
assertUrlAllowed?: (url: string) => void | Promise<void>;
|
|
298
|
+
/**
|
|
299
|
+
* Exact origins that may resolve to a private address. A workspace runs
|
|
300
|
+
* every app on loopback behind one gateway, so sibling A2A calls are
|
|
301
|
+
* private by construction; without this they are indistinguishable from an
|
|
302
|
+
* SSRF attempt and get blocked. Only ever pass origins the deployment
|
|
303
|
+
* itself configured (never a request-supplied value).
|
|
304
|
+
*/
|
|
305
|
+
allowedPrivateOrigins?: readonly string[];
|
|
267
306
|
} = {},
|
|
268
307
|
): Promise<Response> {
|
|
269
308
|
const maxRedirects = options.maxRedirects ?? 3;
|
|
270
|
-
const dispatcher =
|
|
309
|
+
const dispatcher =
|
|
310
|
+
(await createSsrfSafeDispatcher(options.allowedPrivateOrigins)) ??
|
|
311
|
+
undefined;
|
|
312
|
+
const allowedPrivateOrigins = new Set(
|
|
313
|
+
(options.allowedPrivateOrigins ?? [])
|
|
314
|
+
.map((origin) => {
|
|
315
|
+
try {
|
|
316
|
+
return new URL(origin).origin;
|
|
317
|
+
} catch {
|
|
318
|
+
return "";
|
|
319
|
+
}
|
|
320
|
+
})
|
|
321
|
+
.filter(Boolean),
|
|
322
|
+
);
|
|
323
|
+
const isAllowedPrivateOrigin = (candidate: string): boolean => {
|
|
324
|
+
if (allowedPrivateOrigins.size === 0) return false;
|
|
325
|
+
try {
|
|
326
|
+
return allowedPrivateOrigins.has(new URL(candidate).origin);
|
|
327
|
+
} catch {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
};
|
|
271
331
|
|
|
272
332
|
let currentUrl = url;
|
|
273
333
|
for (let hop = 0; hop <= maxRedirects; hop++) {
|
|
@@ -277,7 +337,10 @@ export async function ssrfSafeFetch(
|
|
|
277
337
|
`SSRF blocked: refusing to fetch non-HTTPS address (${currentUrl})`,
|
|
278
338
|
);
|
|
279
339
|
}
|
|
280
|
-
if (
|
|
340
|
+
if (
|
|
341
|
+
!isAllowedPrivateOrigin(currentUrl) &&
|
|
342
|
+
(await isBlockedExtensionUrlWithDns(currentUrl))
|
|
343
|
+
) {
|
|
281
344
|
throw new Error(
|
|
282
345
|
`SSRF blocked: refusing to fetch private/internal address (${currentUrl})`,
|
|
283
346
|
);
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
type ChatHistoryItem,
|
|
15
15
|
} from "@agent-native/toolkit/chat-history";
|
|
16
16
|
import {
|
|
17
|
-
IconHierarchy2,
|
|
18
17
|
IconLayoutSidebarLeftCollapse,
|
|
19
18
|
IconLayoutSidebarLeftExpand,
|
|
20
19
|
IconMessageCircle,
|
|
@@ -43,12 +42,6 @@ const navItems = [
|
|
|
43
42
|
];
|
|
44
43
|
|
|
45
44
|
const bottomNavItems = [
|
|
46
|
-
{
|
|
47
|
-
icon: IconHierarchy2,
|
|
48
|
-
labelKey: "settings.agentTitle",
|
|
49
|
-
href: "/agent",
|
|
50
|
-
view: "agent",
|
|
51
|
-
},
|
|
52
45
|
{
|
|
53
46
|
icon: IconSettings,
|
|
54
47
|
labelKey: "navigation.settings",
|
|
@@ -2414,6 +2414,10 @@ type NitroModuleGraph = {
|
|
|
2414
2414
|
|
|
2415
2415
|
const NITRO_STARTUP_SETTLE_MS = 1_000;
|
|
2416
2416
|
const NITRO_STARTUP_TIMEOUT_MS = 30_000;
|
|
2417
|
+
const NITRO_STARTUP_RETRY_KEY = "__agent_native_nitro_startup_retry";
|
|
2418
|
+
const NITRO_STARTUP_RETRY_MAX = 5;
|
|
2419
|
+
const NITRO_STARTUP_RETRY_DELAY_MS = 1_000;
|
|
2420
|
+
const NITRO_STARTUP_RETRY_RESET_MS = 15_000;
|
|
2417
2421
|
|
|
2418
2422
|
function nitroModuleGraphSignature(environment: unknown): string | null {
|
|
2419
2423
|
const graph = (environment as { moduleGraph?: NitroModuleGraph } | undefined)
|
|
@@ -2456,9 +2460,66 @@ function sendNitroStartingResponse(
|
|
|
2456
2460
|
res.end();
|
|
2457
2461
|
return;
|
|
2458
2462
|
}
|
|
2459
|
-
res.end(
|
|
2460
|
-
|
|
2461
|
-
|
|
2463
|
+
res.end(`<!doctype html>
|
|
2464
|
+
<html>
|
|
2465
|
+
<head>
|
|
2466
|
+
<meta charset="utf-8">
|
|
2467
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2468
|
+
<title>Dev server restarting…</title>
|
|
2469
|
+
<style>
|
|
2470
|
+
body { margin: 0; min-height: 100vh; display: grid; place-items: center; font: 16px/1.5 system-ui, sans-serif; color: #171717; background: #fafafa; }
|
|
2471
|
+
main { width: min(560px, calc(100vw - 48px)); }
|
|
2472
|
+
h1 { margin: 0 0 8px; font-size: 1.25rem; }
|
|
2473
|
+
p { margin: 0; color: #737373; }
|
|
2474
|
+
</style>
|
|
2475
|
+
</head>
|
|
2476
|
+
<body>
|
|
2477
|
+
<main>
|
|
2478
|
+
<h1>Dev server is restarting…</h1>
|
|
2479
|
+
<p id="agent-native-nitro-retry-status">Checking again shortly.</p>
|
|
2480
|
+
</main>
|
|
2481
|
+
<script>
|
|
2482
|
+
(() => {
|
|
2483
|
+
const key = ${JSON.stringify(NITRO_STARTUP_RETRY_KEY)};
|
|
2484
|
+
const maxRetries = ${NITRO_STARTUP_RETRY_MAX};
|
|
2485
|
+
const resetAfterMs = ${NITRO_STARTUP_RETRY_RESET_MS};
|
|
2486
|
+
const retryDelayMs = ${NITRO_STARTUP_RETRY_DELAY_MS};
|
|
2487
|
+
const status = document.getElementById("agent-native-nitro-retry-status");
|
|
2488
|
+
const now = Date.now();
|
|
2489
|
+
let count = 0;
|
|
2490
|
+
let lastAttemptAt = 0;
|
|
2491
|
+
|
|
2492
|
+
try {
|
|
2493
|
+
const stored = JSON.parse(sessionStorage.getItem(key) || "null");
|
|
2494
|
+
if (stored && typeof stored === "object") {
|
|
2495
|
+
count = Number.isFinite(stored.count) ? stored.count : 0;
|
|
2496
|
+
lastAttemptAt = Number.isFinite(stored.at) ? stored.at : 0;
|
|
2497
|
+
}
|
|
2498
|
+
} catch (error) {
|
|
2499
|
+
// A blocked session store is handled below by showing a manual retry.
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
if (now - lastAttemptAt > resetAfterMs) count = 0;
|
|
2503
|
+
if (count >= maxRetries) {
|
|
2504
|
+
if (status) status.textContent = "The server is still unavailable. Refresh when it is ready.";
|
|
2505
|
+
return;
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
const nextState = JSON.stringify({ count: count + 1, at: now });
|
|
2509
|
+
try {
|
|
2510
|
+
sessionStorage.setItem(key, nextState);
|
|
2511
|
+
if (sessionStorage.getItem(key) !== nextState) throw new Error("unavailable");
|
|
2512
|
+
} catch (error) {
|
|
2513
|
+
if (status) status.textContent = "Refresh manually when the server is ready.";
|
|
2514
|
+
return;
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
if (status) status.textContent = "Retrying in one second…";
|
|
2518
|
+
setTimeout(() => window.location.reload(), retryDelayMs);
|
|
2519
|
+
})();
|
|
2520
|
+
</script>
|
|
2521
|
+
</body>
|
|
2522
|
+
</html>`);
|
|
2462
2523
|
}
|
|
2463
2524
|
|
|
2464
2525
|
function nitroStartupGate(
|
|
@@ -19,14 +19,12 @@ import {
|
|
|
19
19
|
IconLock,
|
|
20
20
|
IconLink,
|
|
21
21
|
IconMessageCircle,
|
|
22
|
-
IconHierarchy2,
|
|
23
22
|
IconUsersGroup,
|
|
24
23
|
IconEye,
|
|
25
24
|
IconEyeOff,
|
|
26
25
|
IconPlayerPlay,
|
|
27
26
|
IconLayoutSidebarLeftCollapse,
|
|
28
27
|
IconLayoutSidebarLeftExpand,
|
|
29
|
-
IconArrowUpRight,
|
|
30
28
|
} from "@tabler/icons-react";
|
|
31
29
|
import {
|
|
32
30
|
useQuery,
|
|
@@ -183,12 +181,6 @@ import {
|
|
|
183
181
|
import { CSS } from "@dnd-kit/utilities";
|
|
184
182
|
|
|
185
183
|
const bottomItems = [
|
|
186
|
-
{
|
|
187
|
-
icon: IconHierarchy2,
|
|
188
|
-
labelKey: "settings.agentTitle",
|
|
189
|
-
href: "/agent",
|
|
190
|
-
showExternalLinkIcon: true,
|
|
191
|
-
},
|
|
192
184
|
{ icon: IconSettings, labelKey: "navigation.settings", href: "/settings" },
|
|
193
185
|
];
|
|
194
186
|
|
|
@@ -2098,12 +2090,6 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
|
|
|
2098
2090
|
href: "/data-dictionary",
|
|
2099
2091
|
active: location.pathname.startsWith("/data-dictionary"),
|
|
2100
2092
|
},
|
|
2101
|
-
{
|
|
2102
|
-
icon: IconHierarchy2,
|
|
2103
|
-
label: t("settings.agentTitle"),
|
|
2104
|
-
href: "/agent",
|
|
2105
|
-
active: location.pathname === "/agent",
|
|
2106
|
-
},
|
|
2107
2093
|
{
|
|
2108
2094
|
icon: IconSettings,
|
|
2109
2095
|
label: t("navigation.settings"),
|
|
@@ -2572,15 +2558,7 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
|
|
|
2572
2558
|
)}
|
|
2573
2559
|
>
|
|
2574
2560
|
<Icon className="h-4 w-4" />
|
|
2575
|
-
<span className="
|
|
2576
|
-
<span className="truncate">{t(item.labelKey)}</span>
|
|
2577
|
-
{item.showExternalLinkIcon && (
|
|
2578
|
-
<IconArrowUpRight
|
|
2579
|
-
aria-hidden="true"
|
|
2580
|
-
className="h-3.5 w-3.5 shrink-0 text-muted-foreground/70"
|
|
2581
|
-
/>
|
|
2582
|
-
)}
|
|
2583
|
-
</span>
|
|
2561
|
+
<span className="truncate">{t(item.labelKey)}</span>
|
|
2584
2562
|
</Link>
|
|
2585
2563
|
);
|
|
2586
2564
|
})}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { redirect, type LoaderFunctionArgs } from "react-router";
|
|
2
2
|
|
|
3
3
|
const SEO_TITLE =
|
|
4
|
-
"
|
|
4
|
+
"Analytics - Open Source Alternative to Amplitude & FullStory";
|
|
5
5
|
const SEO_DESCRIPTION =
|
|
6
6
|
"Open Source analytics app and alternative to Amplitude and FullStory where AI agents connect to warehouses, product analytics, and CRM data to answer questions and build dashboards.";
|
|
7
7
|
|
|
@@ -13,7 +13,7 @@ export default createAuthPlugin({
|
|
|
13
13
|
"/_agent-native/actions/get-public-status-page",
|
|
14
14
|
],
|
|
15
15
|
marketing: {
|
|
16
|
-
appName: "
|
|
16
|
+
appName: "Analytics",
|
|
17
17
|
tagline:
|
|
18
18
|
"Your AI agent queries your data sources, builds dashboards, and answers business questions alongside you.",
|
|
19
19
|
features: [
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
type ChatHistoryItem,
|
|
18
18
|
} from "@agent-native/toolkit/chat-history";
|
|
19
19
|
import {
|
|
20
|
-
IconHierarchy2,
|
|
21
20
|
IconClipboardList,
|
|
22
21
|
IconLayoutSidebarLeftCollapse,
|
|
23
22
|
IconLayoutSidebarLeftExpand,
|
|
@@ -45,7 +44,6 @@ const baseNavItems = [
|
|
|
45
44
|
];
|
|
46
45
|
|
|
47
46
|
const bottomNavItems = [
|
|
48
|
-
{ icon: IconHierarchy2, labelKey: "settings.agentTitle", href: "/agent" },
|
|
49
47
|
{ icon: IconSettings, labelKey: "navigation.settings", href: "/settings" },
|
|
50
48
|
];
|
|
51
49
|
|
|
@@ -35,7 +35,7 @@ const CHAT_STARTERS = [
|
|
|
35
35
|
] as const;
|
|
36
36
|
|
|
37
37
|
const SEO_TITLE =
|
|
38
|
-
"
|
|
38
|
+
"Assets - Open Source AI asset library for brand-safe images and video";
|
|
39
39
|
const SEO_DESCRIPTION =
|
|
40
40
|
"Open Source asset manager for AI teams to organize brand libraries, search creative work, and generate on-brand images and videos.";
|
|
41
41
|
|
|
@@ -2,7 +2,7 @@ import { createAuthPlugin } from "@agent-native/core/server";
|
|
|
2
2
|
|
|
3
3
|
export default createAuthPlugin({
|
|
4
4
|
marketing: {
|
|
5
|
-
appName: "
|
|
5
|
+
appName: "Assets",
|
|
6
6
|
tagline:
|
|
7
7
|
"Your AI agent creates, refines, and organizes on-brand assets alongside you.",
|
|
8
8
|
features: [
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { Icon } from "@tabler/icons-react";
|
|
2
2
|
import {
|
|
3
|
-
IconHierarchy2,
|
|
4
3
|
IconBook2,
|
|
5
4
|
IconChecks,
|
|
6
5
|
IconDatabase,
|
|
@@ -1007,12 +1006,6 @@ export const navItems: Array<{
|
|
|
1007
1006
|
href: "/settings",
|
|
1008
1007
|
icon: IconSettings,
|
|
1009
1008
|
},
|
|
1010
|
-
{
|
|
1011
|
-
view: "agent",
|
|
1012
|
-
label: "Manage agent",
|
|
1013
|
-
href: "/agent",
|
|
1014
|
-
icon: IconHierarchy2,
|
|
1015
|
-
},
|
|
1016
1009
|
];
|
|
1017
1010
|
|
|
1018
1011
|
export const defaultSettings: BrainSettings = {
|