@xpert-ai/plugin-live-dashboard 0.3.0 → 0.4.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 (153) hide show
  1. package/.xpertai-plugin/plugin.json +10 -2
  2. package/README.md +70 -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 +14 -1
  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 +2 -0
  16. package/dist/lib/assistant-template.js +7 -0
  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 +197 -46
  21. package/dist/lib/dashboard-view.schema.js +8 -1
  22. package/dist/lib/dashboard.service.d.ts +39 -2
  23. package/dist/lib/dashboard.service.js +52 -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 +48 -20
  39. package/dist/lib/monitor.d.ts +70 -0
  40. package/dist/lib/monitor.js +101 -0
  41. package/dist/lib/plugin.js +5 -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 +49 -0
  45. package/dist/lib/projects.schema.js +7 -0
  46. package/dist/lib/projects.service.d.ts +23 -0
  47. package/dist/lib/projects.service.js +65 -0
  48. package/dist/lib/prompt-workflows.d.ts +3 -2
  49. package/dist/lib/prompt-workflows.js +17 -3
  50. package/dist/lib/provider-options.controller.d.ts +10 -0
  51. package/dist/lib/provider-options.controller.js +49 -0
  52. package/dist/lib/resource-card.js +1 -1
  53. package/dist/lib/runtime-context.d.ts +3 -0
  54. package/dist/lib/runtime-context.js +13 -0
  55. package/dist/lib/save-evidence.d.ts +1 -1
  56. package/dist/lib/templates.d.ts +2 -0
  57. package/dist/lib/templates.js +55 -0
  58. package/dist/lib/view.provider.d.ts +97 -7
  59. package/dist/lib/view.provider.js +39 -10
  60. package/dist/libraries/TREND-WORKSPACE-ICONS-LICENSE.txt +43 -0
  61. package/dist/libraries/catalog.json +1 -1
  62. package/dist/remote/app.css +1 -1
  63. package/dist/remote/app.js +33 -33
  64. package/dist/remote/build-manifest.json +118 -32
  65. package/dist/sandbox-actions/action.json +1 -1
  66. package/dist/sandbox-actions/bundle/host.js +2 -2
  67. package/dist/sandbox-actions/bundle/resource-cache.mjs +2 -1
  68. package/dist/sandbox-actions/bundle/validate.mjs +1 -1
  69. package/docs/docs.json +7 -0
  70. package/docs/frontend-libraries.mdx +12 -4
  71. package/docs/html-mode.md +3 -3
  72. package/docs/quick-start-acceptance.mdx +50 -0
  73. package/docs/quick-start.mdx +64 -0
  74. package/docs/superstore.mdx +38 -0
  75. package/docs/team-sharing.mdx +6 -2
  76. package/docs/trend-workspace-acceptance.mdx +43 -0
  77. package/docs/trend-workspace.mdx +30 -0
  78. package/docs/verification.mdx +4 -2
  79. package/docs/visual-styles-acceptance.mdx +40 -0
  80. package/docs/visual-styles.mdx +46 -0
  81. package/package.json +25 -18
  82. package/scripts/import-prompts.mjs +108 -0
  83. package/skills/live-dashboard/SKILL.md +27 -3
  84. package/skills/live-dashboard/examples/README.md +25 -0
  85. package/skills/live-dashboard/examples/catalog.json +79 -0
  86. package/skills/live-dashboard/examples/production-quality/README.md +25 -0
  87. package/skills/live-dashboard/examples/production-quality/dashboard.html +129 -0
  88. package/skills/live-dashboard/examples/production-quality/definition.json +67 -0
  89. package/skills/live-dashboard/examples/production-quality/demo.csv +7 -0
  90. package/skills/live-dashboard/examples/production-quality/demo.json +44 -0
  91. package/skills/live-dashboard/examples/production-quality/expected.json +26 -0
  92. package/skills/live-dashboard/examples/production-quality/preview.png +0 -0
  93. package/skills/live-dashboard/examples/production-quality/query.json +53 -0
  94. package/skills/live-dashboard/examples/regional-sales/README.md +25 -0
  95. package/skills/live-dashboard/examples/regional-sales/dashboard-create.json +35 -0
  96. package/skills/live-dashboard/examples/regional-sales/definition.json +35 -0
  97. package/skills/live-dashboard/examples/regional-sales/demo.csv +5 -0
  98. package/skills/live-dashboard/examples/regional-sales/demo.json +18 -0
  99. package/skills/live-dashboard/examples/regional-sales/expected.json +19 -0
  100. package/skills/live-dashboard/examples/regional-sales/preview.png +0 -0
  101. package/skills/live-dashboard/examples/regional-sales/query.json +45 -0
  102. package/skills/live-dashboard/examples/sales-overview/README.md +25 -0
  103. package/skills/live-dashboard/examples/sales-overview/dashboard.html +129 -0
  104. package/skills/live-dashboard/examples/sales-overview/definition.json +57 -0
  105. package/skills/live-dashboard/examples/sales-overview/demo.csv +7 -0
  106. package/skills/live-dashboard/examples/sales-overview/demo.json +38 -0
  107. package/skills/live-dashboard/examples/sales-overview/expected.json +22 -0
  108. package/skills/live-dashboard/examples/sales-overview/preview.png +0 -0
  109. package/skills/live-dashboard/examples/sales-overview/query.json +52 -0
  110. package/skills/live-dashboard/examples/superstore/README.md +64 -0
  111. package/skills/live-dashboard/examples/superstore/THIRD_PARTY_LICENSE.txt +207 -0
  112. package/skills/live-dashboard/examples/superstore/build_data.py +155 -0
  113. package/skills/live-dashboard/examples/superstore/build_pages.py +129 -0
  114. package/skills/live-dashboard/examples/superstore/checks.json +104 -0
  115. package/skills/live-dashboard/examples/superstore/customers-mobile-dark.png +0 -0
  116. package/skills/live-dashboard/examples/superstore/customers-select.png +0 -0
  117. package/skills/live-dashboard/examples/superstore/customers.html +316 -0
  118. package/skills/live-dashboard/examples/superstore/customers.png +0 -0
  119. package/skills/live-dashboard/examples/superstore/customers.queries.json +512 -0
  120. package/skills/live-dashboard/examples/superstore/expected.json +4069 -0
  121. package/skills/live-dashboard/examples/superstore/import.json +200 -0
  122. package/skills/live-dashboard/examples/superstore/orders.csv +10195 -0
  123. package/skills/live-dashboard/examples/superstore/overview-mobile-dark.png +0 -0
  124. package/skills/live-dashboard/examples/superstore/overview-select.png +0 -0
  125. package/skills/live-dashboard/examples/superstore/overview.html +310 -0
  126. package/skills/live-dashboard/examples/superstore/overview.png +0 -0
  127. package/skills/live-dashboard/examples/superstore/overview.queries.json +636 -0
  128. package/skills/live-dashboard/examples/superstore/page.js +109 -0
  129. package/skills/live-dashboard/examples/superstore/profitability-mobile-dark.png +0 -0
  130. package/skills/live-dashboard/examples/superstore/profitability-select.png +0 -0
  131. package/skills/live-dashboard/examples/superstore/profitability.html +307 -0
  132. package/skills/live-dashboard/examples/superstore/profitability.png +0 -0
  133. package/skills/live-dashboard/examples/superstore/profitability.queries.json +334 -0
  134. package/skills/live-dashboard/examples/superstore/provenance.json +24 -0
  135. package/skills/live-dashboard/examples/superstore/semantic-model.json +220 -0
  136. package/skills/live-dashboard/examples/superstore/test_data.py +62 -0
  137. package/skills/live-dashboard/references/html-runtime.md +1 -1
  138. package/skills/live-dashboard/themes/README.md +50 -0
  139. package/skills/live-dashboard/themes/customer-brief/README.md +47 -0
  140. package/skills/live-dashboard/themes/customer-brief/charts.js +17 -0
  141. package/skills/live-dashboard/themes/customer-brief/layout.html +22 -0
  142. package/skills/live-dashboard/themes/customer-brief/theme.css +108 -0
  143. package/skills/live-dashboard/themes/profit-atlas/README.md +43 -0
  144. package/skills/live-dashboard/themes/profit-atlas/charts.js +30 -0
  145. package/skills/live-dashboard/themes/profit-atlas/layout.html +19 -0
  146. package/skills/live-dashboard/themes/profit-atlas/theme.css +75 -0
  147. package/skills/live-dashboard/themes/trend-workspace/ICON_LICENSE.txt +43 -0
  148. package/skills/live-dashboard/themes/trend-workspace/README.md +144 -0
  149. package/skills/live-dashboard/themes/trend-workspace/charts.js +60 -0
  150. package/skills/live-dashboard/themes/trend-workspace/icons.js +11 -0
  151. package/skills/live-dashboard/themes/trend-workspace/layout.html +45 -0
  152. package/skills/live-dashboard/themes/trend-workspace/select.js +127 -0
  153. package/skills/live-dashboard/themes/trend-workspace/theme.css +137 -0
