@xpert-ai/plugin-live-dashboard 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/.xpertai-plugin/plugin.json +10 -2
  2. package/README.md +6 -0
  3. package/assets/branding/cover-v1.prompt.txt +27 -0
  4. package/assets/branding/cover-v1.webp +0 -0
  5. package/assets/previews/customer-brief.png +0 -0
  6. package/assets/previews/profit-atlas.png +0 -0
  7. package/assets/previews/trend-workspace.png +0 -0
  8. package/dist/index.js +6 -3
  9. package/dist/lib/agent.schema.d.ts +43 -37
  10. package/dist/lib/agent.schema.js +2 -1
  11. package/dist/lib/app-config.d.ts +62 -0
  12. package/dist/lib/app-config.js +38 -0
  13. package/dist/lib/artifact-type.provider.d.ts +15 -0
  14. package/dist/lib/artifact-type.provider.js +78 -0
  15. package/dist/lib/assistant-template.d.ts +1 -2
  16. package/dist/lib/assistant-template.js +3 -52
  17. package/dist/lib/content.provider.js +13 -5
  18. package/dist/lib/dashboard-navigation.d.ts +58 -14
  19. package/dist/lib/dashboard-navigation.js +11 -10
  20. package/dist/lib/dashboard-view.schema.d.ts +278 -46
  21. package/dist/lib/dashboard-view.schema.js +9 -1
  22. package/dist/lib/dashboard.service.d.ts +49 -3
  23. package/dist/lib/dashboard.service.js +64 -28
  24. package/dist/lib/document-libraries.d.ts +1 -1
  25. package/dist/lib/document-libraries.js +3 -2
  26. package/dist/lib/document-network.d.ts +1 -1
  27. package/dist/lib/document-network.js +2 -6
  28. package/dist/lib/editing-feedback.d.ts +5 -0
  29. package/dist/lib/editing-feedback.js +19 -0
  30. package/dist/lib/editing-patch.d.ts +22 -0
  31. package/dist/lib/editing-patch.js +46 -0
  32. package/dist/lib/editing-session.d.ts +167 -0
  33. package/dist/lib/editing-session.js +53 -0
  34. package/dist/lib/editing.d.ts +92 -60
  35. package/dist/lib/editing.js +36 -24
  36. package/dist/lib/html-guidance.d.ts +1 -1
  37. package/dist/lib/html-guidance.js +1 -1
  38. package/dist/lib/middleware.js +42 -18
  39. package/dist/lib/monitor.d.ts +70 -0
  40. package/dist/lib/monitor.js +101 -0
  41. package/dist/lib/plugin.js +4 -1
  42. package/dist/lib/project.d.ts +106 -0
  43. package/dist/lib/project.js +17 -0
  44. package/dist/lib/projects.schema.d.ts +63 -0
  45. package/dist/lib/projects.schema.js +8 -0
  46. package/dist/lib/projects.service.d.ts +25 -0
  47. package/dist/lib/projects.service.js +75 -0
  48. package/dist/lib/resource-card.js +1 -1
  49. package/dist/lib/runtime-context.d.ts +3 -0
  50. package/dist/lib/runtime-context.js +13 -0
  51. package/dist/lib/save-evidence.d.ts +1 -1
  52. package/dist/lib/templates.d.ts +2 -0
  53. package/dist/lib/templates.js +55 -0
  54. package/dist/lib/view.provider.d.ts +127 -8
  55. package/dist/lib/view.provider.js +49 -12
  56. package/dist/libraries/TREND-WORKSPACE-ICONS-LICENSE.txt +43 -0
  57. package/dist/libraries/catalog.json +1 -1
  58. package/dist/remote/app.css +1 -1
  59. package/dist/remote/app.js +34 -34
  60. package/dist/remote/build-manifest.json +116 -32
  61. package/dist/sandbox-actions/action.json +1 -1
  62. package/dist/sandbox-actions/bundle/host.js +2 -2
  63. package/dist/sandbox-actions/bundle/resource-cache.mjs +2 -1
  64. package/dist/sandbox-actions/bundle/validate.mjs +1 -1
  65. package/docs/docs.json +7 -0
  66. package/docs/frontend-libraries.mdx +12 -4
  67. package/docs/html-mode.md +3 -3
  68. package/docs/quick-start-acceptance.mdx +50 -0
  69. package/docs/quick-start.mdx +64 -0
  70. package/docs/superstore.mdx +38 -0
  71. package/docs/team-sharing.mdx +6 -2
  72. package/docs/trend-workspace-acceptance.mdx +43 -0
  73. package/docs/trend-workspace.mdx +30 -0
  74. package/docs/verification.mdx +4 -2
  75. package/docs/visual-styles-acceptance.mdx +40 -0
  76. package/docs/visual-styles.mdx +46 -0
  77. package/package.json +23 -17
  78. package/skills/live-dashboard/SKILL.md +27 -3
  79. package/skills/live-dashboard/examples/README.md +25 -0
  80. package/skills/live-dashboard/examples/catalog.json +79 -0
  81. package/skills/live-dashboard/examples/production-quality/README.md +25 -0
  82. package/skills/live-dashboard/examples/production-quality/dashboard.html +129 -0
  83. package/skills/live-dashboard/examples/production-quality/definition.json +67 -0
  84. package/skills/live-dashboard/examples/production-quality/demo.csv +7 -0
  85. package/skills/live-dashboard/examples/production-quality/demo.json +44 -0
  86. package/skills/live-dashboard/examples/production-quality/expected.json +26 -0
  87. package/skills/live-dashboard/examples/production-quality/preview.png +0 -0
  88. package/skills/live-dashboard/examples/production-quality/query.json +53 -0
  89. package/skills/live-dashboard/examples/regional-sales/README.md +25 -0
  90. package/skills/live-dashboard/examples/regional-sales/dashboard-create.json +35 -0
  91. package/skills/live-dashboard/examples/regional-sales/definition.json +35 -0
  92. package/skills/live-dashboard/examples/regional-sales/demo.csv +5 -0
  93. package/skills/live-dashboard/examples/regional-sales/demo.json +18 -0
  94. package/skills/live-dashboard/examples/regional-sales/expected.json +19 -0
  95. package/skills/live-dashboard/examples/regional-sales/preview.png +0 -0
  96. package/skills/live-dashboard/examples/regional-sales/query.json +45 -0
  97. package/skills/live-dashboard/examples/sales-overview/README.md +25 -0
  98. package/skills/live-dashboard/examples/sales-overview/dashboard.html +129 -0
  99. package/skills/live-dashboard/examples/sales-overview/definition.json +57 -0
  100. package/skills/live-dashboard/examples/sales-overview/demo.csv +7 -0
  101. package/skills/live-dashboard/examples/sales-overview/demo.json +38 -0
  102. package/skills/live-dashboard/examples/sales-overview/expected.json +22 -0
  103. package/skills/live-dashboard/examples/sales-overview/preview.png +0 -0
  104. package/skills/live-dashboard/examples/sales-overview/query.json +52 -0
  105. package/skills/live-dashboard/examples/superstore/README.md +64 -0
  106. package/skills/live-dashboard/examples/superstore/THIRD_PARTY_LICENSE.txt +207 -0
  107. package/skills/live-dashboard/examples/superstore/build_data.py +155 -0
  108. package/skills/live-dashboard/examples/superstore/build_pages.py +129 -0
  109. package/skills/live-dashboard/examples/superstore/checks.json +104 -0
  110. package/skills/live-dashboard/examples/superstore/customers-mobile-dark.png +0 -0
  111. package/skills/live-dashboard/examples/superstore/customers-select.png +0 -0
  112. package/skills/live-dashboard/examples/superstore/customers.html +316 -0
  113. package/skills/live-dashboard/examples/superstore/customers.png +0 -0
  114. package/skills/live-dashboard/examples/superstore/customers.queries.json +512 -0
  115. package/skills/live-dashboard/examples/superstore/expected.json +4069 -0
  116. package/skills/live-dashboard/examples/superstore/import.json +200 -0
  117. package/skills/live-dashboard/examples/superstore/orders.csv +10195 -0
  118. package/skills/live-dashboard/examples/superstore/overview-mobile-dark.png +0 -0
  119. package/skills/live-dashboard/examples/superstore/overview-select.png +0 -0
  120. package/skills/live-dashboard/examples/superstore/overview.html +310 -0
  121. package/skills/live-dashboard/examples/superstore/overview.png +0 -0
  122. package/skills/live-dashboard/examples/superstore/overview.queries.json +636 -0
  123. package/skills/live-dashboard/examples/superstore/page.js +109 -0
  124. package/skills/live-dashboard/examples/superstore/profitability-mobile-dark.png +0 -0
  125. package/skills/live-dashboard/examples/superstore/profitability-select.png +0 -0
  126. package/skills/live-dashboard/examples/superstore/profitability.html +307 -0
  127. package/skills/live-dashboard/examples/superstore/profitability.png +0 -0
  128. package/skills/live-dashboard/examples/superstore/profitability.queries.json +334 -0
  129. package/skills/live-dashboard/examples/superstore/provenance.json +24 -0
  130. package/skills/live-dashboard/examples/superstore/semantic-model.json +220 -0
  131. package/skills/live-dashboard/examples/superstore/test_data.py +62 -0
  132. package/skills/live-dashboard/references/html-runtime.md +1 -1
  133. package/skills/live-dashboard/themes/README.md +50 -0
  134. package/skills/live-dashboard/themes/customer-brief/README.md +47 -0
  135. package/skills/live-dashboard/themes/customer-brief/charts.js +17 -0
  136. package/skills/live-dashboard/themes/customer-brief/layout.html +22 -0
  137. package/skills/live-dashboard/themes/customer-brief/theme.css +108 -0
  138. package/skills/live-dashboard/themes/profit-atlas/README.md +43 -0
  139. package/skills/live-dashboard/themes/profit-atlas/charts.js +30 -0
  140. package/skills/live-dashboard/themes/profit-atlas/layout.html +19 -0
  141. package/skills/live-dashboard/themes/profit-atlas/theme.css +75 -0
  142. package/skills/live-dashboard/themes/trend-workspace/ICON_LICENSE.txt +43 -0
  143. package/skills/live-dashboard/themes/trend-workspace/README.md +144 -0
  144. package/skills/live-dashboard/themes/trend-workspace/charts.js +60 -0
  145. package/skills/live-dashboard/themes/trend-workspace/icons.js +11 -0
  146. package/skills/live-dashboard/themes/trend-workspace/layout.html +45 -0
  147. package/skills/live-dashboard/themes/trend-workspace/select.js +127 -0
  148. package/skills/live-dashboard/themes/trend-workspace/theme.css +137 -0
