@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.
Files changed (120) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +6 -0
  3. package/corpus/core/docs/content/agent-native-toolkit.mdx +162 -0
  4. package/corpus/core/docs/content/locales/ar-SA/agent-native-toolkit.mdx +73 -0
  5. package/corpus/core/docs/content/locales/ar-SA/sharing.mdx +15 -7
  6. package/corpus/core/docs/content/locales/de-DE/agent-native-toolkit.mdx +77 -0
  7. package/corpus/core/docs/content/locales/de-DE/sharing.mdx +15 -7
  8. package/corpus/core/docs/content/locales/es-ES/agent-native-toolkit.mdx +77 -0
  9. package/corpus/core/docs/content/locales/es-ES/sharing.mdx +15 -7
  10. package/corpus/core/docs/content/locales/fr-FR/agent-native-toolkit.mdx +79 -0
  11. package/corpus/core/docs/content/locales/fr-FR/sharing.mdx +15 -7
  12. package/corpus/core/docs/content/locales/hi-IN/agent-native-toolkit.mdx +74 -0
  13. package/corpus/core/docs/content/locales/hi-IN/sharing.mdx +15 -7
  14. package/corpus/core/docs/content/locales/ja-JP/agent-native-toolkit.mdx +67 -0
  15. package/corpus/core/docs/content/locales/ja-JP/sharing.mdx +15 -7
  16. package/corpus/core/docs/content/locales/ko-KR/agent-native-toolkit.mdx +63 -0
  17. package/corpus/core/docs/content/locales/ko-KR/sharing.mdx +15 -7
  18. package/corpus/core/docs/content/locales/pt-BR/agent-native-toolkit.mdx +77 -0
  19. package/corpus/core/docs/content/locales/pt-BR/sharing.mdx +15 -7
  20. package/corpus/core/docs/content/locales/zh-CN/agent-native-toolkit.mdx +61 -0
  21. package/corpus/core/docs/content/locales/zh-CN/sharing.mdx +15 -7
  22. package/corpus/core/docs/content/locales/zh-TW/agent-native-toolkit.mdx +61 -0
  23. package/corpus/core/docs/content/locales/zh-TW/sharing.mdx +15 -7
  24. package/corpus/core/docs/content/sharing.mdx +39 -7
  25. package/corpus/core/package.json +2 -1
  26. package/corpus/core/src/server/action-discovery.ts +4 -0
  27. package/corpus/core/src/server/action-routes.ts +8 -1
  28. package/corpus/core/src/server/index.ts +9 -0
  29. package/corpus/core/src/shared/agent-readable-resource.ts +111 -0
  30. package/corpus/core/src/shared/index.ts +9 -0
  31. package/corpus/core/src/sharing/actions/create-agent-resource-link.ts +91 -0
  32. package/corpus/core/src/sharing/registry.ts +19 -0
  33. package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +92 -0
  34. package/corpus/core/src/vite/client.ts +1 -0
  35. package/corpus/templates/analytics/actions/get-sql-dashboard.ts +9 -113
  36. package/corpus/templates/analytics/server/db/index.ts +14 -0
  37. package/corpus/templates/analytics/server/lib/agent-readable-resource-context.ts +156 -0
  38. package/corpus/templates/analytics/server/routes/[...page].get.ts +92 -25
  39. package/corpus/templates/analytics/server/routes/api/analysis-agent-context.json.get.ts +94 -0
  40. package/corpus/templates/analytics/server/routes/api/dashboard-agent-context.json.get.ts +109 -0
  41. package/corpus/templates/analytics/shared/resource-agent-access.ts +6 -0
  42. package/corpus/templates/content/app/routes/p.$id.tsx +114 -9
  43. package/corpus/templates/content/server/db/index.ts +9 -0
  44. package/corpus/templates/content/server/routes/api/document-agent-context.json.get.ts +90 -0
  45. package/corpus/templates/content/shared/agent-readable.ts +56 -0
  46. package/corpus/templates/design/server/db/index.ts +8 -0
  47. package/corpus/templates/design/server/routes/[...page].get.ts +94 -1
  48. package/corpus/templates/design/server/routes/api/design-agent-context.json.get.ts +84 -0
  49. package/corpus/templates/design/shared/agent-readable.ts +2 -0
  50. package/corpus/templates/plan/server/db/index.ts +12 -0
  51. package/corpus/templates/plan/server/plans.ts +25 -1
  52. package/corpus/templates/plan/server/routes/[...page].get.ts +107 -1
  53. package/corpus/templates/plan/server/routes/api/plan-agent-context.json.get.ts +65 -0
  54. package/corpus/templates/plan/shared/agent-readable.ts +2 -0
  55. package/corpus/templates/slides/app/routes/p.$id.tsx +126 -12
  56. package/corpus/templates/slides/app/routes/share.$token.tsx +55 -8
  57. package/corpus/templates/slides/server/db/index.ts +9 -0
  58. package/corpus/templates/slides/server/routes/api/deck-agent-context.json.get.ts +97 -0
  59. package/corpus/templates/slides/shared/agent-readable.ts +2 -0
  60. package/dist/collab/routes.d.ts +1 -1
  61. package/dist/collab/struct-routes.d.ts +1 -1
  62. package/dist/notifications/routes.d.ts +1 -1
  63. package/dist/observability/routes.d.ts +5 -5
  64. package/dist/progress/routes.d.ts +1 -1
  65. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  66. package/dist/resources/handlers.d.ts +1 -1
  67. package/dist/secrets/routes.d.ts +9 -9
  68. package/dist/server/action-discovery.d.ts.map +1 -1
  69. package/dist/server/action-discovery.js +4 -0
  70. package/dist/server/action-discovery.js.map +1 -1
  71. package/dist/server/action-routes.d.ts.map +1 -1
  72. package/dist/server/action-routes.js +8 -2
  73. package/dist/server/action-routes.js.map +1 -1
  74. package/dist/server/index.d.ts +1 -0
  75. package/dist/server/index.d.ts.map +1 -1
  76. package/dist/server/index.js +1 -0
  77. package/dist/server/index.js.map +1 -1
  78. package/dist/shared/agent-readable-resource.d.ts +29 -0
  79. package/dist/shared/agent-readable-resource.d.ts.map +1 -0
  80. package/dist/shared/agent-readable-resource.js +60 -0
  81. package/dist/shared/agent-readable-resource.js.map +1 -0
  82. package/dist/shared/index.d.ts +1 -0
  83. package/dist/shared/index.d.ts.map +1 -1
  84. package/dist/shared/index.js +1 -0
  85. package/dist/shared/index.js.map +1 -1
  86. package/dist/sharing/actions/create-agent-resource-link.d.ts +13 -0
  87. package/dist/sharing/actions/create-agent-resource-link.d.ts.map +1 -0
  88. package/dist/sharing/actions/create-agent-resource-link.js +72 -0
  89. package/dist/sharing/actions/create-agent-resource-link.js.map +1 -0
  90. package/dist/sharing/registry.d.ts +17 -0
  91. package/dist/sharing/registry.d.ts.map +1 -1
  92. package/dist/sharing/registry.js.map +1 -1
  93. package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +92 -0
  94. package/dist/vite/client.d.ts.map +1 -1
  95. package/dist/vite/client.js +1 -0
  96. package/dist/vite/client.js.map +1 -1
  97. package/docs/content/agent-native-toolkit.mdx +162 -0
  98. package/docs/content/locales/ar-SA/agent-native-toolkit.mdx +73 -0
  99. package/docs/content/locales/ar-SA/sharing.mdx +15 -7
  100. package/docs/content/locales/de-DE/agent-native-toolkit.mdx +77 -0
  101. package/docs/content/locales/de-DE/sharing.mdx +15 -7
  102. package/docs/content/locales/es-ES/agent-native-toolkit.mdx +77 -0
  103. package/docs/content/locales/es-ES/sharing.mdx +15 -7
  104. package/docs/content/locales/fr-FR/agent-native-toolkit.mdx +79 -0
  105. package/docs/content/locales/fr-FR/sharing.mdx +15 -7
  106. package/docs/content/locales/hi-IN/agent-native-toolkit.mdx +74 -0
  107. package/docs/content/locales/hi-IN/sharing.mdx +15 -7
  108. package/docs/content/locales/ja-JP/agent-native-toolkit.mdx +67 -0
  109. package/docs/content/locales/ja-JP/sharing.mdx +15 -7
  110. package/docs/content/locales/ko-KR/agent-native-toolkit.mdx +63 -0
  111. package/docs/content/locales/ko-KR/sharing.mdx +15 -7
  112. package/docs/content/locales/pt-BR/agent-native-toolkit.mdx +77 -0
  113. package/docs/content/locales/pt-BR/sharing.mdx +15 -7
  114. package/docs/content/locales/zh-CN/agent-native-toolkit.mdx +61 -0
  115. package/docs/content/locales/zh-CN/sharing.mdx +15 -7
  116. package/docs/content/locales/zh-TW/agent-native-toolkit.mdx +61 -0
  117. package/docs/content/locales/zh-TW/sharing.mdx +15 -7
  118. package/docs/content/sharing.mdx +39 -7
  119. package/package.json +2 -1
  120. package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +92 -0
