@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
@@ -6,97 +6,12 @@ import {
6
6
  } from "@agent-native/core/server";
7
7
  import { z } from "zod";
8
8
 
9
- import { buildDashboardPanelGroups } from "../app/pages/adhoc/sql-dashboard/dashboard-layout";
10
9
  import {
11
- clampDashboardColumns,
12
- type SqlPanel,
13
- } from "../app/pages/adhoc/sql-dashboard/types";
10
+ buildDashboardAgentContext,
11
+ buildDashboardSeedAgentContext,
12
+ } from "../server/lib/agent-readable-resource-context";
14
13
  import { loadDashboardSeed } from "../server/lib/dashboard-seeds";
15
14
  import { getDashboard } from "../server/lib/dashboards-store";
16
- import { getPanelOrder } from "./dashboard-panel-order";
17
-
18
- function dashboardLayoutSummary(config: Record<string, unknown>) {
19
- const panels = Array.isArray(config.panels)
20
- ? (config.panels as SqlPanel[])
21
- : [];
22
- const columns = clampDashboardColumns(config.columns);
23
- const groups = buildDashboardPanelGroups(panels, columns);
24
- const panelOrder = getPanelOrder(config);
25
- let visibleRowNumber = 1;
26
-
27
- return {
28
- panelCount: panelOrder.length,
29
- panelOrder,
30
- firstPanelIds: panelOrder.slice(0, 10),
31
- groups: groups.map((group) => ({
32
- key: group.key,
33
- sectionId: group.section?.id ?? null,
34
- sectionTitle: group.section?.title ?? null,
35
- columns: group.columns,
36
- rows: group.rows.map((row, rowIndex) => {
37
- const rowNumber = visibleRowNumber++;
38
- return {
39
- rowNumber,
40
- rowIndex,
41
- panelIds: row.panels.map((panel) => panel.id),
42
- };
43
- }),
44
- })),
45
- };
46
- }
47
-
48
- function panelSummaries(config: Record<string, unknown>) {
49
- const panels = Array.isArray(config.panels)
50
- ? (config.panels as Array<Record<string, unknown>>)
51
- : [];
52
- return panels.map((panel, index) => {
53
- const panelConfig =
54
- panel.config &&
55
- typeof panel.config === "object" &&
56
- !Array.isArray(panel.config)
57
- ? (panel.config as Record<string, unknown>)
58
- : {};
59
- return {
60
- index,
61
- id: typeof panel.id === "string" ? panel.id : "",
62
- title: typeof panel.title === "string" ? panel.title : "",
63
- chartType: typeof panel.chartType === "string" ? panel.chartType : "",
64
- source: typeof panel.source === "string" ? panel.source : undefined,
65
- width: typeof panel.width === "number" ? panel.width : undefined,
66
- columns: typeof panel.columns === "number" ? panel.columns : undefined,
67
- tab: typeof panel.tab === "string" ? panel.tab : undefined,
68
- description:
69
- typeof panelConfig.description === "string"
70
- ? panelConfig.description
71
- : undefined,
72
- };
73
- });
74
- }
75
-
76
- function seededResponse(
77
- id: string,
78
- seed: Record<string, unknown>,
79
- includeConfig: boolean,
80
- ): Record<string, unknown> {
81
- const base = {
82
- id,
83
- name: typeof seed.name === "string" ? seed.name : id,
84
- description:
85
- typeof seed.description === "string" ? seed.description : undefined,
86
- filters: seed.filters,
87
- variables: seed.variables,
88
- columns: seed.columns,
89
- panels: panelSummaries(seed),
90
- layout: dashboardLayoutSummary(seed),
91
- ownerEmail: null,
92
- orgId: null,
93
- visibility: "org",
94
- archivedAt: null,
95
- hiddenAt: null,
96
- hiddenBy: null,
97
- };
98
- return includeConfig ? { ...base, ...seed } : base;
99
- }
100
15
 