@@ -1,18 +1,26 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.dashboardViewDataSchema = exports.dashboardSavedSchema = exports.dashboardSummarySchema = void 0;
3
+ exports.dashboardViewDataSchema = exports.dashboardSavedSchema = exports.dashboardPickerPageSchema = exports.dashboardSummarySchema = void 0;
4
4
  const v3_1 = require("zod/v3");
5
5
  const analytics_1 = require("@xpert-ai/plugin-sdk/analytics");
6
6
  const artifact_document_1 = require("@xpert-ai/plugin-sdk/artifact-document");
7
+ const projects_schema_1 = require("./projects.schema");
7
8
  const dashboard_schema_1 = require("./dashboard.schema");
8
9
  const dashboard_navigation_1 = require("./dashboard-navigation");
9
10
  exports.dashboardSummarySchema = v3_1.z.object({ artifactId: v3_1.z.string(), versionId: v3_1.z.string().nullish(), versionNumber: v3_1.z.number().optional(), title: v3_1.z.string().nullish(), updatedAt: v3_1.z.string().optional(), draftRevision: v3_1.z.number().optional(), canDelete: v3_1.z.boolean().optional(), isDraft: v3_1.z.boolean().optional(), baseVersionId: v3_1.z.string().nullish() });
11
+ exports.dashboardPickerPageSchema = v3_1.z.object({ items: v3_1.z.array(exports.dashboardSummarySchema), page: v3_1.z.number().int().positive(), hasMore: v3_1.z.boolean(), canEdit: v3_1.z.boolean().default(false), mode: v3_1.z.enum(['draft', 'published']).optional() });
10
12
  exports.dashboardSavedSchema = exports.dashboardSummarySchema.extend({ manifest: dashboard_schema_1.dashboardSchema, dependencies: artifact_document_1.artifactDocumentDependencyAssets.optional() });