@@ -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;
@@ -19,6 +20,37 @@ export declare class DashboardService {
19
20
  versionNumber: number | undefined;
20
21
  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
+ canDelete: boolean;
32
+ artifactId: string;
33
+ versionId: string | null | undefined;
34
+ versionNumber: number | undefined;
35
+ updatedAt: string | Date | 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
+ canDelete: boolean;
46
+ artifactId: string;
47
+ versionId: string | null | undefined;
48
+ versionNumber: number | undefined;
49
+ updatedAt: string | Date | undefined;
50
+ }[];
51
+ page: number;
52
+ hasMore: boolean;
53
+ }>;
22
54
  trash(context: XpertResolvedViewHostContext, artifactId: string, expectedRevision: number): Promise<{
23
55
  artifactId: string;
24
56
  }>;
@@ -30,7 +62,7 @@ export declare class DashboardService {
30
62
  status: "draft";
31
63
  title: string;
32
64
  }>;
33
- open(context: XpertResolvedViewHostContext, artifactId: string, versionId?: string, draftRevision?: number): Promise<{
65
+ open(context: XpertResolvedViewHostContext, artifactId: string, versionId?: string, draftRevision?: number, mode?: DashboardMode): Promise<{
34
66
  manifest: {
35
67
  schema: "xpert.live-dashboard";
36
68
  id: string;
@@ -64,6 +96,7 @@ export declare class DashboardService {
64
96
  dependencies?: import("@xpert-ai/plugin-sdk/artifact-document").ArtifactDocumentDependencyLock[] | undefined;
65
97
  } | undefined;
66
98
  };
99
+ contentHash: string;
67
100
  draftId?: string | undefined;
68
101
  draftRevision?: number | undefined;
69
102
  baseVersionId?: string | null | undefined;
@@ -75,7 +108,7 @@ export declare class DashboardService {
75
108
  versionNumber: number | undefined;
76
109
  }>;
77
110
  /** 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<{
111
+ openDocument(context: XpertResolvedViewHostContext, artifactId: string, versionId?: string, draftRevision?: number, mode?: DashboardMode): Promise<{
79
112
  manifest: {
80
113
  schema: "xpert.live-dashboard";
81
114
  id: string;
@@ -110,6 +143,7 @@ export declare class DashboardService {
110
143
  } | undefined;
111
144
  };
112
145
  dependencies: import("@xpert-ai/plugin-sdk/artifact-document").ArtifactDocumentDependencyAsset[];
146
+ contentHash: string;
113
147
  draftId?: string | undefined;
114
148
  draftRevision?: number | undefined;
115
149
  baseVersionId?: string | null | undefined;
@@ -142,6 +176,8 @@ export declare class DashboardService {
142
176
  versionNumber: number;
143
177
  title: string | null | undefined;
144
178
  }>;
179
+ validatePublication(context: XpertResolvedViewHostContext, artifactId: string, expectedRevision: number, expectedCurrentVersionId: string | null): Promise<void>;
180
+ private publishDraft;
145
181
  query(context: XpertResolvedViewHostContext, artifactId: string, versionId: string | undefined, controls: Record<string, AnalyticsValue>, draftRevision?: number): Promise<{
146
182
  payload?: {
147
183
  ok: boolean;
@@ -237,6 +273,7 @@ export declare class DashboardService {
237
273
  dependencies?: import("@xpert-ai/plugin-sdk/artifact-document").ArtifactDocumentDependencyLock[] | undefined;
238
274
  } | undefined;
239
275
  };
276
+ contentHash: string;
240
277
  draftId?: string | undefined;
241
278
  draftRevision?: number | undefined;
242
279
  baseVersionId?: string | null | undefined;
@@ -32,23 +32,49 @@ 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 } : {}) };
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 };
52
78
  }
53
79
  async trash(context, artifactId, expectedRevision) {
54
80
  await this.authorizeProject(context, 'manage');
@@ -88,38 +114,30 @@ let DashboardService = class DashboardService {
88
114
  for (const binding of parsed.bindings)
89
115
  await this.probeControls(context, binding, Object.fromEntries(parsed.controls.filter((control) => control.defaultValue !== undefined).map((control) => [control.controlId, control.defaultValue ?? null])));
90
116
  const htmlValidation = await this.htmlValidation.validate(context, parsed, preview, dependencies);
91
- const artifact = existing ?? await artifacts.createArtifact({ source, kind: 'file', title: parsed.title,
117
+ const artifact = existing ?? await artifacts.createArtifact({ source, type: { pluginName: exports.PLUGIN_NAME, resourceType: RESOURCE_TYPE }, kind: 'file', title: parsed.title,
92
118
  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
- }));
119
+ const reference = await this.runtime.require(plugin_sdk_1.ArtifactContentRuntimeCapability).write({ artifactId: artifact.id, buffer });
105
120
  const result = await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).save({ artifactId: artifact.id, expectedRevision,
106
121
  expectedCurrentVersionId: expectedVersionId, mimeType: MIME_TYPE, title: parsed.title, fileName: 'dashboard.json', sha256: digest,
107
122
  ...(htmlValidation ? { metadata: { htmlValidation } } : {}), workspaceFileRef: reference });
108
123
  return { artifactId: artifact.id, draftId: result.id, draftRevision: result.revision, baseVersionId: result.baseVersionId, status: 'draft', title: parsed.title };
109
124
  }
110
- async open(context, artifactId, versionId, draftRevision) {
111
- const { dependencies, ...saved } = await this.openDocument(context, artifactId, versionId, draftRevision);
125
+ async open(context, artifactId, versionId, draftRevision, mode = 'draft') {
126
+ const { dependencies, ...saved } = await this.openDocument(context, artifactId, versionId, draftRevision, mode);
112
127
  return saved;
113
128
  }
114
129
  /** 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);
130
+ async openDocument(context, artifactId, versionId, draftRevision, mode = 'draft') {
131
+ const access = await this.access(context);
132
+ const readDraft = draftRevision !== undefined || !versionId && mode === 'draft';
133
+ if (readDraft && !access.canReadDraft)
134
+ throw new common_1.ForbiddenException('DASHBOARD_DRAFT_ACCESS_DENIED');
117
135
  const artifacts = this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability);
118
136
  const artifact = await artifacts.getArtifact(artifactId);
119
137
  assertOwned(context, artifact);
120
138
  if (artifact.status !== 'active')
121
139
  throw new common_1.ForbiddenException('DASHBOARD_ARCHIVED');
122
- const draft = !versionId || draftRevision !== undefined ? await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).get(artifactId) : null;
140
+ const draft = readDraft ? await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).get(artifactId) : null;
123
141
  if (draftRevision !== undefined && draft?.revision !== draftRevision)
124
142
  throw new common_1.ConflictException('DASHBOARD_DRAFT_CONFLICT');
125
143
  const version = draft ?? (versionId ? (await artifacts.listArtifactVersions({ artifactId, status: 'active' })).find((item) => item.id === versionId) : artifact.currentVersion);
@@ -135,7 +153,7 @@ let DashboardService = class DashboardService {
135
153
  throw new common_1.BadRequestException('DASHBOARD_IDENTITY_MISMATCH');
136
154
  return { ...summary(artifact, 'versionNumber' in version ? version : undefined),
137
155
  ...(draft ? { draftId: draft.id, draftRevision: draft.revision, baseVersionId: draft.baseVersionId,
138
- isDraft: draft.revision !== draft.publishedRevision, title: draft.title, updatedAt: draft.updatedAt } : {}), manifest, dependencies };
156
+ isDraft: draft.revision !== draft.publishedRevision, title: draft.title, updatedAt: draft.updatedAt } : {}), manifest, dependencies, contentHash: version.sha256 };
139
157
  }
140
158
  async history(context, artifactId) {
141
159
  await this.authorizeProject(context);
@@ -156,6 +174,9 @@ let DashboardService = class DashboardService {
156
174
  }
157
175
  async publish(context, artifactId, expectedRevision, expectedCurrentVersionId) {
158
176
  await this.authorizeProject(context, 'manage');
177
+ return this.publishDraft(context, artifactId, expectedRevision, expectedCurrentVersionId);
178
+ }
179
+ async validatePublication(context, artifactId, expectedRevision, expectedCurrentVersionId) {
159
180
  const saved = await this.open(context, artifactId, undefined, expectedRevision);
160
181
  if (!saved.manifest.bindings.length)
161
182
  throw new common_1.BadRequestException('DASHBOARD_CONFIGURE_BEFORE_PUBLISHING');
@@ -167,6 +188,9 @@ let DashboardService = class DashboardService {
167
188
  }
168
189
  // Revalidate current source access and bindings before publishing the exact reviewed bytes.
169
190
  await this.execute(context, saved.manifest, {});
191
+ }
192
+ async publishDraft(context, artifactId, expectedRevision, expectedCurrentVersionId) {
193
+ await this.validatePublication(context, artifactId, expectedRevision, expectedCurrentVersionId);
170
194
  const version = await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).publish({ artifactId, expectedRevision, expectedCurrentVersionId });
171
195
  return { artifactId, versionId: version.id, versionNumber: version.versionNumber, title: version.title };
172
196
  }
@@ -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;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dashboardStateSchema = exports.dashboardContextSchema = exports.dashboardSelectionSchema = void 0;
4
+ exports.editingSession = editingSession;
5
+ exports.selectionFromContext = selectionFromContext;
6
+ exports.selectionFromConfig = selectionFromConfig;
7
+ const v3_1 = require("zod/v3");
8
+ exports.dashboardSelectionSchema = v3_1.z.object({ schemaVersion: v3_1.z.literal(1), artifactId: v3_1.z.string().uuid(),
9
+ versionId: v3_1.z.string().uuid().optional(), draftRevision: v3_1.z.number().int().positive().optional() }).strict();
10
+ const newSelection = v3_1.z.object({ schemaVersion: v3_1.z.literal(1), mode: v3_1.z.literal('new') }).strict();
11
+ exports.dashboardContextSchema = v3_1.z.object({ live_dashboard: v3_1.z.union([exports.dashboardSelectionSchema, newSelection]).nullable().optional() });
12
+ exports.dashboardStateSchema = v3_1.z.object({ dashboardEditing: v3_1.z.object({
13
+ scope: v3_1.z.string(), target: exports.dashboardSelectionSchema.nullable()
14
+ }).nullable().optional() });
15
+ /** Checkpoints belong to an actor and conversation. Copied state must never choose another user's target. */
16
+ function editingSession(context) {
17
+ const scope = JSON.stringify([context.tenantId, context.organizationId, context.userId, context.xpertId,
18
+ context.projectId, context.conversationId, context.threadId, context.agentKey]);
19
+ let active = null;
20
+ let initialized = false;
21
+ return {
22
+ current: () => active,
23
+ resolve(config) { return initialized ? active : selectionFromConfig(config) ?? active; },
24
+ restore(state, requestContext) {
25
+ const persisted = exports.dashboardStateSchema.safeParse(state);
26
+ const selected = selectionFromContext(requestContext);
27
+ active = selected !== undefined ? selected : persisted.success && persisted.data.dashboardEditing?.scope === scope
28
+ ? persisted.data.dashboardEditing.target : null;
29
+ initialized = true;
30
+ return { dashboardEditing: { scope, target: active } };
31
+ },
32
+ select(target) { active = target; initialized = true; return { dashboardEditing: { scope, target } }; }
33
+ };
34
+ }
35
+ function selectionFromContext(value) {
36
+ if (value === undefined || value === null)
37
+ return undefined;
38
+ const parsed = exports.dashboardContextSchema.safeParse(value);
39
+ if (!parsed.success)
40
+ throw new Error('DASHBOARD_SELECTION_INVALID: select the dashboard again before editing');
41
+ if (parsed.data.live_dashboard === undefined)
42
+ return undefined;
43
+ const selected = parsed.data.live_dashboard;
44
+ return selected && 'artifactId' in selected ? selected : null;
45
+ }
46
+ function selectionFromConfig(value) {
47
+ const config = v3_1.z.object({ context: v3_1.z.unknown(),
48
+ configurable: v3_1.z.object({ context: v3_1.z.unknown() }).optional() }).safeParse(value);
49
+ if (!config.success)
50
+ return undefined;
51
+ const primary = selectionFromContext(config.data.context);
52
+ return primary !== undefined ? primary : selectionFromContext(config.data.configurable?.context);
53
+ }