101
16
  export default defineAction({
102
17
  description:
@@ -149,34 +64,15 @@ export default defineAction({
149
64
  if (!dash || dash.kind !== "sql") {
150
65
  const seed = loadDashboardSeed(args.id);
151
66
  if (seed)
152
- return seededResponse(args.id, seed, args.includeConfig === true);
67
+ return buildDashboardSeedAgentContext(args.id, seed, {
68
+ includeConfig: args.includeConfig === true,
69
+ });
153
70
  throw Object.assign(new Error("Dashboard not found"), {
154
71
  statusCode: 404,
155
72
  });
156
73
  }
157
- const config = dash.config as Record<string, unknown>;
158
- const base = {
159
- id: args.id,
160
- name: typeof config.name === "string" ? config.name : args.id,
161
- description:
162
- typeof config.description === "string" ? config.description : undefined,
163
- filters: config.filters,
164
- variables: config.variables,
165
- columns: config.columns,
166
- panels: panelSummaries(config),
167
- layout: dashboardLayoutSummary(config),
168
- ownerEmail: dash.ownerEmail,
169
- orgId: dash.orgId,
170
- visibility: dash.visibility,
171
- role: dash.role,
172
- canEdit: dash.canEdit,
173
- canManage: dash.canManage,
174
- archivedAt: dash.archivedAt,
175
- hiddenAt: dash.hiddenAt,
176
- hiddenBy: dash.hiddenBy,
177
- createdAt: dash.createdAt,
178
- updatedAt: dash.updatedAt,
179
- };
180
- return args.includeConfig === true ? { ...base, ...config } : base;
74
+ return buildDashboardAgentContext(dash, {
75
+ includeConfig: args.includeConfig === true,
76
+ });
181
77
  },
182
78
  });
@@ -1,6 +1,12 @@
1
1
  import { createGetDb } from "@agent-native/core/db";
2
2
  import { registerShareableResource } from "@agent-native/core/sharing";
3
3
 
4
+ import {
5
+ ANALYTICS_ANALYSIS_AGENT_CONTEXT_ENDPOINT,
6
+ ANALYTICS_ANALYSIS_AGENT_RESOURCE_KIND,
7
+ ANALYTICS_DASHBOARD_AGENT_CONTEXT_ENDPOINT,
8
+ ANALYTICS_DASHBOARD_AGENT_RESOURCE_KIND,
9
+ } from "../../shared/resource-agent-access.js";
4
10
  import * as schema from "./schema.js";
5
11
 
6
12
  export const getDb = createGetDb(schema);
@@ -13,6 +19,10 @@ registerShareableResource({
13
19
  displayName: "Dashboard",
14
20
  titleColumn: "title",
15
21
  getResourcePath: (dashboard) => `/dashboards/${dashboard.id}`,
22
+ agentReadable: {
23
+ resourceKind: ANALYTICS_DASHBOARD_AGENT_RESOURCE_KIND,
24
+ getContextPath: () => ANALYTICS_DASHBOARD_AGENT_CONTEXT_ENDPOINT,
25
+ },
16
26
  getDb,
17
27
  });
18
28
 
@@ -23,6 +33,10 @@ registerShareableResource({
23
33
  displayName: "Analysis",
24
34
  titleColumn: "name",
25
35
  getResourcePath: (analysis) => `/analyses/${analysis.id}`,
36
+ agentReadable: {
37
+ resourceKind: ANALYTICS_ANALYSIS_AGENT_RESOURCE_KIND,
38
+ getContextPath: () => ANALYTICS_ANALYSIS_AGENT_CONTEXT_ENDPOINT,
39
+ },
26
40
  getDb,
27
41
  });
28
42
 
@@ -0,0 +1,156 @@
1
+ import { getPanelOrder } from "../../actions/dashboard-panel-order.js";
2
+ import { buildDashboardPanelGroups } from "../../app/pages/adhoc/sql-dashboard/dashboard-layout";
3
+ import {
4
+ clampDashboardColumns,
5
+ type SqlPanel,
6
+ } from "../../app/pages/adhoc/sql-dashboard/types";
7
+ import type { AnalysisRecord, DashboardRecord } from "./dashboards-store.js";
8
+
9
+ function dashboardLayoutSummary(config: Record<string, unknown>) {
10
+ const panels = Array.isArray(config.panels)
11
+ ? (config.panels as SqlPanel[])
12
+ : [];
13
+ const columns = clampDashboardColumns(config.columns);
14
+ const groups = buildDashboardPanelGroups(panels, columns);
15
+ const panelOrder = getPanelOrder(config);
16
+ let visibleRowNumber = 1;
17
+
18
+ return {
19
+ panelCount: panelOrder.length,
20
+ panelOrder,
21
+ firstPanelIds: panelOrder.slice(0, 10),
22
+ groups: groups.map((group) => ({
23
+ key: group.key,
24
+ sectionId: group.section?.id ?? null,
25
+ sectionTitle: group.section?.title ?? null,
26
+ columns: group.columns,
27
+ rows: group.rows.map((row, rowIndex) => {
28
+ const rowNumber = visibleRowNumber++;
29
+ return {
30
+ rowNumber,
31
+ rowIndex,
32
+ panelIds: row.panels.map((panel) => panel.id),
33
+ };
34
+ }),
35
+ })),
36
+ };
37
+ }
38
+
39
+ function panelSummaries(config: Record<string, unknown>) {
40
+ const panels = Array.isArray(config.panels)
41
+ ? (config.panels as Array<Record<string, unknown>>)
42
+ : [];
43
+ return panels.map((panel, index) => {
44
+ const panelConfig =
45
+ panel.config &&
46
+ typeof panel.config === "object" &&
47
+ !Array.isArray(panel.config)
48
+ ? (panel.config as Record<string, unknown>)
49
+ : {};
50
+ return {
51
+ index,
52
+ id: typeof panel.id === "string" ? panel.id : "",
53
+ title: typeof panel.title === "string" ? panel.title : "",
54
+ chartType: typeof panel.chartType === "string" ? panel.chartType : "",
55
+ source: typeof panel.source === "string" ? panel.source : undefined,
56
+ width: typeof panel.width === "number" ? panel.width : undefined,
57
+ columns: typeof panel.columns === "number" ? panel.columns : undefined,
58
+ tab: typeof panel.tab === "string" ? panel.tab : undefined,
59
+ description:
60
+ typeof panelConfig.description === "string"
61
+ ? panelConfig.description
62
+ : undefined,
63
+ };
64
+ });
65
+ }
66
+
67
+ export function buildDashboardAgentContext(
68
+ dashboard: DashboardRecord,
69
+ options: { includeConfig?: boolean } = {},
70
+ ): Record<string, unknown> {
71
+ const config = dashboard.config as Record<string, unknown>;
72
+ const base = {
73
+ resourceType: "analytics-dashboard",
74
+ id: dashboard.id,
75
+ kind: dashboard.kind,
76
+ name: typeof config.name === "string" ? config.name : dashboard.title,
77
+ title: dashboard.title,
78
+ description:
79
+ typeof config.description === "string" ? config.description : undefined,
80
+ filters: config.filters,
81
+ variables: config.variables,
82
+ columns: config.columns,
83
+ panels: panelSummaries(config),
84
+ layout: dashboardLayoutSummary(config),
85
+ ownerEmail: dashboard.ownerEmail,
86
+ orgId: dashboard.orgId,
87
+ visibility: dashboard.visibility,
88
+ role: dashboard.role,
89
+ canEdit: dashboard.canEdit,
90
+ canManage: dashboard.canManage,
91
+ archivedAt: dashboard.archivedAt,
92
+ hiddenAt: dashboard.hiddenAt,
93
+ hiddenBy: dashboard.hiddenBy,
94
+ createdAt: dashboard.createdAt,
95
+ updatedAt: dashboard.updatedAt,
96
+ url: `/dashboards/${dashboard.id}`,
97
+ };
98
+ return options.includeConfig === false ? base : { ...base, ...config };
99
+ }
100
+
101
+ export function buildDashboardSeedAgentContext(
102
+ id: string,
103
+ seed: Record<string, unknown>,
104
+ options: { includeConfig?: boolean } = {},
105
+ ): Record<string, unknown> {
106
+ const base = {
107
+ resourceType: "analytics-dashboard",
108
+ id,
109
+ kind: "sql",
110
+ name: typeof seed.name === "string" ? seed.name : id,
111
+ title: typeof seed.name === "string" ? seed.name : id,
112
+ description:
113
+ typeof seed.description === "string" ? seed.description : undefined,
114
+ filters: seed.filters,
115
+ variables: seed.variables,
116
+ columns: seed.columns,
117
+ panels: panelSummaries(seed),
118
+ layout: dashboardLayoutSummary(seed),
119
+ ownerEmail: null,
120
+ orgId: null,
121
+ visibility: "org",
122
+ archivedAt: null,
123
+ hiddenAt: null,
124
+ hiddenBy: null,
125
+ url: `/dashboards/${id}`,
126
+ };
127
+ return options.includeConfig === false ? base : { ...base, ...seed };
128
+ }
129
+
130
+ export function buildAnalysisAgentContext(
131
+ analysis: AnalysisRecord,
132
+ ): Record<string, unknown> {
133
+ return {
134
+ resourceType: "analytics-analysis",
135
+ id: analysis.id,
136
+ name: analysis.name,
137
+ description: analysis.description,
138
+ question: analysis.question,
139
+ instructions: analysis.instructions,
140
+ dataSources: analysis.dataSources,
141
+ resultMarkdown: analysis.resultMarkdown,
142
+ resultData: analysis.resultData,
143
+ author: analysis.author,
144
+ createdAt: analysis.createdAt,
145
+ updatedAt: analysis.updatedAt,
146
+ ownerEmail: analysis.ownerEmail,
147
+ orgId: analysis.orgId,
148
+ visibility: analysis.visibility,
149
+ role: analysis.role,
150
+ canEdit: analysis.canEdit,
151
+ canManage: analysis.canManage,
152
+ hiddenAt: analysis.hiddenAt,
153
+ hiddenBy: analysis.hiddenBy,
154
+ url: `/analyses/${analysis.id}`,
155
+ };
156
+ }
@@ -1,4 +1,9 @@
1
+ import { AGENT_ACCESS_PARAM } from "@agent-native/core/server";
1
2
  import { createH3SSRHandler } from "@agent-native/core/server/ssr-handler";
3
+ import {
4
+ buildAgentReadableResourceDiscovery,
5
+ renderAgentReadableResourceDiscoveryScript,
6
+ } from "@agent-native/core/shared";
2
7
  import {
3
8
  defineEventHandler,
4
9
  getQuery,
@@ -6,6 +11,10 @@ import {
6
11
  setResponseHeader,
7
12
  } from "h3";
8
13
 
14
+ import {
15
+ ANALYTICS_ANALYSIS_AGENT_CONTEXT_ENDPOINT,
16
+ ANALYTICS_DASHBOARD_AGENT_CONTEXT_ENDPOINT,
17
+ } from "../../shared/resource-agent-access.js";
9
18
  import {
10
19
  buildSessionReplayAgentContext,
11
20
  safeJsonForHtml,
@@ -43,36 +52,92 @@ function sessionRecordingIdFromPath(pathname: string): string | null {
43
52
  }
44
53
  }
45
54
 
55
+ function analyticsResourceFromPath(
56
+ pathname: string,
57
+ ): { type: "dashboard" | "analysis"; id: string } | null {
58
+ const stripped = stripAppBasePath(pathname);
59
+ const dashboard = stripped.match(/^\/dashboards\/([^/]+)\/?$/);
60
+ const analysis = stripped.match(/^\/analyses\/([^/]+)\/?$/);
61
+ const type = dashboard ? "dashboard" : analysis ? "analysis" : null;
62
+ const rawId = dashboard?.[1] ?? analysis?.[1];
63
+ if (!type || !rawId) return null;
64
+ try {
65
+ return { type, id: decodeURIComponent(rawId) };
66
+ } catch {
67
+ return { type, id: rawId };
68
+ }
69
+ }
70
+
46
71
  function queryString(value: unknown): string {
47
72
  if (typeof value === "string") return value;
48
73
  if (Array.isArray(value) && typeof value[0] === "string") return value[0];
49
74
  return "";
50
75
  }
51
76
 
52
- async function buildAgentDiscoveryScript(event: any): Promise<string | null> {
77
+ interface AgentDiscoveryInjection {
78
+ script: string;
79
+ privateResponse: boolean;
80
+ }
81
+
82
+ async function buildAgentDiscoveryScript(
83
+ event: any,
84
+ ): Promise<AgentDiscoveryInjection | null> {
53
85
  const requestUrl = getRequestURL(event);
54
86
  const recordingId = sessionRecordingIdFromPath(requestUrl.pathname);
55
- if (!recordingId) return null;
56
-
57
87
  const query = getQuery(event);
58
- const token = queryString(query[SESSION_REPLAY_AGENT_ACCESS_PARAM]);
59
- if (!token) return null;
60
-
61
- const context = await buildSessionReplayAgentContext({
62
- recordingId,
63
- token,
64
- origin: requestUrl.origin,
65
- includeTimeline: false,
66
- }).catch(() => null);
67
- if (!context) return null;
68
-
69
- return `<script type="application/agent-native+json" id="analytics-session-replay-agent-context">${safeJsonForHtml(
70
- context,
71
- )}</script>`;
88
+ if (recordingId) {
89
+ const token = queryString(query[SESSION_REPLAY_AGENT_ACCESS_PARAM]);
90
+ if (!token) return null;
91
+
92
+ const context = await buildSessionReplayAgentContext({
93
+ recordingId,
94
+ token,
95
+ origin: requestUrl.origin,
96
+ includeTimeline: false,
97
+ }).catch(() => null);
98
+ if (!context) return null;
99
+
100
+ return {
101
+ script: `<script type="application/agent-native+json" id="analytics-session-replay-agent-context">${safeJsonForHtml(
102
+ context,
103
+ )}</script>`,
104
+ privateResponse: true,
105
+ };
106
+ }
107
+
108
+ const resource = analyticsResourceFromPath(requestUrl.pathname);
109
+ if (!resource) return null;
110
+
111
+ const token = queryString(query[AGENT_ACCESS_PARAM]);
112
+ const isDashboard = resource.type === "dashboard";
113
+ return {
114
+ script: renderAgentReadableResourceDiscoveryScript(
115
+ buildAgentReadableResourceDiscovery({
116
+ resourceType: isDashboard ? "dashboard" : "analysis",
117
+ resourceId: resource.id,
118
+ path: `/${isDashboard ? "dashboards" : "analyses"}/${encodeURIComponent(resource.id)}`,
119
+ contextEndpoint: isDashboard
120
+ ? ANALYTICS_DASHBOARD_AGENT_CONTEXT_ENDPOINT
121
+ : ANALYTICS_ANALYSIS_AGENT_CONTEXT_ENDPOINT,
122
+ origin: requestUrl.origin,
123
+ basePath: configuredAppBasePath(),
124
+ token,
125
+ instructions:
126
+ "Use contextUrl to read this Analytics artifact as structured JSON. Token links are read-only and do not grant edit access.",
127
+ }),
128
+ {
129
+ id: isDashboard
130
+ ? "analytics-dashboard-agent-context"
131
+ : "analytics-analysis-agent-context",
132
+ },
133
+ ),
134
+ privateResponse: Boolean(token),
135
+ };
72
136
  }
73
137
 
74
138
  function injectAgentDiscovery(html: string, script: string): string {
75
- if (html.includes('id="analytics-session-replay-agent-context"')) {
139
+ const scriptId = script.match(/\sid="([^"]+)"/)?.[1];
140
+ if (scriptId && html.includes(`id="${scriptId}"`)) {
76
141
  return html;
77
142
  }
78
143
  if (html.includes("</head>")) {
@@ -86,8 +151,8 @@ function injectAgentDiscovery(html: string, script: string): string {
86
151
 
87
152
  export default defineEventHandler(async (event) => {
88
153
  const response = (await ssrHandler(event)) as Response;
89
- const script = await buildAgentDiscoveryScript(event);
90
- if (!script) return response;
154
+ const discovery = await buildAgentDiscoveryScript(event);
155
+ if (!discovery) return response;
91
156
 
92
157
  const contentType = response.headers.get("content-type") ?? "";
93
158
  if (!contentType.includes("text/html")) return response;
@@ -95,12 +160,14 @@ export default defineEventHandler(async (event) => {
95
160
  const html = await response.text();
96
161
  const headers = new Headers(response.headers);
97
162
  headers.delete("content-length");
98
- headers.set("Cache-Control", "private, max-age=0, no-store");
99
- headers.set("Referrer-Policy", "no-referrer");
100
- setResponseHeader(event, "Cache-Control", "private, max-age=0, no-store");
101
- setResponseHeader(event, "Referrer-Policy", "no-referrer");
163
+ if (discovery.privateResponse) {
164
+ headers.set("Cache-Control", "private, max-age=0, no-store");
165
+ headers.set("Referrer-Policy", "no-referrer");
166
+ setResponseHeader(event, "Cache-Control", "private, max-age=0, no-store");
167
+ setResponseHeader(event, "Referrer-Policy", "no-referrer");
168
+ }
102
169
 
103
- return new Response(injectAgentDiscovery(html, script), {
170
+ return new Response(injectAgentDiscovery(html, discovery.script), {
104
171
  status: response.status,
105
172
  statusText: response.statusText,
106
173
  headers,
@@ -0,0 +1,94 @@
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 { ANALYTICS_ANALYSIS_AGENT_RESOURCE_KIND } from "../../../shared/resource-agent-access.js";
14
+ import { getDb, schema } from "../../db/index.js";
15
+ import { buildAnalysisAgentContext } from "../../lib/agent-readable-resource-context.js";
16
+ import type { AnalysisRecord } from "../../lib/dashboards-store.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
+ function parseJson<T>(value: unknown, fallback: T): T {
25
+ if (typeof value !== "string") return fallback;
26
+ try {
27
+ return JSON.parse(value) as T;
28
+ } catch {
29
+ return fallback;
30
+ }
31
+ }
32
+
33
+ function rowToAnalysis(row: any): AnalysisRecord {
34
+ return {
35
+ id: row.id,
36
+ name: row.name,
37
+ description: row.description,
38
+ question: row.question,
39
+ instructions: row.instructions,
40
+ dataSources: parseJson<string[]>(row.dataSources, []),
41
+ resultMarkdown: row.resultMarkdown,
42
+ resultData: row.resultData
43
+ ? parseJson<Record<string, unknown> | null>(row.resultData, null)
44
+ : null,
45
+ author: row.author ?? null,
46
+ ownerEmail: row.ownerEmail,
47
+ orgId: row.orgId ?? null,
48
+ visibility: row.visibility,
49
+ createdAt: row.createdAt,
50
+ updatedAt: row.updatedAt,
51
+ hiddenAt: row.hiddenAt ?? null,
52
+ hiddenBy: row.hiddenBy ?? null,
53
+ };
54
+ }
55
+
56
+ export default defineEventHandler(async (event) => {
57
+ setResponseHeader(event, "Cache-Control", "no-store");
58
+ setResponseHeader(event, "Referrer-Policy", "no-referrer");
59
+ setResponseHeader(event, "X-Content-Type-Options", "nosniff");
60
+
61
+ const query = getQuery(event);
62
+ const id = queryString(query.id);
63
+ if (!id) {
64
+ setResponseStatus(event, 400);
65
+ return { error: "Analysis id is required" };
66
+ }
67
+
68
+ const token = queryString(query[AGENT_ACCESS_PARAM]);
69
+ const db = getDb() as any;
70
+ // guard:allow-unscoped -- this endpoint returns analysis context only when the analysis is public or an analysis-scoped agent_access token verifies for this id.
71
+ const [row] = await db
72
+ .select()
73
+ .from(schema.analyses)
74
+ .where(eq(schema.analyses.id, id))
75
+ .limit(1);
76
+
77
+ if (!row) {
78
+ setResponseStatus(event, 404);
79
+ return { error: "Analysis not found" };
80
+ }
81
+
82
+ const tokenAccess = token
83
+ ? verifyScopedAgentAccessToken(token, {
84
+ resourceKind: ANALYTICS_ANALYSIS_AGENT_RESOURCE_KIND,
85
+ resourceId: id,
86
+ }).ok
87
+ : false;
88
+ if (row.visibility !== "public" && !tokenAccess) {
89
+ setResponseStatus(event, 403);
90
+ return { error: "Invalid or expired agent access token" };
91
+ }
92
+
93
+ return buildAnalysisAgentContext(rowToAnalysis(row));
94
+ });
@@ -0,0 +1,109 @@
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 { ANALYTICS_DASHBOARD_AGENT_RESOURCE_KIND } from "../../../shared/resource-agent-access.js";
14
+ import { getDb, schema } from "../../db/index.js";
15
+ import {
16
+ buildDashboardAgentContext,
17
+ buildDashboardSeedAgentContext,
18
+ } from "../../lib/agent-readable-resource-context.js";
19
+ import { loadDashboardSeed } from "../../lib/dashboard-seeds.js";
20
+ import type { DashboardRecord } from "../../lib/dashboards-store.js";
21
+
22
+ function queryString(value: unknown): string {
23
+ if (typeof value === "string") return value;
24
+ if (Array.isArray(value) && typeof value[0] === "string") return value[0];
25
+ return "";
26
+ }
27
+
28
+ function parseJsonObject(value: unknown): Record<string, unknown> {
29
+ if (value && typeof value === "object" && !Array.isArray(value)) {
30
+ return value as Record<string, unknown>;
31
+ }
32
+ if (typeof value !== "string") return {};
33
+ try {
34
+ const parsed = JSON.parse(value);
35
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
36
+ ? (parsed as Record<string, unknown>)
37
+ : {};
38
+ } catch {
39
+ return {};
40
+ }
41
+ }
42
+
43
+ function rowToDashboard(row: any): DashboardRecord {
44
+ return {
45
+ id: row.id,
46
+ kind: row.kind,
47
+ title: row.title,
48
+ config: parseJsonObject(row.config),
49
+ ownerEmail: row.ownerEmail,
50
+ orgId: row.orgId ?? null,
51
+ visibility: row.visibility,
52
+ createdAt: row.createdAt,
53
+ updatedAt: row.updatedAt,
54
+ archivedAt: row.archivedAt ?? null,
55
+ hiddenAt: row.hiddenAt ?? null,
56
+ hiddenBy: row.hiddenBy ?? null,
57
+ };
58
+ }
59
+
60
+ export default defineEventHandler(async (event) => {
61
+ setResponseHeader(event, "Cache-Control", "no-store");
62
+ setResponseHeader(event, "Referrer-Policy", "no-referrer");
63
+ setResponseHeader(event, "X-Content-Type-Options", "nosniff");
64
+
65
+ const query = getQuery(event);
66
+ const id = queryString(query.id);
67
+ if (!id) {
68
+ setResponseStatus(event, 400);
69
+ return { error: "Dashboard id is required" };
70
+ }
71
+
72
+ const token = queryString(query[AGENT_ACCESS_PARAM]);
73
+ const tokenAccess = token
74
+ ? verifyScopedAgentAccessToken(token, {
75
+ resourceKind: ANALYTICS_DASHBOARD_AGENT_RESOURCE_KIND,
76
+ resourceId: id,
77
+ }).ok
78
+ : false;
79
+ const db = getDb() as any;
80
+ // guard:allow-unscoped -- this endpoint returns dashboard context only when the dashboard is public or a dashboard-scoped agent_access token verifies for this id.
81
+ const [row] = await db
82
+ .select()
83
+ .from(schema.dashboards)
84
+ .where(eq(schema.dashboards.id, id))
85
+ .limit(1);
86
+
87
+ const seed = row ? null : loadDashboardSeed(id);
88
+ if (!row && seed) {
89
+ if (!tokenAccess) {
90
+ setResponseStatus(event, 403);
91
+ return { error: "Invalid or expired agent access token" };
92
+ }
93
+ return buildDashboardSeedAgentContext(id, seed, { includeConfig: true });
94
+ }
95
+
96
+ if (!row) {
97
+ setResponseStatus(event, 404);
98
+ return { error: "Dashboard not found" };
99
+ }
100
+
101
+ if (row.visibility !== "public" && !tokenAccess) {
102
+ setResponseStatus(event, 403);
103
+ return { error: "Invalid or expired agent access token" };
104
+ }
105
+
106
+ return buildDashboardAgentContext(rowToDashboard(row), {
107
+ includeConfig: true,
108
+ });
109
+ });