@@ -1,5 +1,111 @@
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
- export default createH3SSRHandler(
17
+ import { PLAN_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 planFromPath(pathname: string): {
34
+ id: string;
35
+ kind: "plan" | "recap";
36
+ } | null {
37
+ const stripped = stripBasePath(pathname);
38
+ const match = stripped.match(/^\/(plans|recaps)\/([^/]+)\/?$/);
39
+ if (!match?.[1] || !match[2]) return null;
40
+ try {
41
+ return {
42
+ kind: match[1] === "recaps" ? "recap" : "plan",
43
+ id: decodeURIComponent(match[2]),
44
+ };
45
+ } catch {
46
+ return {
47
+ kind: match[1] === "recaps" ? "recap" : "plan",
48
+ id: match[2],
49
+ };
50
+ }
51
+ }
52
+
53
+ function queryString(value: unknown): string {
54
+ if (typeof value === "string") return value;
55
+ if (Array.isArray(value) && typeof value[0] === "string") return value[0];
56
+ return "";
57
+ }
58
+
59
+ function injectScript(html: string, script: string): string {
60
+ if (html.includes("agent-native-plan-agent-context")) return html;
61
+ if (html.includes("</head>"))
62
+ return html.replace("</head>", `${script}</head>`);
63
+ if (html.includes("</body>"))
64
+ return html.replace("</body>", `${script}</body>`);
65
+ return `${html}${script}`;
66
+ }
67
+
68
+ export default defineEventHandler(async (event) => {
69
+ const response = (await ssrHandler(event)) as Response;
70
+ const requestUrl = getRequestURL(event);
71
+ const resource = planFromPath(requestUrl.pathname);
72
+ if (!resource) return response;
73
+
74
+ const token = queryString(getQuery(event)[AGENT_ACCESS_PARAM]);
75
+ const script = renderAgentReadableResourceDiscoveryScript(
76
+ buildAgentReadableResourceDiscovery({
77
+ resourceType: "plan",
78
+ resourceId: resource.id,
79
+ path:
80
+ resource.kind === "recap"
81
+ ? `/recaps/${resource.id}`
82
+ : `/plans/${resource.id}`,
83
+ contextEndpoint: PLAN_AGENT_CONTEXT_ENDPOINT,
84
+ origin: requestUrl.origin,
85
+ basePath: getConfiguredAppBasePath(),
86
+ token,
87
+ instructions:
88
+ "Use contextUrl to read the visual plan bundle as structured JSON. Token links are read-only.",
89
+ }),
90
+ { id: "agent-native-plan-agent-context" },
91
+ );
92
+
93
+ const contentType = response.headers.get("content-type") ?? "";
94
+ if (!contentType.includes("text/html")) return response;
95
+
96
+ const html = await response.text();
97
+ const headers = new Headers(response.headers);
98
+ headers.delete("content-length");
99
+ if (token) {
100
+ headers.set("Cache-Control", "private, max-age=0, no-store");
101
+ headers.set("Referrer-Policy", "no-referrer");
102
+ setResponseHeader(event, "Cache-Control", "private, max-age=0, no-store");
103
+ setResponseHeader(event, "Referrer-Policy", "no-referrer");
104
+ }
105
+
106
+ return new Response(injectScript(html, script), {
107
+ status: response.status,
108
+ statusText: response.statusText,
109
+ headers,
110
+ });
111
+ });
@@ -0,0 +1,65 @@
1
+ import {
2
+ AGENT_ACCESS_PARAM,
3
+ verifyScopedAgentAccessToken,
4
+ } from "@agent-native/core/server";
5
+ import {
6
+ defineEventHandler,
7
+ getQuery,
8
+ setResponseHeader,
9
+ setResponseStatus,
10
+ } from "h3";
11
+
12
+ import { PLAN_AGENT_RESOURCE_KIND } from "../../../shared/agent-readable.js";
13
+ import { loadPlanBundle, loadPlanBundleForAgentAccess } from "../../plans.js";
14
+
15
+ function queryString(value: unknown): string {
16
+ if (typeof value === "string") return value;
17
+ if (Array.isArray(value) && typeof value[0] === "string") return value[0];
18
+ return "";
19
+ }
20
+
21
+ export default defineEventHandler(async (event) => {
22
+ setResponseHeader(event, "Cache-Control", "no-store");
23
+ setResponseHeader(event, "Referrer-Policy", "no-referrer");
24
+ setResponseHeader(event, "X-Content-Type-Options", "nosniff");
25
+
26
+ const query = getQuery(event);
27
+ const id = queryString(query.id);
28
+ if (!id) {
29
+ setResponseStatus(event, 400);
30
+ return { error: "Plan id is required" };
31
+ }
32
+
33
+ const token = queryString(query[AGENT_ACCESS_PARAM]);
34
+ try {
35
+ const tokenAccess = token
36
+ ? verifyScopedAgentAccessToken(token, {
37
+ resourceKind: PLAN_AGENT_RESOURCE_KIND,
38
+ resourceId: id,
39
+ }).ok
40
+ : false;
41
+ const bundle = tokenAccess
42
+ ? await loadPlanBundleForAgentAccess(id)
43
+ : await loadPlanBundle(id);
44
+
45
+ return {
46
+ resourceType: "plan",
47
+ id: bundle.plan.id,
48
+ title: bundle.plan.title,
49
+ kind: bundle.plan.kind,
50
+ access: bundle.access,
51
+ plan: bundle.plan,
52
+ sections: bundle.sections,
53
+ comments: bundle.comments,
54
+ events: bundle.events,
55
+ summary: bundle.summary,
56
+ url:
57
+ bundle.plan.kind === "recap"
58
+ ? `/recaps/${bundle.plan.id}`
59
+ : `/plans/${bundle.plan.id}`,
60
+ };
61
+ } catch (error: any) {
62
+ setResponseStatus(event, error?.statusCode === 403 ? 403 : 404);
63
+ return { error: "Plan not found" };
64
+ }
65
+ });
@@ -0,0 +1,2 @@
1
+ export const PLAN_AGENT_RESOURCE_KIND = "plan:plan";
2
+ export const PLAN_AGENT_CONTEXT_ENDPOINT = "/api/plan-agent-context.json";
@@ -1,4 +1,13 @@
1
- import { getConfiguredAppBasePath } from "@agent-native/core/server";
1
+ import {
2
+ AGENT_ACCESS_PARAM,
3
+ getConfiguredAppBasePath,
4
+ verifyScopedAgentAccessToken,
5
+ } from "@agent-native/core/server";
6
+ import {
7
+ AGENT_READABLE_RESOURCE_SCRIPT_TYPE,
8
+ buildAgentReadableResourceDiscovery,
9
+ safeJsonForHtml,
10
+ } from "@agent-native/core/shared";
2
11
  import {
3
12
  toSharedDeckSlide,
4
13
  type SharedDeckResponse,
@@ -6,15 +15,29 @@ import {
6
15
  } from "@shared/api";
7
16
  import { eq } from "drizzle-orm";
8
17
  import { useEffect } from "react";
9
- import type { LoaderFunctionArgs, MetaFunction } from "react-router";
10
- import { useLoaderData } from "react-router";
18
+ import type {
19
+ HeadersArgs,
20
+ LoaderFunctionArgs,
21
+ MetaFunction,
22
+ } from "react-router";
23
+ import { data, useLoaderData } from "react-router";
11
24
 
12
25
  import SharedPresentation from "@/pages/SharedPresentation";
13
26
 
14
27
  import { getDb, schema } from "../../server/db";
28
+ import {
29
+ DECK_AGENT_CONTEXT_ENDPOINT,
30
+ DECK_AGENT_RESOURCE_KIND,
31
+ } from "../../shared/agent-readable";
15
32
 
16
33
  type LoaderData =
17
- | { deck: SharedDeckResponse; error?: undefined }
34
+ | {
35
+ deck: SharedDeckResponse;
36
+ error?: undefined;
37
+ id: string;
38
+ basePath: string;
39
+ agentAccessToken?: string | null;
40
+ }
18
41
  | {
19
42
  deck: null;
20
43
  error: string;
@@ -34,6 +57,22 @@ type DeckData = {
34
57
  aspectRatio?: SharedDeckResponse["aspectRatio"];
35
58
  };
36
59
 
60
+ const PRIVATE_AGENT_DECK_HEADERS = {
61
+ "Cache-Control": "private, max-age=0, no-store",
62
+ "Referrer-Policy": "no-referrer",
63
+ };
64
+
65
+ function publicDeckLoaderData(payload: LoaderData, privateAgentAccess = false) {
66
+ if (!privateAgentAccess) return payload;
67
+ return data(payload, {
68
+ headers: PRIVATE_AGENT_DECK_HEADERS,
69
+ });
70
+ }
71
+
72
+ export function headers({ loaderHeaders }: HeadersArgs) {
73
+ return loaderHeaders;
74
+ }
75
+
37
76
  function toSharedDeck(row: {
38
77
  title: string | null;
39
78
  data: string;
@@ -48,11 +87,13 @@ function toSharedDeck(row: {
48
87
  };
49
88
  }
50
89
 
51
- export async function loader({
52
- params,
53
- }: LoaderFunctionArgs): Promise<LoaderData> {
90
+ export async function loader({ params, request }: LoaderFunctionArgs) {
54
91
  const id = params.id;
55
92
  if (!id) throw new Response("Not found", { status: 404 });
93
+ const agentAccessToken = new URL(request.url).searchParams.get(
94
+ AGENT_ACCESS_PARAM,
95
+ );
96
+ const basePath = getConfiguredAppBasePath();
56
97
 
57
98
  // Access is checked on the deck, not the URL shape: `/p/<id>` (presentation)
58
99
  // and `/deck/<id>` (editor) share the same rules. SSR renders impersonally (no
@@ -76,12 +117,28 @@ export async function loader({
76
117
  .limit(1);
77
118
 
78
119
  if (!deck) throw new Response("Not found", { status: 404 });
79
- if (deck.visibility === "public") return { deck: toSharedDeck(deck) };
80
- return {
120
+ const tokenAccess = agentAccessToken
121
+ ? verifyScopedAgentAccessToken(agentAccessToken, {
122
+ resourceKind: DECK_AGENT_RESOURCE_KIND,
123
+ resourceId: id,
124
+ }).ok
125
+ : false;
126
+ if (deck.visibility === "public" || tokenAccess) {
127
+ return publicDeckLoaderData(
128
+ {
129
+ deck: toSharedDeck(deck),
130
+ id,
131
+ basePath,
132
+ agentAccessToken: tokenAccess ? agentAccessToken : null,
133
+ },
134
+ tokenAccess,
135
+ );
136
+ }
137
+ return publicDeckLoaderData({
81
138
  deck: null,
82
139
  error: "restricted",
83
- restricted: { id, basePath: getConfiguredAppBasePath() },
84
- };
140
+ restricted: { id, basePath },
141
+ });
85
142
  }
86
143
 
87
144
  export const meta: MetaFunction<typeof loader> = ({ loaderData }) => {
@@ -101,8 +158,65 @@ export default function PublicDeckRoute() {
101
158
 
102
159
  // Redirecting to the guarded editor to resolve per-user access client-side.
103
160
  if (restricted) return null;
161
+ if (data.deck === null) {
162
+ return (
163
+ <SharedPresentation initialDeck={data.deck} initialError={data.error} />
164
+ );
165
+ }
104
166
 
105
167
  return (
106
- <SharedPresentation initialDeck={data.deck} initialError={data.error} />
168
+ <>
169
+ <AgentReadableDeckDiscovery
170
+ id={data.id}
171
+ title={data.deck.title}
172
+ basePath={data.basePath}
173
+ token={data.agentAccessToken}
174
+ />
175
+ <SharedPresentation initialDeck={data.deck} initialError={data.error} />
176
+ </>
177
+ );
178
+ }
179
+
180
+ export function buildDeckDiscovery({
181
+ id,
182
+ title,
183
+ basePath,
184
+ token,
185
+ }: {
186
+ id: string;
187
+ title?: string;
188
+ basePath?: string;
189
+ token?: string | null;
190
+ }) {
191
+ return buildAgentReadableResourceDiscovery({
192
+ resourceType: "deck",
193
+ resourceId: id,
194
+ title,
195
+ path: `/p/${id}`,
196
+ contextEndpoint: DECK_AGENT_CONTEXT_ENDPOINT,
197
+ basePath,
198
+ token,
199
+ instructions:
200
+ "Use contextUrl to read this shared Slides deck as JSON. Slide numbers are 1-based for users.",
201
+ });
202
+ }
203
+
204
+ function AgentReadableDeckDiscovery({
205
+ id,
206
+ title,
207
+ basePath,
208
+ token,
209
+ }: {
210
+ id: string;
211
+ title?: string;
212
+ basePath?: string;
213
+ token?: string | null;
214
+ }) {
215
+ const discovery = buildDeckDiscovery({ id, title, basePath, token });
216
+ return (
217
+ <script
218
+ type={AGENT_READABLE_RESOURCE_SCRIPT_TYPE}
219
+ dangerouslySetInnerHTML={{ __html: safeJsonForHtml(discovery) }}
220
+ />
107
221
  );
108
222
  }
@@ -1,12 +1,17 @@
1
+ import {
2
+ AGENT_READABLE_RESOURCE_PAYLOAD_TYPE,
3
+ AGENT_READABLE_RESOURCE_SCRIPT_TYPE,
4
+ safeJsonForHtml,
5
+ } from "@agent-native/core/shared";
1
6
  import type { SharedDeckResponse } from "@shared/api";
2
7
  import type { LoaderFunctionArgs } from "react-router";
3
- import { useLoaderData } from "react-router";
8
+ import { useLoaderData, useParams } from "react-router";
4
9
 
5
10
  import messages from "@/i18n/en-US";
6
11
  import SharedPresentation from "@/pages/SharedPresentation";
7
12
 
8
13
  type LoaderData =
9
- | { deck: SharedDeckResponse; error?: undefined }
14
+ | { deck: SharedDeckResponse; error?: undefined; basePath: string }
10
15
  | { deck: null; error: string };
11
16
 
12
17
  function normalizeBasePath(value: string | undefined): string {
@@ -30,10 +35,8 @@ export async function loader({
30
35
  return { deck: null, error: "Token is required" };
31
36
  }
32
37
 
33
- const url = new URL(
34
- `${appBasePathForRequest()}/api/share/${params.token}`,
35
- requestUrl,
36
- );
38
+ const basePath = appBasePathForRequest();
39
+ const url = new URL(`${basePath}/api/share/${params.token}`, requestUrl);
37
40
  const res = await fetch(url, { headers: { accept: "application/json" } });
38
41
  const data = await res.json().catch(() => null);
39
42
 
@@ -44,7 +47,7 @@ export async function loader({
44
47
  };
45
48
  }
46
49
 
47
- return { deck: data as SharedDeckResponse };
50
+ return { deck: data as SharedDeckResponse, basePath };
48
51
  }
49
52
 
50
53
  export function meta() {
@@ -54,6 +57,50 @@ export function meta() {
54
57
  export default function SharedPresentationRoute() {
55
58
  const data = useLoaderData<typeof loader>();
56
59
  return (
57
- <SharedPresentation initialDeck={data.deck} initialError={data.error} />
60
+ <>
61
+ {data.deck ? (
62
+ <SnapshotDiscovery deck={data.deck} basePath={data.basePath} />
63
+ ) : null}
64
+ <SharedPresentation initialDeck={data.deck} initialError={data.error} />
65
+ </>
66
+ );
67
+ }
68
+
69
+ export function buildSnapshotDiscovery({
70
+ deck,
71
+ token,
72
+ basePath,
73
+ }: {
74
+ deck: SharedDeckResponse;
75
+ token: string;
76
+ basePath?: string;
77
+ }) {
78
+ const normalizedBasePath = normalizeBasePath(basePath);
79
+ return {
80
+ type: AGENT_READABLE_RESOURCE_PAYLOAD_TYPE,
81
+ resourceType: "deck-snapshot",
82
+ resourceId: token,
83
+ title: deck.title,
84
+ contextUrl: `${normalizedBasePath}/api/share/${encodeURIComponent(token)}`,
85
+ instructions:
86
+ "Use contextUrl to read this shared Slides snapshot as JSON. This snapshot link is independent of live deck visibility.",
87
+ };
88
+ }
89
+
90
+ function SnapshotDiscovery({
91
+ deck,
92
+ basePath,
93
+ }: {
94
+ deck: SharedDeckResponse;
95
+ basePath: string;
96
+ }) {
97
+ const { token } = useParams<{ token: string }>();
98
+ if (!token) return null;
99
+ const discovery = buildSnapshotDiscovery({ deck, token, basePath });
100
+ return (
101
+ <script
102
+ type={AGENT_READABLE_RESOURCE_SCRIPT_TYPE}
103
+ dangerouslySetInnerHTML={{ __html: safeJsonForHtml(discovery) }}
104
+ />
58
105
  );
59
106
  }
@@ -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
+ DECK_AGENT_CONTEXT_ENDPOINT,
6
+ DECK_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,6 +17,11 @@ registerShareableResource({
13
17
  displayName: "Deck",
14
18
  titleColumn: "title",
15
19
  getResourcePath: (deck) => `/deck/${deck.id}`,
20
+ agentReadable: {
21
+ resourceKind: DECK_AGENT_RESOURCE_KIND,
22
+ getContextPath: () => DECK_AGENT_CONTEXT_ENDPOINT,
23
+ getPagePath: (deck) => `/p/${deck.id}`,
24
+ },
16
25
  getDb,
17
26
  });
18
27
 
@@ -0,0 +1,97 @@
1
+ import {
2
+ AGENT_ACCESS_PARAM,
3
+ verifyScopedAgentAccessToken,
4
+ } from "@agent-native/core/server";
5
+ import { toSharedDeckSlide } from "@shared/api";
6
+ import { eq } from "drizzle-orm";
7
+ import {
8
+ defineEventHandler,
9
+ getQuery,
10
+ setResponseHeader,
11
+ setResponseStatus,
12
+ } from "h3";
13
+
14
+ import { DECK_AGENT_RESOURCE_KIND } from "../../../shared/agent-readable.js";
15
+ import { getDb, schema } from "../../db/index.js";
16
+
17
+ function queryString(value: unknown): string {
18
+ if (typeof value === "string") return value;
19
+ if (Array.isArray(value) && typeof value[0] === "string") return value[0];
20
+ return "";
21
+ }
22
+
23
+ function parseDeckData(value: string): Record<string, any> {
24
+ try {
25
+ const parsed = JSON.parse(value);
26
+ return parsed && typeof parsed === "object" ? parsed : {};
27
+ } catch {
28
+ return {};
29
+ }
30
+ }
31
+
32
+ export default defineEventHandler(async (event) => {
33
+ setResponseHeader(event, "Cache-Control", "no-store");
34
+ setResponseHeader(event, "Referrer-Policy", "no-referrer");
35
+ setResponseHeader(event, "X-Content-Type-Options", "nosniff");
36
+
37
+ const query = getQuery(event);
38
+ const id = queryString(query.id);
39
+ if (!id) {
40
+ setResponseStatus(event, 400);
41
+ return { error: "Deck id is required" };
42
+ }
43
+
44
+ const token = queryString(query[AGENT_ACCESS_PARAM]);
45
+ const db = getDb();
46
+ // guard:allow-unscoped -- this endpoint returns a deck only when it is public or a deck-scoped agent_access token verifies for this id.
47
+ const [deck] = await db
48
+ .select({
49
+ id: schema.decks.id,
50
+ title: schema.decks.title,
51
+ data: schema.decks.data,
52
+ visibility: schema.decks.visibility,
53
+ designSystemId: schema.decks.designSystemId,
54
+ createdAt: schema.decks.createdAt,
55
+ updatedAt: schema.decks.updatedAt,
56
+ })
57
+ .from(schema.decks)
58
+ .where(eq(schema.decks.id, id))
59
+ .limit(1);
60
+
61
+ if (!deck) {
62
+ setResponseStatus(event, 404);
63
+ return { error: "Deck not found" };
64
+ }
65
+
66
+ const tokenAccess = token
67
+ ? verifyScopedAgentAccessToken(token, {
68
+ resourceKind: DECK_AGENT_RESOURCE_KIND,
69
+ resourceId: id,
70
+ }).ok
71
+ : false;
72
+ if (deck.visibility !== "public" && !tokenAccess) {
73
+ setResponseStatus(event, 403);
74
+ return { error: "Invalid or expired agent access token" };
75
+ }
76
+
77
+ const data = parseDeckData(deck.data);
78
+ const slides = Array.isArray(data.slides)
79
+ ? data.slides.map((slide: unknown, index: number) =>
80
+ toSharedDeckSlide(slide, index),
81
+ )
82
+ : [];
83
+
84
+ return {
85
+ resourceType: "deck",
86
+ id: deck.id,
87
+ title: deck.title || data.title || "Untitled",
88
+ visibility: deck.visibility,
89
+ designSystemId: deck.designSystemId ?? null,
90
+ slideCount: slides.length,
91
+ aspectRatio: data.aspectRatio ?? null,
92
+ slides,
93
+ createdAt: deck.createdAt,
94
+ updatedAt: deck.updatedAt,
95
+ url: `/p/${deck.id}`,
96
+ };
97
+ });
@@ -0,0 +1,2 @@
1
+ export const DECK_AGENT_RESOURCE_KIND = "slides:deck";
2
+ export const DECK_AGENT_CONTEXT_ENDPOINT = "/api/deck-agent-context.json";
@@ -41,9 +41,9 @@ export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import
41
41
  * Body: { text: string, fieldName?: string, requestSource?: string }
42
42
  */
43
43
  export declare const postCollabText: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
44
- text?: undefined;
45
44
  ok?: undefined;
46
45
  error: string;
46
+ text?: undefined;
47
47
  } | {
48
48
  error?: undefined;
49
49
  ok: boolean;
@@ -13,8 +13,8 @@
13
13
  * Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
14
14
  */
15
15
  export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
16
- ok?: undefined;
17
16
  error: string;
17
+ ok?: undefined;
18
18
  } | {
19
19
  error?: undefined;
20
20
  ok: boolean;
@@ -28,7 +28,7 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
28
28
  } | {
29
29
  count?: undefined;
30
30
  updated?: undefined;
31
- error?: undefined;
32
31
  ok: boolean;
32
+ error?: undefined;
33
33
  }>>;
34
34
  //# sourceMappingURL=routes.d.ts.map
@@ -41,28 +41,28 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
41
41
  thumbsUpRate: number;
42
42
  avgEvalScore: number;
43
43
  } | {
44
- error?: undefined;
45
- ok?: undefined;
46
44
  summary: import("./types.js").TraceSummary;
47
45
  spans: import("./types.js").TraceSpan[];
48
46
  id?: undefined;
49
- } | {
50
47
  error?: undefined;
51
48
  ok?: undefined;
49
+ } | {
52
50
  summary?: undefined;
53
51
  spans?: undefined;
54
52
  id: string;
55
- } | {
53
+ error?: undefined;
56
54
  ok?: undefined;
55
+ } | {
57
56
  summary?: undefined;
58
57
  spans?: undefined;
59
58
  id?: undefined;
60
59
  error: any;
60
+ ok?: undefined;
61
61
  } | {
62
- error?: undefined;
63
62
  summary?: undefined;
64
63
  spans?: undefined;
65
64
  id?: undefined;
65
+ error?: undefined;
66
66
  ok: boolean;
67
67
  }>>;
68
68
  //# sourceMappingURL=routes.d.ts.map
@@ -15,7 +15,7 @@ export declare function createProgressHandler(): import("h3").EventHandlerWithFe
15
15
  error: string;
16
16
  ok?: undefined;
17
17
  } | {
18
- error?: undefined;
19
18
  ok: boolean;
19
+ error?: undefined;
20
20
  }>>;
21
21
  //# sourceMappingURL=routes.d.ts.map
@@ -73,7 +73,6 @@ export declare function createProviderCorpusJobAction(options: CreateProviderCor
73
73
  offset?: unknown;
74
74
  limit?: unknown;
75
75
  }, {
76
- deleted?: undefined;
77
76
  jobs: {
78
77
  id: string;
79
78
  name: string;
@@ -84,6 +83,7 @@ export declare function createProviderCorpusJobAction(options: CreateProviderCor
84
83
  updatedAt: string;
85
84
  }[];
86
85
  total: number;
86
+ deleted?: undefined;
87
87
  jobId?: undefined;
88
88
  } | {
89
89
  jobs?: undefined;
@@ -91,9 +91,9 @@ export declare function createProviderCorpusJobAction(options: CreateProviderCor
91
91
  deleted: boolean;
92
92
  jobId: string;
93
93
  } | {
94
- deleted?: undefined;
95
94
  jobs?: undefined;
96
95
  total?: undefined;
96
+ deleted?: undefined;
97
97
  jobId?: undefined;
98
98
  hits: Record<string, unknown>[];
99
99
  offset: number;