@agent-native/core 0.87.0 → 0.88.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +6 -0
- package/corpus/core/docs/content/agent-native-toolkit.mdx +162 -0
- package/corpus/core/docs/content/locales/ar-SA/agent-native-toolkit.mdx +73 -0
- package/corpus/core/docs/content/locales/ar-SA/sharing.mdx +15 -7
- package/corpus/core/docs/content/locales/de-DE/agent-native-toolkit.mdx +77 -0
- package/corpus/core/docs/content/locales/de-DE/sharing.mdx +15 -7
- package/corpus/core/docs/content/locales/es-ES/agent-native-toolkit.mdx +77 -0
- package/corpus/core/docs/content/locales/es-ES/sharing.mdx +15 -7
- package/corpus/core/docs/content/locales/fr-FR/agent-native-toolkit.mdx +79 -0
- package/corpus/core/docs/content/locales/fr-FR/sharing.mdx +15 -7
- package/corpus/core/docs/content/locales/hi-IN/agent-native-toolkit.mdx +74 -0
- package/corpus/core/docs/content/locales/hi-IN/sharing.mdx +15 -7
- package/corpus/core/docs/content/locales/ja-JP/agent-native-toolkit.mdx +67 -0
- package/corpus/core/docs/content/locales/ja-JP/sharing.mdx +15 -7
- package/corpus/core/docs/content/locales/ko-KR/agent-native-toolkit.mdx +63 -0
- package/corpus/core/docs/content/locales/ko-KR/sharing.mdx +15 -7
- package/corpus/core/docs/content/locales/pt-BR/agent-native-toolkit.mdx +77 -0
- package/corpus/core/docs/content/locales/pt-BR/sharing.mdx +15 -7
- package/corpus/core/docs/content/locales/zh-CN/agent-native-toolkit.mdx +61 -0
- package/corpus/core/docs/content/locales/zh-CN/sharing.mdx +15 -7
- package/corpus/core/docs/content/locales/zh-TW/agent-native-toolkit.mdx +61 -0
- package/corpus/core/docs/content/locales/zh-TW/sharing.mdx +15 -7
- package/corpus/core/docs/content/sharing.mdx +39 -7
- package/corpus/core/package.json +2 -1
- package/corpus/core/src/server/action-discovery.ts +4 -0
- package/corpus/core/src/server/action-routes.ts +8 -1
- package/corpus/core/src/server/index.ts +9 -0
- package/corpus/core/src/shared/agent-readable-resource.ts +111 -0
- package/corpus/core/src/shared/index.ts +9 -0
- package/corpus/core/src/sharing/actions/create-agent-resource-link.ts +91 -0
- package/corpus/core/src/sharing/registry.ts +19 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +92 -0
- package/corpus/core/src/vite/client.ts +1 -0
- package/corpus/templates/analytics/actions/get-sql-dashboard.ts +9 -113
- package/corpus/templates/analytics/server/db/index.ts +14 -0
- package/corpus/templates/analytics/server/lib/agent-readable-resource-context.ts +156 -0
- package/corpus/templates/analytics/server/routes/[...page].get.ts +92 -25
- package/corpus/templates/analytics/server/routes/api/analysis-agent-context.json.get.ts +94 -0
- package/corpus/templates/analytics/server/routes/api/dashboard-agent-context.json.get.ts +109 -0
- package/corpus/templates/analytics/shared/resource-agent-access.ts +6 -0
- package/corpus/templates/content/app/routes/p.$id.tsx +114 -9
- package/corpus/templates/content/server/db/index.ts +9 -0
- package/corpus/templates/content/server/routes/api/document-agent-context.json.get.ts +90 -0
- package/corpus/templates/content/shared/agent-readable.ts +56 -0
- package/corpus/templates/design/server/db/index.ts +8 -0
- package/corpus/templates/design/server/routes/[...page].get.ts +94 -1
- package/corpus/templates/design/server/routes/api/design-agent-context.json.get.ts +84 -0
- package/corpus/templates/design/shared/agent-readable.ts +2 -0
- package/corpus/templates/plan/server/db/index.ts +12 -0
- package/corpus/templates/plan/server/plans.ts +25 -1
- package/corpus/templates/plan/server/routes/[...page].get.ts +107 -1
- package/corpus/templates/plan/server/routes/api/plan-agent-context.json.get.ts +65 -0
- package/corpus/templates/plan/shared/agent-readable.ts +2 -0
- package/corpus/templates/slides/app/routes/p.$id.tsx +126 -12
- package/corpus/templates/slides/app/routes/share.$token.tsx +55 -8
- package/corpus/templates/slides/server/db/index.ts +9 -0
- package/corpus/templates/slides/server/routes/api/deck-agent-context.json.get.ts +97 -0
- package/corpus/templates/slides/shared/agent-readable.ts +2 -0
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +4 -0
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +8 -2
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/shared/agent-readable-resource.d.ts +29 -0
- package/dist/shared/agent-readable-resource.d.ts.map +1 -0
- package/dist/shared/agent-readable-resource.js +60 -0
- package/dist/shared/agent-readable-resource.js.map +1 -0
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +1 -0
- package/dist/shared/index.js.map +1 -1
- package/dist/sharing/actions/create-agent-resource-link.d.ts +13 -0
- package/dist/sharing/actions/create-agent-resource-link.d.ts.map +1 -0
- package/dist/sharing/actions/create-agent-resource-link.js +72 -0
- package/dist/sharing/actions/create-agent-resource-link.js.map +1 -0
- package/dist/sharing/registry.d.ts +17 -0
- package/dist/sharing/registry.d.ts.map +1 -1
- package/dist/sharing/registry.js.map +1 -1
- package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +92 -0
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +1 -0
- package/dist/vite/client.js.map +1 -1
- package/docs/content/agent-native-toolkit.mdx +162 -0
- package/docs/content/locales/ar-SA/agent-native-toolkit.mdx +73 -0
- package/docs/content/locales/ar-SA/sharing.mdx +15 -7
- package/docs/content/locales/de-DE/agent-native-toolkit.mdx +77 -0
- package/docs/content/locales/de-DE/sharing.mdx +15 -7
- package/docs/content/locales/es-ES/agent-native-toolkit.mdx +77 -0
- package/docs/content/locales/es-ES/sharing.mdx +15 -7
- package/docs/content/locales/fr-FR/agent-native-toolkit.mdx +79 -0
- package/docs/content/locales/fr-FR/sharing.mdx +15 -7
- package/docs/content/locales/hi-IN/agent-native-toolkit.mdx +74 -0
- package/docs/content/locales/hi-IN/sharing.mdx +15 -7
- package/docs/content/locales/ja-JP/agent-native-toolkit.mdx +67 -0
- package/docs/content/locales/ja-JP/sharing.mdx +15 -7
- package/docs/content/locales/ko-KR/agent-native-toolkit.mdx +63 -0
- package/docs/content/locales/ko-KR/sharing.mdx +15 -7
- package/docs/content/locales/pt-BR/agent-native-toolkit.mdx +77 -0
- package/docs/content/locales/pt-BR/sharing.mdx +15 -7
- package/docs/content/locales/zh-CN/agent-native-toolkit.mdx +61 -0
- package/docs/content/locales/zh-CN/sharing.mdx +15 -7
- package/docs/content/locales/zh-TW/agent-native-toolkit.mdx +61 -0
- package/docs/content/locales/zh-TW/sharing.mdx +15 -7
- package/docs/content/sharing.mdx +39 -7
- package/package.json +2 -1
- package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +92 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const ANALYTICS_DASHBOARD_AGENT_RESOURCE_KIND = "analytics:dashboard";
|
|
2
|
+
export const ANALYTICS_ANALYSIS_AGENT_RESOURCE_KIND = "analytics:analysis";
|
|
3
|
+
export const ANALYTICS_DASHBOARD_AGENT_CONTEXT_ENDPOINT =
|
|
4
|
+
"/api/dashboard-agent-context.json";
|
|
5
|
+
export const ANALYTICS_ANALYSIS_AGENT_CONTEXT_ENDPOINT =
|
|
6
|
+
"/api/analysis-agent-context.json";
|
|
@@ -1,23 +1,82 @@
|
|
|
1
1
|
import { agentNativePath, useT } from "@agent-native/core/client";
|
|
2
|
+
import {
|
|
3
|
+
AGENT_ACCESS_PARAM,
|
|
4
|
+
verifyScopedAgentAccessToken,
|
|
5
|
+
} from "@agent-native/core/server";
|
|
2
6
|
import {
|
|
3
7
|
getConfiguredAppBasePath,
|
|
4
8
|
getRequestUserEmail,
|
|
5
9
|
} from "@agent-native/core/server";
|
|
10
|
+
import {
|
|
11
|
+
AGENT_READABLE_RESOURCE_SCRIPT_TYPE,
|
|
12
|
+
safeJsonForHtml,
|
|
13
|
+
} from "@agent-native/core/shared";
|
|
6
14
|
import { resolveAccess } from "@agent-native/core/sharing";
|
|
7
15
|
import { buildPublicDocumentDescription } from "@shared/og-description";
|
|
8
16
|
import { IconLock, IconMessageCircle } from "@tabler/icons-react";
|
|
9
17
|
import { eq } from "drizzle-orm";
|
|
10
18
|
import { useEffect, useState } from "react";
|
|
11
|
-
import type {
|
|
12
|
-
|
|
19
|
+
import type {
|
|
20
|
+
HeadersArgs,
|
|
21
|
+
LoaderFunctionArgs,
|
|
22
|
+
MetaFunction,
|
|
23
|
+
} from "react-router";
|
|
24
|
+
import { data, redirect, useLoaderData } from "react-router";
|
|
13
25
|
|
|
14
26
|
import { VisualEditor } from "@/components/editor/VisualEditor";
|
|
15
27
|
|
|
16
28
|
import { getDb, schema } from "../../server/db";
|
|
29
|
+
import {
|
|
30
|
+
buildContentDocumentAgentDiscovery,
|
|
31
|
+
buildContentPublicDocumentUrl,
|
|
32
|
+
DOCUMENT_AGENT_RESOURCE_KIND,
|
|
33
|
+
} from "../../shared/agent-readable";
|
|
34
|
+
|
|
35
|
+
type PublicDocumentLoaderData =
|
|
36
|
+
| {
|
|
37
|
+
document: {
|
|
38
|
+
id: string;
|
|
39
|
+
title: string;
|
|
40
|
+
content: string;
|
|
41
|
+
updatedAt: string;
|
|
42
|
+
visibility: string;
|
|
43
|
+
};
|
|
44
|
+
agentAccessToken: string | null;
|
|
45
|
+
basePath: string;
|
|
46
|
+
unavailable?: undefined;
|
|
47
|
+
}
|
|
48
|
+
| {
|
|
49
|
+
document: null;
|
|
50
|
+
agentAccessToken: null;
|
|
51
|
+
basePath: string;
|
|
52
|
+
unavailable: { reason: "private"; id: string; basePath: string };
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const PRIVATE_AGENT_DOCUMENT_HEADERS = {
|
|
56
|
+
"Cache-Control": "private, max-age=0, no-store",
|
|
57
|
+
"Referrer-Policy": "no-referrer",
|
|
58
|
+
};
|
|
17
59
|
|
|
18
|
-
|
|
60
|
+
function publicDocumentLoaderData(
|
|
61
|
+
payload: PublicDocumentLoaderData,
|
|
62
|
+
privateAgentAccess = false,
|
|
63
|
+
) {
|
|
64
|
+
if (!privateAgentAccess) return payload;
|
|
65
|
+
return data(payload, {
|
|
66
|
+
headers: PRIVATE_AGENT_DOCUMENT_HEADERS,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function headers({ loaderHeaders }: HeadersArgs) {
|
|
71
|
+
return loaderHeaders;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function loader({ params, request }: LoaderFunctionArgs) {
|
|
19
75
|
const id = params.id;
|
|
20
76
|
if (!id) throw new Response("Not found", { status: 404 });
|
|
77
|
+
const agentAccessToken = new URL(request.url).searchParams.get(
|
|
78
|
+
AGENT_ACCESS_PARAM,
|
|
79
|
+
);
|
|
21
80
|
|
|
22
81
|
// This is a server loader; use the server-side base-path helper
|
|
23
82
|
// (reads APP_BASE_PATH / VITE_APP_BASE_PATH at request time)
|
|
@@ -45,7 +104,22 @@ export async function loader({ params }: LoaderFunctionArgs) {
|
|
|
45
104
|
.limit(1);
|
|
46
105
|
|
|
47
106
|
if (!doc) throw new Response("Not found", { status: 404 });
|
|
48
|
-
|
|
107
|
+
const tokenAccess = agentAccessToken
|
|
108
|
+
? verifyScopedAgentAccessToken(agentAccessToken, {
|
|
109
|
+
resourceKind: DOCUMENT_AGENT_RESOURCE_KIND,
|
|
110
|
+
resourceId: id,
|
|
111
|
+
}).ok
|
|
112
|
+
: false;
|
|
113
|
+
if (doc.visibility === "public" || tokenAccess) {
|
|
114
|
+
return publicDocumentLoaderData(
|
|
115
|
+
{
|
|
116
|
+
document: doc,
|
|
117
|
+
agentAccessToken: tokenAccess ? agentAccessToken : null,
|
|
118
|
+
basePath,
|
|
119
|
+
},
|
|
120
|
+
tokenAccess,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
49
123
|
|
|
50
124
|
// Doc exists but isn't public. SSR renders impersonally (no session is read
|
|
51
125
|
// server-side, so the page can be CDN-cached for everyone), which means we
|
|
@@ -56,10 +130,12 @@ export async function loader({ params }: LoaderFunctionArgs) {
|
|
|
56
130
|
// routes the viewer to the auth-guarded `/page/<id>` editor, where the real
|
|
57
131
|
// per-user access check runs (signed-in-with-access sees the doc; everyone
|
|
58
132
|
// else gets the standard sign-in / no-access handling).
|
|
59
|
-
return {
|
|
133
|
+
return publicDocumentLoaderData({
|
|
60
134
|
document: null,
|
|
135
|
+
agentAccessToken: null,
|
|
136
|
+
basePath,
|
|
61
137
|
unavailable: { reason: "private" as const, id, basePath },
|
|
62
|
-
};
|
|
138
|
+
});
|
|
63
139
|
}
|
|
64
140
|
|
|
65
141
|
export const meta: MetaFunction<typeof loader> = ({ loaderData }) => {
|
|
@@ -139,6 +215,7 @@ function renderMarkdownBlocks(content: string) {
|
|
|
139
215
|
|
|
140
216
|
function PublicDocumentContextSync({
|
|
141
217
|
document,
|
|
218
|
+
basePath,
|
|
142
219
|
}: {
|
|
143
220
|
document: {
|
|
144
221
|
id: string;
|
|
@@ -146,6 +223,7 @@ function PublicDocumentContextSync({
|
|
|
146
223
|
content: string;
|
|
147
224
|
updatedAt: string;
|
|
148
225
|
};
|
|
226
|
+
basePath?: string;
|
|
149
227
|
}) {
|
|
150
228
|
useEffect(() => {
|
|
151
229
|
fetch(agentNativePath("/_agent-native/application-state/navigation"), {
|
|
@@ -156,10 +234,10 @@ function PublicDocumentContextSync({
|
|
|
156
234
|
view: "public-document",
|
|
157
235
|
documentId: document.id,
|
|
158
236
|
title: document.title,
|
|
159
|
-
publicUrl:
|
|
237
|
+
publicUrl: buildContentPublicDocumentUrl(document.id, { basePath }),
|
|
160
238
|
}),
|
|
161
239
|
}).catch(() => {});
|
|
162
|
-
}, [document.id, document.title]);
|
|
240
|
+
}, [basePath, document.id, document.title]);
|
|
163
241
|
|
|
164
242
|
return null;
|
|
165
243
|
}
|
|
@@ -178,6 +256,28 @@ function ReadOnlyMarkdownContent({ content }: { content: string }) {
|
|
|
178
256
|
);
|
|
179
257
|
}
|
|
180
258
|
|
|
259
|
+
function AgentReadableDocumentDiscovery({
|
|
260
|
+
document,
|
|
261
|
+
token,
|
|
262
|
+
basePath,
|
|
263
|
+
}: {
|
|
264
|
+
document: { id: string; title: string };
|
|
265
|
+
token?: string | null;
|
|
266
|
+
basePath?: string;
|
|
267
|
+
}) {
|
|
268
|
+
const discovery = buildContentDocumentAgentDiscovery({
|
|
269
|
+
document,
|
|
270
|
+
token,
|
|
271
|
+
basePath,
|
|
272
|
+
});
|
|
273
|
+
return (
|
|
274
|
+
<script
|
|
275
|
+
type={AGENT_READABLE_RESOURCE_SCRIPT_TYPE}
|
|
276
|
+
dangerouslySetInnerHTML={{ __html: safeJsonForHtml(discovery) }}
|
|
277
|
+
/>
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
181
281
|
function PrivateDocumentNotice({
|
|
182
282
|
id,
|
|
183
283
|
basePath,
|
|
@@ -230,7 +330,12 @@ export default function PublicDocumentPage() {
|
|
|
230
330
|
|
|
231
331
|
return (
|
|
232
332
|
<main className="min-h-screen bg-background text-foreground">
|
|
233
|
-
<PublicDocumentContextSync document={document} />
|
|
333
|
+
<PublicDocumentContextSync document={document} basePath={data.basePath} />
|
|
334
|
+
<AgentReadableDocumentDiscovery
|
|
335
|
+
document={document}
|
|
336
|
+
token={data.agentAccessToken}
|
|
337
|
+
basePath={data.basePath}
|
|
338
|
+
/>
|
|
234
339
|
<div className="mx-auto flex max-w-3xl justify-end px-6 pt-5 sm:px-8">
|
|
235
340
|
<button
|
|
236
341
|
type="button"
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { createGetDb } from "@agent-native/core/db";
|
|
2
2
|
import { registerShareableResource } from "@agent-native/core/sharing";
|
|
3
3
|
|
|
4
|
+
import {
|
|
5
|
+
DOCUMENT_AGENT_CONTEXT_ENDPOINT,
|
|
6
|
+
DOCUMENT_AGENT_RESOURCE_KIND,
|
|
7
|
+
} from "../../shared/agent-readable.js";
|
|
4
8
|
import * as schema from "./schema.js";
|
|
5
9
|
|
|
6
10
|
export const getDb = createGetDb(schema);
|
|
@@ -13,5 +17,10 @@ registerShareableResource({
|
|
|
13
17
|
displayName: "Document",
|
|
14
18
|
titleColumn: "title",
|
|
15
19
|
getResourcePath: (document) => `/page/${document.id}`,
|
|
20
|
+
agentReadable: {
|
|
21
|
+
resourceKind: DOCUMENT_AGENT_RESOURCE_KIND,
|
|
22
|
+
getContextPath: () => DOCUMENT_AGENT_CONTEXT_ENDPOINT,
|
|
23
|
+
getPagePath: (document) => `/p/${document.id}`,
|
|
24
|
+
},
|
|
16
25
|
getDb,
|
|
17
26
|
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AGENT_ACCESS_PARAM,
|
|
3
|
+
getConfiguredAppBasePath,
|
|
4
|
+
verifyScopedAgentAccessToken,
|
|
5
|
+
} from "@agent-native/core/server";
|
|
6
|
+
import { eq } from "drizzle-orm";
|
|
7
|
+
import {
|
|
8
|
+
defineEventHandler,
|
|
9
|
+
getQuery,
|
|
10
|
+
setResponseHeader,
|
|
11
|
+
setResponseStatus,
|
|
12
|
+
} from "h3";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
buildContentPublicDocumentUrl,
|
|
16
|
+
DOCUMENT_AGENT_RESOURCE_KIND,
|
|
17
|
+
} from "../../../shared/agent-readable.js";
|
|
18
|
+
import { getDb, schema } from "../../db/index.js";
|
|
19
|
+
|
|
20
|
+
function queryString(value: unknown): string {
|
|
21
|
+
if (typeof value === "string") return value;
|
|
22
|
+
if (Array.isArray(value) && typeof value[0] === "string") return value[0];
|
|
23
|
+
return "";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function deny(statusCode: number, message: string) {
|
|
27
|
+
return { statusCode, body: { error: message } };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default defineEventHandler(async (event) => {
|
|
31
|
+
setResponseHeader(event, "Cache-Control", "no-store");
|
|
32
|
+
setResponseHeader(event, "Referrer-Policy", "no-referrer");
|
|
33
|
+
setResponseHeader(event, "X-Content-Type-Options", "nosniff");
|
|
34
|
+
|
|
35
|
+
const query = getQuery(event);
|
|
36
|
+
const id = queryString(query.id);
|
|
37
|
+
if (!id) {
|
|
38
|
+
setResponseStatus(event, 400);
|
|
39
|
+
return { error: "Document id is required" };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const token = queryString(query[AGENT_ACCESS_PARAM]);
|
|
43
|
+
const db = getDb();
|
|
44
|
+
// guard:allow-unscoped -- this endpoint returns a document only when it is public or a document-scoped agent_access token verifies for this id.
|
|
45
|
+
const [document] = await db
|
|
46
|
+
.select({
|
|
47
|
+
id: schema.documents.id,
|
|
48
|
+
title: schema.documents.title,
|
|
49
|
+
content: schema.documents.content,
|
|
50
|
+
icon: schema.documents.icon,
|
|
51
|
+
visibility: schema.documents.visibility,
|
|
52
|
+
updatedAt: schema.documents.updatedAt,
|
|
53
|
+
createdAt: schema.documents.createdAt,
|
|
54
|
+
})
|
|
55
|
+
.from(schema.documents)
|
|
56
|
+
.where(eq(schema.documents.id, id))
|
|
57
|
+
.limit(1);
|
|
58
|
+
|
|
59
|
+
if (!document) {
|
|
60
|
+
setResponseStatus(event, 404);
|
|
61
|
+
return { error: "Document not found" };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const tokenAccess = token
|
|
65
|
+
? verifyScopedAgentAccessToken(token, {
|
|
66
|
+
resourceKind: DOCUMENT_AGENT_RESOURCE_KIND,
|
|
67
|
+
resourceId: id,
|
|
68
|
+
}).ok
|
|
69
|
+
: false;
|
|
70
|
+
if (document.visibility !== "public" && !tokenAccess) {
|
|
71
|
+
const denied = deny(403, "Invalid or expired agent access token");
|
|
72
|
+
setResponseStatus(event, denied.statusCode);
|
|
73
|
+
return denied.body;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
resourceType: "document",
|
|
78
|
+
id: document.id,
|
|
79
|
+
title: document.title,
|
|
80
|
+
icon: document.icon,
|
|
81
|
+
content: document.content,
|
|
82
|
+
visibility: document.visibility,
|
|
83
|
+
createdAt: document.createdAt,
|
|
84
|
+
updatedAt: document.updatedAt,
|
|
85
|
+
url: buildContentPublicDocumentUrl(document.id, {
|
|
86
|
+
basePath: getConfiguredAppBasePath(),
|
|
87
|
+
token: tokenAccess ? token : null,
|
|
88
|
+
}),
|
|
89
|
+
};
|
|
90
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AGENT_ACCESS_PARAM,
|
|
3
|
+
buildAgentAccessUrl,
|
|
4
|
+
buildAgentReadableResourceDiscovery,
|
|
5
|
+
normalizeAgentAccessBasePath,
|
|
6
|
+
toAgentAccessUrl,
|
|
7
|
+
type AgentReadableResourceDiscovery,
|
|
8
|
+
} from "@agent-native/core/shared";
|
|
9
|
+
|
|
10
|
+
export const DOCUMENT_AGENT_RESOURCE_KIND = "content:document";
|
|
11
|
+
export const DOCUMENT_AGENT_CONTEXT_ENDPOINT =
|
|
12
|
+
"/api/document-agent-context.json";
|
|
13
|
+
export const DOCUMENT_AGENT_READABLE_INSTRUCTIONS =
|
|
14
|
+
"Use contextUrl to read this shared Content document as structured JSON. The page is read-only for token and public viewers.";
|
|
15
|
+
|
|
16
|
+
export function buildContentPublicDocumentPath(documentId: string): string {
|
|
17
|
+
return `/p/${documentId}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function buildContentPublicDocumentUrl(
|
|
21
|
+
documentId: string,
|
|
22
|
+
options: { basePath?: string; token?: string | null } = {},
|
|
23
|
+
): string {
|
|
24
|
+
const path = buildContentPublicDocumentPath(documentId);
|
|
25
|
+
const basePath = normalizeAgentAccessBasePath(options.basePath);
|
|
26
|
+
if (options.token) {
|
|
27
|
+
return buildAgentAccessUrl({
|
|
28
|
+
path,
|
|
29
|
+
basePath,
|
|
30
|
+
token: options.token,
|
|
31
|
+
tokenParam: AGENT_ACCESS_PARAM,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return toAgentAccessUrl(path, { basePath });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function buildContentDocumentAgentDiscovery({
|
|
38
|
+
document,
|
|
39
|
+
token,
|
|
40
|
+
basePath,
|
|
41
|
+
}: {
|
|
42
|
+
document: { id: string; title: string };
|
|
43
|
+
token?: string | null;
|
|
44
|
+
basePath?: string;
|
|
45
|
+
}): AgentReadableResourceDiscovery {
|
|
46
|
+
return buildAgentReadableResourceDiscovery({
|
|
47
|
+
resourceType: "document",
|
|
48
|
+
resourceId: document.id,
|
|
49
|
+
title: document.title,
|
|
50
|
+
path: buildContentPublicDocumentPath(document.id),
|
|
51
|
+
contextEndpoint: DOCUMENT_AGENT_CONTEXT_ENDPOINT,
|
|
52
|
+
token,
|
|
53
|
+
basePath,
|
|
54
|
+
instructions: DOCUMENT_AGENT_READABLE_INSTRUCTIONS,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { createGetDb } from "@agent-native/core/db";
|
|
2
2
|
import { registerShareableResource } from "@agent-native/core/sharing";
|
|
3
3
|
|
|
4
|
+
import {
|
|
5
|
+
DESIGN_AGENT_CONTEXT_ENDPOINT,
|
|
6
|
+
DESIGN_AGENT_RESOURCE_KIND,
|
|
7
|
+
} from "../../shared/agent-readable.js";
|
|
4
8
|
import * as schema from "./schema.js";
|
|
5
9
|
|
|
6
10
|
export const getDb = createGetDb(schema);
|
|
@@ -63,6 +67,10 @@ registerShareableResource({
|
|
|
63
67
|
displayName: "Design",
|
|
64
68
|
titleColumn: "title",
|
|
65
69
|
getResourcePath: (design) => `/design/${design.id}`,
|
|
70
|
+
agentReadable: {
|
|
71
|
+
resourceKind: DESIGN_AGENT_RESOURCE_KIND,
|
|
72
|
+
getContextPath: () => DESIGN_AGENT_CONTEXT_ENDPOINT,
|
|
73
|
+
},
|
|
66
74
|
getDb,
|
|
67
75
|
publicAccessRole: (design) =>
|
|
68
76
|
isLoopbackLocalhostDesign(design) ? "editor" : "viewer",
|
|
@@ -1,5 +1,98 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AGENT_ACCESS_PARAM,
|
|
3
|
+
getConfiguredAppBasePath,
|
|
4
|
+
} from "@agent-native/core/server";
|
|
1
5
|
import { createH3SSRHandler } from "@agent-native/core/server/ssr-handler";
|
|
6
|
+
import {
|
|
7
|
+
buildAgentReadableResourceDiscovery,
|
|
8
|
+
renderAgentReadableResourceDiscoveryScript,
|
|
9
|
+
} from "@agent-native/core/shared";
|
|
10
|
+
import {
|
|
11
|
+
defineEventHandler,
|
|
12
|
+
getQuery,
|
|
13
|
+
getRequestURL,
|
|
14
|
+
setResponseHeader,
|
|
15
|
+
} from "h3";
|
|
2
16
|
|
|
3
|
-
|
|
17
|
+
import { DESIGN_AGENT_CONTEXT_ENDPOINT } from "../../shared/agent-readable.js";
|
|
18
|
+
|
|
19
|
+
const ssrHandler = createH3SSRHandler(
|
|
4
20
|
() => import("virtual:react-router/server-build"),
|
|
5
21
|
);
|
|
22
|
+
|
|
23
|
+
function stripBasePath(pathname: string): string {
|
|
24
|
+
const basePath = getConfiguredAppBasePath();
|
|
25
|
+
if (!basePath) return pathname;
|
|
26
|
+
if (pathname === basePath) return "/";
|
|
27
|
+
if (pathname.startsWith(`${basePath}/`)) {
|
|
28
|
+
return pathname.slice(basePath.length) || "/";
|
|
29
|
+
}
|
|
30
|
+
return pathname;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function designIdFromPath(pathname: string): string | null {
|
|
34
|
+
const match = stripBasePath(pathname).match(/^\/design\/([^/]+)\/?$/);
|
|
35
|
+
if (!match?.[1]) return null;
|
|
36
|
+
try {
|
|
37
|
+
return decodeURIComponent(match[1]);
|
|
38
|
+
} catch {
|
|
39
|
+
return match[1];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function queryString(value: unknown): string {
|
|
44
|
+
if (typeof value === "string") return value;
|
|
45
|
+
if (Array.isArray(value) && typeof value[0] === "string") return value[0];
|
|
46
|
+
return "";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function injectScript(html: string, script: string): string {
|
|
50
|
+
if (html.includes("agent-native-design-agent-context")) return html;
|
|
51
|
+
if (html.includes("</head>"))
|
|
52
|
+
return html.replace("</head>", `${script}</head>`);
|
|
53
|
+
if (html.includes("</body>"))
|
|
54
|
+
return html.replace("</body>", `${script}</body>`);
|
|
55
|
+
return `${html}${script}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default defineEventHandler(async (event) => {
|
|
59
|
+
const response = (await ssrHandler(event)) as Response;
|
|
60
|
+
const requestUrl = getRequestURL(event);
|
|
61
|
+
const designId = designIdFromPath(requestUrl.pathname);
|
|
62
|
+
if (!designId) return response;
|
|
63
|
+
|
|
64
|
+
const token = queryString(getQuery(event)[AGENT_ACCESS_PARAM]);
|
|
65
|
+
const script = renderAgentReadableResourceDiscoveryScript(
|
|
66
|
+
buildAgentReadableResourceDiscovery({
|
|
67
|
+
resourceType: "design",
|
|
68
|
+
resourceId: designId,
|
|
69
|
+
path: `/design/${designId}`,
|
|
70
|
+
contextEndpoint: DESIGN_AGENT_CONTEXT_ENDPOINT,
|
|
71
|
+
origin: requestUrl.origin,
|
|
72
|
+
basePath: getConfiguredAppBasePath(),
|
|
73
|
+
token,
|
|
74
|
+
instructions:
|
|
75
|
+
"Use contextUrl to read the current design handoff JSON. Token links are read-only and do not grant edit access.",
|
|
76
|
+
}),
|
|
77
|
+
{ id: "agent-native-design-agent-context" },
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
81
|
+
if (!contentType.includes("text/html")) return response;
|
|
82
|
+
|
|
83
|
+
const html = await response.text();
|
|
84
|
+
const headers = new Headers(response.headers);
|
|
85
|
+
headers.delete("content-length");
|
|
86
|
+
if (token) {
|
|
87
|
+
headers.set("Cache-Control", "private, max-age=0, no-store");
|
|
88
|
+
headers.set("Referrer-Policy", "no-referrer");
|
|
89
|
+
setResponseHeader(event, "Cache-Control", "private, max-age=0, no-store");
|
|
90
|
+
setResponseHeader(event, "Referrer-Policy", "no-referrer");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return new Response(injectScript(html, script), {
|
|
94
|
+
status: response.status,
|
|
95
|
+
statusText: response.statusText,
|
|
96
|
+
headers,
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AGENT_ACCESS_PARAM,
|
|
3
|
+
verifyScopedAgentAccessToken,
|
|
4
|
+
} from "@agent-native/core/server";
|
|
5
|
+
import { eq } from "drizzle-orm";
|
|
6
|
+
import {
|
|
7
|
+
defineEventHandler,
|
|
8
|
+
getQuery,
|
|
9
|
+
setResponseHeader,
|
|
10
|
+
setResponseStatus,
|
|
11
|
+
} from "h3";
|
|
12
|
+
|
|
13
|
+
import { DESIGN_AGENT_RESOURCE_KIND } from "../../../shared/agent-readable.js";
|
|
14
|
+
import { getDb, schema } from "../../db/index.js";
|
|
15
|
+
import { buildDesignHandoffPayload } from "../../lib/coding-handoff.js";
|
|
16
|
+
import { buildDesignSnapshot } from "../../lib/design-snapshot.js";
|
|
17
|
+
|
|
18
|
+
function queryString(value: unknown): string {
|
|
19
|
+
if (typeof value === "string") return value;
|
|
20
|
+
if (Array.isArray(value) && typeof value[0] === "string") return value[0];
|
|
21
|
+
return "";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default defineEventHandler(async (event) => {
|
|
25
|
+
setResponseHeader(event, "Cache-Control", "no-store");
|
|
26
|
+
setResponseHeader(event, "Referrer-Policy", "no-referrer");
|
|
27
|
+
setResponseHeader(event, "X-Content-Type-Options", "nosniff");
|
|
28
|
+
|
|
29
|
+
const query = getQuery(event);
|
|
30
|
+
const id = queryString(query.id);
|
|
31
|
+
if (!id) {
|
|
32
|
+
setResponseStatus(event, 400);
|
|
33
|
+
return { error: "Design id is required" };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const token = queryString(query[AGENT_ACCESS_PARAM]);
|
|
37
|
+
const db = getDb();
|
|
38
|
+
// guard:allow-unscoped -- this endpoint returns a design handoff only when it is public or a design-scoped agent_access token verifies for this id.
|
|
39
|
+
const [design] = await db
|
|
40
|
+
.select()
|
|
41
|
+
.from(schema.designs)
|
|
42
|
+
.where(eq(schema.designs.id, id))
|
|
43
|
+
.limit(1);
|
|
44
|
+
|
|
45
|
+
if (!design) {
|
|
46
|
+
setResponseStatus(event, 404);
|
|
47
|
+
return { error: "Design not found" };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const tokenAccess = token
|
|
51
|
+
? verifyScopedAgentAccessToken(token, {
|
|
52
|
+
resourceKind: DESIGN_AGENT_RESOURCE_KIND,
|
|
53
|
+
resourceId: id,
|
|
54
|
+
}).ok
|
|
55
|
+
: false;
|
|
56
|
+
if (design.visibility !== "public" && !tokenAccess) {
|
|
57
|
+
setResponseStatus(event, 403);
|
|
58
|
+
return { error: "Invalid or expired agent access token" };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const snapshot = await buildDesignSnapshot(id, design.data);
|
|
62
|
+
if (snapshot.files.length === 0) {
|
|
63
|
+
setResponseStatus(event, 404);
|
|
64
|
+
return { error: "This design has no files to hand off yet" };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
resourceType: "design",
|
|
69
|
+
id: design.id,
|
|
70
|
+
visibility: design.visibility,
|
|
71
|
+
url: `/design/${design.id}`,
|
|
72
|
+
handoff: buildDesignHandoffPayload({
|
|
73
|
+
design,
|
|
74
|
+
files: snapshot.files.map((f) => ({
|
|
75
|
+
filename: f.filename,
|
|
76
|
+
fileType: f.fileType,
|
|
77
|
+
content: f.content,
|
|
78
|
+
})),
|
|
79
|
+
resolvedCssVars: snapshot.resolvedCssVars,
|
|
80
|
+
}),
|
|
81
|
+
appliedTweaks: snapshot.appliedTweaks,
|
|
82
|
+
resolvedCssVars: snapshot.resolvedCssVars,
|
|
83
|
+
};
|
|
84
|
+
});
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { createGetDb } from "@agent-native/core/db";
|
|
2
2
|
import { registerShareableResource } from "@agent-native/core/sharing";
|
|
3
3
|
|
|
4
|
+
import {
|
|
5
|
+
PLAN_AGENT_CONTEXT_ENDPOINT,
|
|
6
|
+
PLAN_AGENT_RESOURCE_KIND,
|
|
7
|
+
} from "../../shared/agent-readable.js";
|
|
4
8
|
import { resolvePlanAccessContext } from "../lib/local-identity.js";
|
|
5
9
|
import * as schema from "./schema.js";
|
|
6
10
|
|
|
@@ -17,6 +21,14 @@ registerShareableResource({
|
|
|
17
21
|
(plan as { kind?: string }).kind === "recap"
|
|
18
22
|
? `/recaps/${plan.id}`
|
|
19
23
|
: `/plans/${plan.id}`,
|
|
24
|
+
agentReadable: {
|
|
25
|
+
resourceKind: PLAN_AGENT_RESOURCE_KIND,
|
|
26
|
+
getContextPath: () => PLAN_AGENT_CONTEXT_ENDPOINT,
|
|
27
|
+
getPagePath: (plan) =>
|
|
28
|
+
(plan as { kind?: string }).kind === "recap"
|
|
29
|
+
? `/recaps/${plan.id}`
|
|
30
|
+
: `/plans/${plan.id}`,
|
|
31
|
+
},
|
|
20
32
|
getDb,
|
|
21
33
|
resolveAccessContext: resolvePlanAccessContext,
|
|
22
34
|
});
|
|
@@ -740,6 +740,30 @@ export async function loadPlanBundle(planId: string): Promise<PlanBundle> {
|
|
|
740
740
|
if (plan.deletedAt) {
|
|
741
741
|
throw new ForbiddenError(`Plan ${planId} not found`);
|
|
742
742
|
}
|
|
743
|
+
return loadPlanBundleForAuthorizedPlan(planId, plan, access.role);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
export async function loadPlanBundleForAgentAccess(
|
|
747
|
+
planId: string,
|
|
748
|
+
): Promise<PlanBundle> {
|
|
749
|
+
const db = getDb();
|
|
750
|
+
// guard:allow-unscoped -- callers must verify a plan-scoped agent_access token before using this helper; it intentionally returns a read-only viewer bundle.
|
|
751
|
+
const [plan] = await db
|
|
752
|
+
.select()
|
|
753
|
+
.from(schema.plans)
|
|
754
|
+
.where(eq(schema.plans.id, planId))
|
|
755
|
+
.limit(1);
|
|
756
|
+
if (!plan || plan.deletedAt) {
|
|
757
|
+
throw new ForbiddenError(`Plan ${planId} not found`);
|
|
758
|
+
}
|
|
759
|
+
return loadPlanBundleForAuthorizedPlan(planId, plan, "viewer");
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
async function loadPlanBundleForAuthorizedPlan(
|
|
763
|
+
planId: string,
|
|
764
|
+
plan: typeof schema.plans.$inferSelect,
|
|
765
|
+
role: PlanBundle["access"]["role"],
|
|
766
|
+
): Promise<PlanBundle> {
|
|
743
767
|
const db = getDb();
|
|
744
768
|
const [sectionRows, commentRows, eventRows] = await Promise.all([
|
|
745
769
|
db
|
|
@@ -802,7 +826,7 @@ export async function loadPlanBundle(planId: string): Promise<PlanBundle> {
|
|
|
802
826
|
deletedBy: plan.deletedBy,
|
|
803
827
|
},
|
|
804
828
|
access: {
|
|
805
|
-
role
|
|
829
|
+
role,
|
|
806
830
|
ownerEmail: plan.ownerEmail ?? null,
|
|
807
831
|
orgId: plan.orgId ?? null,
|
|
808
832
|
visibility: plan.visibility ?? "private",
|