11
13
  // Source identities belong to Analytics, including immutable Definitions and Artifacts.
12
14
  // The viewer consumes only the reference and label; DataSource fields are not universal.
13
15
  exports.dashboardViewDataSchema = v3_1.z.object({ meta: v3_1.z.object({
16
+ currentProject: projects_schema_1.dashboardProjectSchema.nullable().optional(),
17
+ access: v3_1.z.object({ project: v3_1.z.boolean(), archived: v3_1.z.boolean(), canReadDraft: v3_1.z.boolean(), canEdit: v3_1.z.boolean(), canManage: v3_1.z.boolean() }).optional(),
18
+ mode: v3_1.z.enum(['published', 'draft']).optional(),
19
+ notice: v3_1.z.literal('not_published').optional(),
20
+ pagination: v3_1.z.object({ page: v3_1.z.number(), pageSize: v3_1.z.number(), hasMore: v3_1.z.boolean() }).optional(),
14
21
  navigationTarget: dashboard_navigation_1.dashboardNavigationTargetSchema.optional(),
15
22
  dashboards: v3_1.z.array(exports.dashboardSummarySchema),
23
+ selectedDashboard: exports.dashboardSummarySchema.optional(),
16
24
  sources: v3_1.z.array(v3_1.z.object({ source: analytics_1.analyticsSourceRefSchema, name: v3_1.z.string() })),
17
25
  saved: exports.dashboardSavedSchema.optional(),
18
26
  versions: v3_1.z.array(exports.dashboardSummarySchema).optional()
@@ -2,6 +2,7 @@ import type { AnalyticsValue, AnalyticsDiscoveryRequest, AnalyticsQueryRequest,
2
2
  import { type RuntimeCapabilityRegistry } from '@xpert-ai/plugin-sdk';
3
3
  import { type DashboardManifest, type DashboardChanges } from './dashboard.schema';
4
4
  import { HtmlDashboardValidation } from './html-validation';
5
+ import type { DashboardAccess, DashboardMode } from './project';
5
6
  export declare const PLUGIN_NAME = "@xpert-ai/plugin-live-dashboard";
6
7
  export declare class DashboardService {
7
8
  private readonly runtime;
@@ -13,12 +14,52 @@ export declare class DashboardService {
13
14
  list(context: XpertResolvedViewHostContext): Promise<{
14
15
  title: string | null | undefined;
15
16
  draftRevision?: number | undefined;
17
+ updatedAt: string | Date | undefined;
16
18
  canDelete: boolean;
17
19
  artifactId: string;
18
20
  versionId: string | null | undefined;
19
21
  versionNumber: number | undefined;
20
- updatedAt: string | Date | undefined;
21
22
  }[]>;
23
+ access(context: XpertResolvedViewHostContext): Promise<DashboardAccess>;
24
+ listPage(context: XpertResolvedViewHostContext, page?: number, pageSize?: number): Promise<{
25
+ page: number;
26
+ pageSize: number;
27
+ hasMore: boolean;
28
+ items: {
29
+ title: string | null | undefined;
30
+ draftRevision?: number | undefined;
31
+ updatedAt: string | Date | undefined;
32
+ canDelete: boolean;
33
+ artifactId: string;
34
+ versionId: string | null | undefined;
35
+ versionNumber: number | undefined;
36
+ }[];
37
+ }>;
38
+ searchPage(context: XpertResolvedViewHostContext, input: {
39
+ search: string;
40
+ page: number;
41
+ }): Promise<{
42
+ items: {
43
+ title: string | null | undefined;
44
+ draftRevision?: number | undefined;
45
+ updatedAt: string | Date | undefined;
46
+ canDelete: boolean;
47
+ artifactId: string;
48
+ versionId: string | null | undefined;
49
+ versionNumber: number | undefined;
50
+ }[];
51
+ page: number;
52
+ hasMore: boolean;
53
+ }>;
54
+ selectedSummary(context: XpertResolvedViewHostContext, artifactId: string): Promise<{
55
+ title: string | null | undefined;
56
+ draftRevision?: number | undefined;
57
+ canDelete: boolean;
58
+ artifactId: string;
59
+ versionId: string | null | undefined;
60
+ versionNumber: number | undefined;
61
+ updatedAt: string | Date | undefined;
62
+ } | undefined>;
22
63
  trash(context: XpertResolvedViewHostContext, artifactId: string, expectedRevision: number): Promise<{
23
64
  artifactId: string;
24
65
  }>;
@@ -30,7 +71,7 @@ export declare class DashboardService {
30
71
  status: "draft";
31
72
  title: string;
32
73
  }>;
33
- open(context: XpertResolvedViewHostContext, artifactId: string, versionId?: string, draftRevision?: number): Promise<{
74
+ open(context: XpertResolvedViewHostContext, artifactId: string, versionId?: string, draftRevision?: number, mode?: DashboardMode): Promise<{
34
75
  manifest: {
35
76
  schema: "xpert.live-dashboard";
36
77
  id: string;
@@ -64,6 +105,7 @@ export declare class DashboardService {
64
105
  dependencies?: import("@xpert-ai/plugin-sdk/artifact-document").ArtifactDocumentDependencyLock[] | undefined;
65
106
  } | undefined;
66
107
  };
108
+ contentHash: string;
67
109
  draftId?: string | undefined;
68
110
  draftRevision?: number | undefined;
69
111
  baseVersionId?: string | null | undefined;
@@ -75,7 +117,7 @@ export declare class DashboardService {
75
117
  versionNumber: number | undefined;
76
118
  }>;
77
119
  /** Trusted View transport only. Agent read tools receive locks, never megabytes of library code. */
78
- openDocument(context: XpertResolvedViewHostContext, artifactId: string, versionId?: string, draftRevision?: number): Promise<{
120
+ openDocument(context: XpertResolvedViewHostContext, artifactId: string, versionId?: string, draftRevision?: number, mode?: DashboardMode): Promise<{
79
121
  manifest: {
80
122
  schema: "xpert.live-dashboard";
81
123
  id: string;
@@ -110,6 +152,7 @@ export declare class DashboardService {
110
152
  } | undefined;
111
153
  };
112
154
  dependencies: import("@xpert-ai/plugin-sdk/artifact-document").ArtifactDocumentDependencyAsset[];
155
+ contentHash: string;
113
156
  draftId?: string | undefined;
114
157
  draftRevision?: number | undefined;
115
158
  baseVersionId?: string | null | undefined;
@@ -142,6 +185,8 @@ export declare class DashboardService {
142
185
  versionNumber: number;
143
186
  title: string | null | undefined;
144
187
  }>;
188
+ validatePublication(context: XpertResolvedViewHostContext, artifactId: string, expectedRevision: number, expectedCurrentVersionId: string | null): Promise<void>;
189
+ private publishDraft;
145
190
  query(context: XpertResolvedViewHostContext, artifactId: string, versionId: string | undefined, controls: Record<string, AnalyticsValue>, draftRevision?: number): Promise<{
146
191
  payload?: {
147
192
  ok: boolean;
@@ -237,6 +282,7 @@ export declare class DashboardService {
237
282
  dependencies?: import("@xpert-ai/plugin-sdk/artifact-document").ArtifactDocumentDependencyLock[] | undefined;
238
283
  } | undefined;
239
284
  };
285
+ contentHash: string;
240
286
  draftId?: string | undefined;
241
287
  draftRevision?: number | undefined;
242
288
  baseVersionId?: string | null | undefined;
@@ -32,23 +32,61 @@ let DashboardService = class DashboardService {
32
32
  this.runtime = runtime;
33
33
  this.htmlValidation = htmlValidation;
34
34
  }
35
- sources(context) { return this.runtime.require(plugin_sdk_1.AnalyticsRuntimeCapability).list(actor(context)); }
36
- discover(context, input) { return this.runtime.require(plugin_sdk_1.AnalyticsRuntimeCapability).discover(actor(context), input); }
35
+ async sources(context) { await this.authorizeProject(context); return this.runtime.require(plugin_sdk_1.AnalyticsRuntimeCapability).list(actor(context)); }
36
+ async discover(context, input) { await this.authorizeProject(context); return this.runtime.require(plugin_sdk_1.AnalyticsRuntimeCapability).discover(actor(context), input); }
37
37
  async preview(context, input) {
38
+ await this.authorizeProject(context);
38
39
  const dataset = await this.runtime.require(plugin_sdk_1.AnalyticsRuntimeCapability).query(actor(context), input);
39
40
  await this.probeControls(context, input.binding, input.controls ?? {});
40
41
  return dataset;
41
42
  }
42
43
  async list(context) {
44
+ return (await this.listPage(context)).items;
45
+ }
46
+ async access(context) {
43
47
  const access = await this.authorizeProject(context);
44
- const result = await this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability).listArtifacts({ pluginName: exports.PLUGIN_NAME, resourceType: RESOURCE_TYPE, resourceIdPrefix: hostPrefix(context), status: 'active', pageSize: 100, includeCurrentVersion: true });
48
+ return { project: !!projectId(context), archived: access?.archived ?? false,
49
+ canReadDraft: !access || access.role !== 'member', canEdit: !access || !access.archived && access.role !== 'member',
50
+ canManage: !access || !access.archived && access.canManage };
51
+ }
52
+ async listPage(context, page = 1, pageSize = 50) {
53
+ const access = await this.access(context);
54
+ if (!Number.isSafeInteger(page) || page < 1 || !Number.isSafeInteger(pageSize) || pageSize < 1 || pageSize > 100)
55
+ throw new common_1.BadRequestException('DASHBOARD_INVALID_CURSOR');
56
+ const result = await this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability).listArtifacts({ pluginName: exports.PLUGIN_NAME, resourceType: RESOURCE_TYPE, resourceIdPrefix: hostPrefix(context), status: 'active', page, pageSize, includeCurrentVersion: true });
45
57
  const items = result.items.filter((item) => item.resourceId.startsWith(hostPrefix(context)) && (item.userId === context.userId || !item.userId && item.projectId === projectId(context)));
46
- const drafts = new Map((await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).list(items.map(item => item.id))).map(draft => [draft.artifactId, draft]));
47
- return items.map(item => {
48
- const draft = drafts.get(item.id);
49
- return { ...summary(item), canDelete: !!draft && (!projectId(context) || access?.canManage === true),
50
- ...(draft ? { title: draft.title ?? item.title, draftRevision: draft.revision } : {}) };
51
- });
58
+ const drafts = new Map((access.canReadDraft ? await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).list(items.map(item => item.id)) : []).map(draft => [draft.artifactId, draft]));
59
+ return { page, pageSize, hasMore: page * pageSize < result.total, items: items.filter(item => access.canReadDraft || !!item.currentVersionId).map(item => {
60
+ const draft = drafts.get(item.id);
61
+ return { ...summary(item), canDelete: !!draft && access.canManage,
62
+ ...(draft ? { title: draft.title ?? item.title, draftRevision: draft.revision, updatedAt: draft.updatedAt ?? item.updatedAt } : {}) };
63
+ }) };
64
+ }
65
+ async searchPage(context, input) {
66
+ // Search the authorized display titles, including editor drafts. Filtering a stored
67
+ // Artifact title in the host would miss renamed drafts or expose them to members.
68
+ const pageSize = 20, offset = (input.page - 1) * pageSize;
69
+ const search = input.search.trim().toLocaleLowerCase();
70
+ const matches = [];
71
+ for (let page = 1;; page++) {
72
+ const batch = await this.listPage(context, page, 100);
73
+ matches.push(...batch.items.filter(item => `${item.title ?? ''} ${item.artifactId}`.toLocaleLowerCase().includes(search)));
74
+ if (!batch.hasMore || matches.length > offset + pageSize)
75
+ break;
76
+ }
77
+ return { items: matches.slice(offset, offset + pageSize), page: input.page, hasMore: matches.length > offset + pageSize };
78
+ }
79
+ async selectedSummary(context, artifactId) {
80
+ const access = await this.access(context);
81
+ const artifact = await this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability).getArtifact(artifactId);
82
+ assertOwned(context, artifact);
83
+ if (artifact.status !== 'active')
84
+ throw new common_1.ForbiddenException('DASHBOARD_ARCHIVED');
85
+ if (!access.canReadDraft && !artifact.currentVersionId)
86
+ return undefined;
87
+ const draft = access.canReadDraft ? (await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).list([artifactId]))[0] : undefined;
88
+ return { ...summary(artifact), canDelete: !!draft && access.canManage,
89
+ ...(draft ? { title: draft.title ?? artifact.title, draftRevision: draft.revision } : {}) };
52
90
  }
53
91
  async trash(context, artifactId, expectedRevision) {
54
92
  await this.authorizeProject(context, 'manage');
@@ -88,38 +126,30 @@ let DashboardService = class DashboardService {
88
126
  for (const binding of parsed.bindings)
89
127
  await this.probeControls(context, binding, Object.fromEntries(parsed.controls.filter((control) => control.defaultValue !== undefined).map((control) => [control.controlId, control.defaultValue ?? null])));
90
128
  const htmlValidation = await this.htmlValidation.validate(context, parsed, preview, dependencies);
91
- const artifact = existing ?? await artifacts.createArtifact({ source, kind: 'file', title: parsed.title,
129
+ const artifact = existing ?? await artifacts.createArtifact({ source, type: { pluginName: exports.PLUGIN_NAME, resourceType: RESOURCE_TYPE }, kind: 'file', title: parsed.title,
92
130
  scope: { ...scopedActor, ...(projectId(context) ? { projectId: projectId(context), userId: null } : { xpertId: context.hostId }) } });
93
- const files = this.runtime.require(plugin_sdk_1.WorkspaceFilesRuntimeCapability);
94
- const project = projectId(context);
95
- // Shared definitions live in the Project volume; private legacy dashboards retain their user volume.
96
- const privateScope = { tenantId: scopedActor.tenantId, userId: scopedActor.userId, catalog: 'users' };
97
- const reference = project
98
- ? (await files.writeRuntimeBuffer({ tenantId: scopedActor.tenantId, userId: scopedActor.userId,
99
- catalog: 'projects', scopeId: project, projectId: project, buffer, originalName: 'dashboard.json',
100
- fileName: `${digest}.json`, folder: `.xpert/live-dashboards/${parsed.id}`, mimeType: MIME_TYPE })).reference
101
- : await files.uploadBuffer({ ...privateScope, buffer, originalName: 'dashboard.json', fileName: `${digest}.json`,
102
- folder: `live-dashboards/${parsed.id}`, mimeType: MIME_TYPE }).then(file => ({
103
- source: 'platform.workspace.files', ...privateScope, filePath: file.filePath, workspacePath: file.workspacePath
104
- }));
131
+ const reference = await this.runtime.require(plugin_sdk_1.ArtifactContentRuntimeCapability).write({ artifactId: artifact.id, buffer });
105
132
  const result = await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).save({ artifactId: artifact.id, expectedRevision,
106
133
  expectedCurrentVersionId: expectedVersionId, mimeType: MIME_TYPE, title: parsed.title, fileName: 'dashboard.json', sha256: digest,
107
134
  ...(htmlValidation ? { metadata: { htmlValidation } } : {}), workspaceFileRef: reference });
108
135
  return { artifactId: artifact.id, draftId: result.id, draftRevision: result.revision, baseVersionId: result.baseVersionId, status: 'draft', title: parsed.title };
109
136
  }
110
- async open(context, artifactId, versionId, draftRevision) {
111
- const { dependencies, ...saved } = await this.openDocument(context, artifactId, versionId, draftRevision);
137
+ async open(context, artifactId, versionId, draftRevision, mode = 'draft') {
138
+ const { dependencies, ...saved } = await this.openDocument(context, artifactId, versionId, draftRevision, mode);
112
139
  return saved;
113
140
  }
114
141
  /** Trusted View transport only. Agent read tools receive locks, never megabytes of library code. */
115
- async openDocument(context, artifactId, versionId, draftRevision) {
116
- await this.authorizeProject(context);
142
+ async openDocument(context, artifactId, versionId, draftRevision, mode = 'draft') {
143
+ const access = await this.access(context);
144
+ const readDraft = draftRevision !== undefined || !versionId && mode === 'draft';
145
+ if (readDraft && !access.canReadDraft)
146
+ throw new common_1.ForbiddenException('DASHBOARD_DRAFT_ACCESS_DENIED');
117
147
  const artifacts = this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability);
118
148
  const artifact = await artifacts.getArtifact(artifactId);
119
149
  assertOwned(context, artifact);
120
150
  if (artifact.status !== 'active')
121
151
  throw new common_1.ForbiddenException('DASHBOARD_ARCHIVED');
122
- const draft = !versionId || draftRevision !== undefined ? await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).get(artifactId) : null;
152
+ const draft = readDraft ? await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).get(artifactId) : null;
123
153
  if (draftRevision !== undefined && draft?.revision !== draftRevision)
124
154
  throw new common_1.ConflictException('DASHBOARD_DRAFT_CONFLICT');
125
155
  const version = draft ?? (versionId ? (await artifacts.listArtifactVersions({ artifactId, status: 'active' })).find((item) => item.id === versionId) : artifact.currentVersion);
@@ -135,7 +165,7 @@ let DashboardService = class DashboardService {
135
165
  throw new common_1.BadRequestException('DASHBOARD_IDENTITY_MISMATCH');
136
166
  return { ...summary(artifact, 'versionNumber' in version ? version : undefined),
137
167
  ...(draft ? { draftId: draft.id, draftRevision: draft.revision, baseVersionId: draft.baseVersionId,
138
- isDraft: draft.revision !== draft.publishedRevision, title: draft.title, updatedAt: draft.updatedAt } : {}), manifest, dependencies };
168
+ isDraft: draft.revision !== draft.publishedRevision, title: draft.title, updatedAt: draft.updatedAt } : {}), manifest, dependencies, contentHash: version.sha256 };
139
169
  }
140
170
  async history(context, artifactId) {
141
171
  await this.authorizeProject(context);
@@ -156,6 +186,9 @@ let DashboardService = class DashboardService {
156
186
  }
157
187
  async publish(context, artifactId, expectedRevision, expectedCurrentVersionId) {
158
188
  await this.authorizeProject(context, 'manage');
189
+ return this.publishDraft(context, artifactId, expectedRevision, expectedCurrentVersionId);
190
+ }
191
+ async validatePublication(context, artifactId, expectedRevision, expectedCurrentVersionId) {
159
192
  const saved = await this.open(context, artifactId, undefined, expectedRevision);
160
193
  if (!saved.manifest.bindings.length)
161
194
  throw new common_1.BadRequestException('DASHBOARD_CONFIGURE_BEFORE_PUBLISHING');
@@ -167,6 +200,9 @@ let DashboardService = class DashboardService {
167
200
  }
168
201
  // Revalidate current source access and bindings before publishing the exact reviewed bytes.
169
202
  await this.execute(context, saved.manifest, {});
203
+ }
204
+ async publishDraft(context, artifactId, expectedRevision, expectedCurrentVersionId) {
205
+ await this.validatePublication(context, artifactId, expectedRevision, expectedCurrentVersionId);
170
206
  const version = await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).publish({ artifactId, expectedRevision, expectedCurrentVersionId });
171
207
  return { artifactId, versionId: version.id, versionNumber: version.versionNumber, title: version.title };
172
208
  }
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod/v3';
2
2
  import { type ArtifactDocumentDependencyAsset, type ArtifactDocumentDependencyLock } from '@xpert-ai/plugin-sdk/artifact-document';
3
3
  /** The plugin owns its catalog. The platform owns verification and isolated execution. */
4
- export declare const libraryRequestsSchema: z.ZodEffects<z.ZodArray<z.ZodEnum<["echarts", "three"]>, "many">, ("echarts" | "three")[], ("echarts" | "three")[]>;
4
+ export declare const libraryRequestsSchema: z.ZodEffects<z.ZodEffects<z.ZodArray<z.ZodEnum<["echarts", "three", "trend-workspace", "profit-atlas", "customer-brief"]>, "many">, ("echarts" | "three" | "trend-workspace" | "profit-atlas" | "customer-brief")[], ("echarts" | "three" | "trend-workspace" | "profit-atlas" | "customer-brief")[]>, ("echarts" | "three" | "trend-workspace" | "profit-atlas" | "customer-brief")[], ("echarts" | "three" | "trend-workspace" | "profit-atlas" | "customer-brief")[]>;
5
5
  export type LibraryRequests = z.output<typeof libraryRequestsSchema>;
6
6
  export declare function bundledLibraries(): Promise<ArtifactDocumentDependencyAsset[]>;
7
7
  export declare function libraryLocks(ids: LibraryRequests): Promise<ArtifactDocumentDependencyLock[]>;
@@ -9,8 +9,9 @@ const node_path_1 = require("node:path");
9
9
  const v3_1 = require("zod/v3");
10
10
  const artifact_document_1 = require("@xpert-ai/plugin-sdk/artifact-document");
11
11
  /** The plugin owns its catalog. The platform owns verification and isolated execution. */
12
- exports.libraryRequestsSchema = v3_1.z.array(v3_1.z.enum(['echarts', 'three'])).max(2)
13
- .refine(ids => new Set(ids).size === ids.length, 'Duplicate library');
12
+ exports.libraryRequestsSchema = v3_1.z.array(v3_1.z.enum(['echarts', 'three', 'trend-workspace', 'profit-atlas', 'customer-brief'])).max(3)
13
+ .refine(ids => new Set(ids).size === ids.length, 'Duplicate library')
14
+ .refine(ids => ids.filter(id => ['trend-workspace', 'profit-atlas', 'customer-brief'].includes(id)).length <= 1, 'Choose one dashboard visual theme');
14
15
  async function bundledLibraries() {
15
16
  return (0, artifact_document_1.verifyArtifactDocumentDependencies)(JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(__dirname, '../libraries/catalog.json'), 'utf8')));
16
17
  }
@@ -1,3 +1,3 @@
1
1
  import type { ArtifactDocumentNetworkPolicy } from '@xpert-ai/plugin-sdk/artifact-document';
2
- /** Trusted plugin policy, shared by the displayed document and browser preflight. */
2
+ /** Temporary functional acceptance policy, shared by preview and viewing. */
3
3
  export declare const DASHBOARD_NETWORK_POLICY: ArtifactDocumentNetworkPolicy;
@@ -1,9 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DASHBOARD_NETWORK_POLICY = void 0;
4
- /** Trusted plugin policy, shared by the displayed document and browser preflight. */
5
- exports.DASHBOARD_NETWORK_POLICY = {
6
- scripts: ['https://cdn.tailwindcss.com', 'https://cdn.jsdelivr.net', 'https://cdnjs.cloudflare.com', 'https://unpkg.com'],
7
- styles: ['https://cdn.jsdelivr.net', 'https://cdnjs.cloudflare.com', 'https://unpkg.com', 'https://fonts.googleapis.com'],
8
- fonts: ['https://cdn.jsdelivr.net', 'https://cdnjs.cloudflare.com', 'https://unpkg.com', 'https://fonts.gstatic.com']
9
- };
4
+ /** Temporary functional acceptance policy, shared by preview and viewing. */
5
+ exports.DASHBOARD_NETWORK_POLICY = { mode: 'unrestricted' };
@@ -0,0 +1,5 @@
1
+ /** Missing selection is a normal interaction outcome; no Artifact operation is attempted. */
2
+ export declare function dashboardSelectionRequired(config: unknown): {
3
+ status: "selection_required";
4
+ message: "Please select a dashboard to edit first." | "请先选择要编辑的看板" | "請先選擇要編輯的看板";
5
+ };
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dashboardSelectionRequired = dashboardSelectionRequired;
4
+ const plugin_sdk_1 = require("@xpert-ai/plugin-sdk");
5
+ const v3_1 = require("zod/v3");
6
+ const messages = {
7
+ en: 'Please select a dashboard to edit first.',
8
+ 'zh-Hans': '请先选择要编辑的看板',
9
+ 'zh-Hant': '請先選擇要編輯的看板'
10
+ };
11
+ const languageConfig = v3_1.z.object({ configurable: v3_1.z.object({ language: v3_1.z.string().optional() }).optional() });
12
+ /** Missing selection is a normal interaction outcome; no Artifact operation is attempted. */
13
+ function dashboardSelectionRequired(config) {
14
+ const parsed = languageConfig.safeParse(config);
15
+ const language = (parsed.success ? parsed.data.configurable?.language : undefined) ?? plugin_sdk_1.RequestContext.getLanguageCode();
16
+ const locale = language.toLowerCase().replaceAll('_', '-');
17
+ const key = ['zh-hant', 'zh-tw', 'zh-hk', 'zh-mo'].includes(locale) ? 'zh-Hant' : locale === 'zh' || locale.startsWith('zh-') ? 'zh-Hans' : 'en';
18
+ return { status: 'selection_required', message: messages[key] };
19
+ }
@@ -0,0 +1,22 @@
1
+ import { z } from 'zod/v3';
2
+ import type { DashboardManifest } from './dashboard.schema';
3
+ export declare const removalsSchema: z.ZodOptional<z.ZodObject<{
4
+ bindingIds: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
5
+ controlIds: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
6
+ viewIds: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
7
+ htmlElementIds: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
8
+ }, "strict", z.ZodTypeAny, {
9
+ bindingIds: string[];
10
+ controlIds: string[];
11
+ viewIds: string[];
12
+ htmlElementIds: string[];
13
+ }, {
14
+ bindingIds?: string[] | undefined;
15
+ controlIds?: string[] | undefined;
16
+ viewIds?: string[] | undefined;
17
+ htmlElementIds?: string[] | undefined;
18
+ }>>;
19
+ export declare function upsertComponents<T>(existing: T[], changes: T[] | undefined, id: (item: T) => string, removed?: string[]): T[];
20
+ export declare function htmlElementIds(html: string): Set<string>;
21
+ /** IDs are an explicit preservation contract, not a semantic comparison of arbitrary HTML. */
22
+ export declare function assertHtmlPreserved(previous: DashboardManifest['presentation'], next: DashboardManifest['presentation'], removed?: string[]): void;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.removalsSchema = void 0;
4
+ exports.upsertComponents = upsertComponents;
5
+ exports.htmlElementIds = htmlElementIds;
6
+ exports.assertHtmlPreserved = assertHtmlPreserved;
7
+ const parse5_1 = require("parse5");
8
+ const v3_1 = require("zod/v3");
9
+ exports.removalsSchema = v3_1.z.object({
10
+ bindingIds: v3_1.z.array(v3_1.z.string()).max(8).default([]), controlIds: v3_1.z.array(v3_1.z.string()).max(24).default([]),
11
+ viewIds: v3_1.z.array(v3_1.z.string()).max(40).default([]), htmlElementIds: v3_1.z.array(v3_1.z.string()).max(500).default([])
12
+ }).strict().optional().describe('Only explicitly requested removals. Arrays of changed components otherwise upsert by stable ID and preserve omitted components.');
13
+ function upsertComponents(existing, changes, id, removed = []) {
14
+ if (new Set(changes?.map(id)).size !== (changes?.length ?? 0))
15
+ throw new Error('DASHBOARD_DUPLICATE_COMPONENT');
16
+ if (removed.some(key => !existing.some(item => id(item) === key) || changes?.some(item => id(item) === key)))
17
+ throw new Error('DASHBOARD_REMOVAL_INVALID: copy existing IDs and do not also update a removed component');
18
+ const next = new Map(existing.filter(item => !removed.includes(id(item))).map(item => [id(item), item]));
19
+ for (const item of changes ?? [])
20
+ next.set(id(item), item);
21
+ return [...next.values()];
22
+ }
23
+ function htmlElementIds(html) {
24
+ const ids = new Set();
25
+ const visit = (node) => {
26
+ if ('attrs' in node)
27
+ for (const attr of node.attrs)
28
+ if (attr.name === 'id' && attr.value)
29
+ ids.add(attr.value);
30
+ if ('childNodes' in node)
31
+ node.childNodes.forEach(visit);
32
+ if ('content' in node)
33
+ visit(node.content);
34
+ };
35
+ visit((0, parse5_1.parse)(html));
36
+ return ids;
37
+ }
38
+ /** IDs are an explicit preservation contract, not a semantic comparison of arbitrary HTML. */
39
+ function assertHtmlPreserved(previous, next, removed = []) {
40
+ if (!previous || !next || previous.html === next.html)
41
+ return;
42
+ const before = htmlElementIds(previous.html), after = htmlElementIds(next.html);
43
+ const missing = [...before].filter(id => !after.has(id) && !removed.includes(id));
44
+ if (missing.length)
45
+ throw new Error(`DASHBOARD_COMPONENT_REMOVAL_REQUIRED: preserve existing HTML IDs or explicitly declare user-requested removals: ${JSON.stringify(missing)}. Your editing file is retained.`);
46
+ }
@@ -0,0 +1,167 @@
1
+ import { z } from 'zod/v3';
2
+ import type { IAgentMiddlewareContext } from '@xpert-ai/plugin-sdk';
3
+ export declare const dashboardSelectionSchema: z.ZodObject<{
4
+ schemaVersion: z.ZodLiteral<1>;
5
+ artifactId: z.ZodString;
6
+ versionId: z.ZodOptional<z.ZodString>;
7
+ draftRevision: z.ZodOptional<z.ZodNumber>;
8
+ }, "strict", z.ZodTypeAny, {
9
+ artifactId: string;
10
+ schemaVersion: 1;
11
+ versionId?: string | undefined;
12
+ draftRevision?: number | undefined;
13
+ }, {
14
+ artifactId: string;
15
+ schemaVersion: 1;
16
+ versionId?: string | undefined;
17
+ draftRevision?: number | undefined;
18
+ }>;
19
+ export declare const dashboardContextSchema: z.ZodObject<{
20
+ live_dashboard: z.ZodOptional<z.ZodNullable<z.ZodUnion<[z.ZodObject<{
21
+ schemaVersion: z.ZodLiteral<1>;
22
+ artifactId: z.ZodString;
23
+ versionId: z.ZodOptional<z.ZodString>;
24
+ draftRevision: z.ZodOptional<z.ZodNumber>;
25
+ }, "strict", z.ZodTypeAny, {
26
+ artifactId: string;
27
+ schemaVersion: 1;
28
+ versionId?: string | undefined;
29
+ draftRevision?: number | undefined;
30
+ }, {
31
+ artifactId: string;
32
+ schemaVersion: 1;
33
+ versionId?: string | undefined;
34
+ draftRevision?: number | undefined;
35
+ }>, z.ZodObject<{
36
+ schemaVersion: z.ZodLiteral<1>;
37
+ mode: z.ZodLiteral<"new">;
38
+ }, "strict", z.ZodTypeAny, {
39
+ mode: "new";
40
+ schemaVersion: 1;
41
+ }, {
42
+ mode: "new";
43
+ schemaVersion: 1;
44
+ }>]>>>;
45
+ }, "strip", z.ZodTypeAny, {
46
+ live_dashboard?: {
47
+ artifactId: string;
48
+ schemaVersion: 1;
49
+ versionId?: string | undefined;
50
+ draftRevision?: number | undefined;
51
+ } | {
52
+ mode: "new";
53
+ schemaVersion: 1;
54
+ } | null | undefined;
55
+ }, {
56
+ live_dashboard?: {
57
+ artifactId: string;
58
+ schemaVersion: 1;
59
+ versionId?: string | undefined;
60
+ draftRevision?: number | undefined;
61
+ } | {
62
+ mode: "new";
63
+ schemaVersion: 1;
64
+ } | null | undefined;
65
+ }>;
66
+ export type DashboardSelection = z.infer<typeof dashboardSelectionSchema>;
67
+ export declare const dashboardStateSchema: z.ZodObject<{
68
+ dashboardEditing: z.ZodOptional<z.ZodNullable<z.ZodObject<{
69
+ scope: z.ZodString;
70
+ target: z.ZodNullable<z.ZodObject<{
71
+ schemaVersion: z.ZodLiteral<1>;
72
+ artifactId: z.ZodString;
73
+ versionId: z.ZodOptional<z.ZodString>;
74
+ draftRevision: z.ZodOptional<z.ZodNumber>;
75
+ }, "strict", z.ZodTypeAny, {
76
+ artifactId: string;
77
+ schemaVersion: 1;
78
+ versionId?: string | undefined;
79
+ draftRevision?: number | undefined;
80
+ }, {
81
+ artifactId: string;
82
+ schemaVersion: 1;
83
+ versionId?: string | undefined;
84
+ draftRevision?: number | undefined;
85
+ }>>;
86
+ }, "strip", z.ZodTypeAny, {
87
+ target: {
88
+ artifactId: string;
89
+ schemaVersion: 1;
90
+ versionId?: string | undefined;
91
+ draftRevision?: number | undefined;
92
+ } | null;
93
+ scope: string;
94
+ }, {
95
+ target: {
96
+ artifactId: string;
97
+ schemaVersion: 1;
98
+ versionId?: string | undefined;
99
+ draftRevision?: number | undefined;
100
+ } | null;
101
+ scope: string;
102
+ }>>>;
103
+ }, "strip", z.ZodTypeAny, {
104
+ dashboardEditing?: {
105
+ target: {
106
+ artifactId: string;
107
+ schemaVersion: 1;
108
+ versionId?: string | undefined;
109
+ draftRevision?: number | undefined;
110
+ } | null;
111
+ scope: string;
112
+ } | null | undefined;
113
+ }, {
114
+ dashboardEditing?: {
115
+ target: {
116
+ artifactId: string;
117
+ schemaVersion: 1;
118
+ versionId?: string | undefined;
119
+ draftRevision?: number | undefined;
120
+ } | null;
121
+ scope: string;
122
+ } | null | undefined;
123
+ }>;
124
+ /** Checkpoints belong to an actor and conversation. Copied state must never choose another user's target. */
125
+ export declare function editingSession(context: IAgentMiddlewareContext): {
126
+ current: () => {
127
+ artifactId: string;
128
+ schemaVersion: 1;
129
+ versionId?: string | undefined;
130
+ draftRevision?: number | undefined;
131
+ } | null;
132
+ resolve(config: unknown): {
133
+ artifactId: string;
134
+ schemaVersion: 1;
135
+ versionId?: string | undefined;
136
+ draftRevision?: number | undefined;
137
+ } | null;
138
+ restore(state: unknown, requestContext: unknown): {
139
+ dashboardEditing: {
140
+ scope: string;
141
+ target: {
142
+ artifactId: string;
143
+ schemaVersion: 1;
144
+ versionId?: string | undefined;
145
+ draftRevision?: number | undefined;
146
+ } | null;
147
+ };
148
+ };
149
+ select(target: DashboardSelection | null): {
150
+ dashboardEditing: {
151
+ scope: string;
152
+ target: {
153
+ artifactId: string;
154
+ schemaVersion: 1;
155
+ versionId?: string | undefined;
156
+ draftRevision?: number | undefined;
157
+ } | null;
158
+ };
159
+ };
160
+ };
161
+ export declare function selectionFromContext(value: unknown): DashboardSelection | null | undefined;
162
+ export declare function selectionFromConfig(value: unknown): {
163
+ artifactId: string;
164
+ schemaVersion: 1;
165
+ versionId?: string | undefined;
166
+ draftRevision?: number | undefined;
167
+ } | null | undefined;