@xpert-ai/plugin-live-dashboard 0.4.1 → 0.6.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 (79) hide show
  1. package/.xpertai-plugin/plugin.json +1 -1
  2. package/README.md +8 -2
  3. package/dist/index.js +3 -1
  4. package/dist/lib/agent.schema.d.ts +177 -23
  5. package/dist/lib/agent.schema.js +11 -9
  6. package/dist/lib/archive.service.d.ts +169 -0
  7. package/dist/lib/archive.service.js +147 -0
  8. package/dist/lib/bounded-map.d.ts +2 -0
  9. package/dist/lib/bounded-map.js +25 -0
  10. package/dist/lib/capacity.d.ts +14 -0
  11. package/dist/lib/capacity.js +16 -0
  12. package/dist/lib/content.provider.d.ts +1 -1
  13. package/dist/lib/dashboard-archive.d.ts +70 -0
  14. package/dist/lib/dashboard-archive.js +118 -0
  15. package/dist/lib/dashboard-queries.d.ts +1 -1
  16. package/dist/lib/dashboard-queries.js +11 -6
  17. package/dist/lib/dashboard-receipt.js +1 -1
  18. package/dist/lib/dashboard-sources.d.ts +69 -0
  19. package/dist/lib/dashboard-sources.js +38 -0
  20. package/dist/lib/dashboard-view.schema.d.ts +422 -88
  21. package/dist/lib/dashboard.schema.d.ts +379 -58
  22. package/dist/lib/dashboard.schema.js +20 -2
  23. package/dist/lib/dashboard.service.d.ts +80 -15
  24. package/dist/lib/dashboard.service.js +65 -9
  25. package/dist/lib/definition-tools.d.ts +4 -4
  26. package/dist/lib/document-host.js +1 -1
  27. package/dist/lib/document-package.d.ts +19 -4
  28. package/dist/lib/editing-patch.d.ts +3 -0
  29. package/dist/lib/editing-patch.js +4 -2
  30. package/dist/lib/editing-session.d.ts +4 -4
  31. package/dist/lib/editing.d.ts +204 -20
  32. package/dist/lib/editing.js +10 -3
  33. package/dist/lib/filter-evidence.d.ts +28 -0
  34. package/dist/lib/filter-evidence.js +3 -2
  35. package/dist/lib/html-guidance.d.ts +1 -0
  36. package/dist/lib/html-guidance.js +2 -1
  37. package/dist/lib/html-validation-feedback.d.ts +7 -0
  38. package/dist/lib/html-validation-feedback.js +19 -0
  39. package/dist/lib/html-validation.js +3 -2
  40. package/dist/lib/inspection-host.d.ts +7 -0
  41. package/dist/lib/inspection-host.js +48 -0
  42. package/dist/lib/inspection-runtime.d.ts +2 -0
  43. package/dist/lib/inspection-runtime.js +77 -0
  44. package/dist/lib/json-input.d.ts +4 -0
  45. package/dist/lib/json-input.js +21 -0
  46. package/dist/lib/middleware.d.ts +3 -1
  47. package/dist/lib/middleware.js +92 -13
  48. package/dist/lib/plugin.js +3 -1
  49. package/dist/lib/presentation.provider.d.ts +24 -9
  50. package/dist/lib/presentation.provider.js +2 -1
  51. package/dist/lib/preview-retry.d.ts +4 -3
  52. package/dist/lib/preview-retry.js +13 -7
  53. package/dist/lib/prompt-workflows.js +1 -1
  54. package/dist/lib/query-session.d.ts +9 -0
  55. package/dist/lib/query-session.js +64 -0
  56. package/dist/lib/save-requests.d.ts +118 -0
  57. package/dist/lib/save-requests.js +199 -0
  58. package/dist/lib/source-executions.d.ts +437 -0
  59. package/dist/lib/source-executions.js +49 -0
  60. package/dist/lib/source-validation.d.ts +2 -0
  61. package/dist/lib/source-validation.js +21 -0
  62. package/dist/lib/view.provider.d.ts +213 -8
  63. package/dist/lib/view.provider.js +47 -5
  64. package/dist/remote/app.css +1 -1
  65. package/dist/remote/app.js +185 -63
  66. package/dist/remote/build-manifest.json +52 -33
  67. package/dist/sandbox-actions/action.json +1 -1
  68. package/dist/sandbox-actions/bundle/host.js +63 -14
  69. package/dist/sandbox-actions/bundle/validate.mjs +2 -2
  70. package/dist/validation/host-entry.d.ts +5 -1
  71. package/dist/validation/host-entry.js +21 -4
  72. package/docs/docs.json +5 -1
  73. package/docs/large-dashboards-acceptance.mdx +74 -0
  74. package/docs/large-dashboards.mdx +80 -0
  75. package/docs/sources-inspect-portability-acceptance.mdx +67 -0
  76. package/docs/sources-inspect-portability.mdx +115 -0
  77. package/package.json +8 -7
  78. package/skills/live-dashboard/SKILL.md +5 -4
  79. package/skills/live-dashboard/references/source-inspection.md +17 -0
@@ -1,13 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.queryBindingSchema = exports.querySchema = exports.saveSchema = exports.controlsSchema = exports.dashboardChangesSchema = exports.dashboardSchema = void 0;
4
+ const capacity_1 = require("./capacity");
4
5
  const v3_1 = require("zod/v3");
5
6
  const analytics_1 = require("@xpert-ai/plugin-sdk/analytics");
6
7
  const artifact_document_1 = require("@xpert-ai/plugin-sdk/artifact-document");
8
+ const dashboard_sources_1 = require("./dashboard-sources");
7
9
  const id = v3_1.z.string().regex(/^[A-Za-z][A-Za-z0-9_.-]{0,79}$/);
8
10
  exports.dashboardSchema = v3_1.z.object({
9
11
  schema: v3_1.z.literal('xpert.live-dashboard'), id: v3_1.z.string().uuid(), title: v3_1.z.string().min(1).max(160),
10
- bindings: v3_1.z.array(analytics_1.analyticsBindingSchema).max(8),
12
+ bindings: v3_1.z.array(analytics_1.analyticsBindingSchema).max(capacity_1.MAX_DASHBOARD_BINDINGS),
13
+ sourceMetadata: dashboard_sources_1.sourceMetadataSchema.optional(),
14
+ components: dashboard_sources_1.dashboardComponentsSchema.optional(),
11
15
  presentation: v3_1.z.object({ type: v3_1.z.literal('html'), html: artifact_document_1.artifactDocumentHtml, dependencies: artifact_document_1.artifactDocumentDependencyLocks.optional() }).strict().optional(),
12
16
  controls: v3_1.z.array(v3_1.z.object({ controlId: id, type: v3_1.z.enum(['string', 'number', 'boolean', 'date']),
13
17
  defaultValue: v3_1.z.union([v3_1.z.string(), v3_1.z.number(), v3_1.z.boolean(), v3_1.z.null()]).optional(),
@@ -25,6 +29,20 @@ exports.dashboardSchema = v3_1.z.object({
25
29
  ctx.addIssue({ code: 'custom', message: 'An unconfigured draft must contain no views or controls' });
26
30
  unique(manifest.bindings.map((binding) => binding.id), 'binding');
27
31
  unique(manifest.bindings.map((binding) => binding.output.datasetId), 'dataset');
32
+ unique((manifest.sourceMetadata ?? []).map(item => item.bindingId), 'source metadata');
33
+ unique((manifest.components ?? []).map(item => item.id), 'component');
34
+ unique((manifest.components ?? []).map(item => item.elementId), 'component element');
35
+ for (const metadata of manifest.sourceMetadata ?? [])
36
+ if (!manifest.bindings.some(binding => binding.id === metadata.bindingId))
37
+ ctx.addIssue({ code: 'custom', message: 'Source metadata references an undeclared Binding' });
38
+ for (const component of manifest.components ?? []) {
39
+ unique(component.sources.map(source => source.bindingId), 'component source');
40
+ for (const source of component.sources) {
41
+ const binding = manifest.bindings.find(item => item.id === source.bindingId);
42
+ if (!binding || source.fieldIds?.some(field => !binding.output.fields.some(item => item.id === field)))
43
+ ctx.addIssue({ code: 'custom', message: 'Component references an undeclared Binding or field' });
44
+ }
45
+ }
28
46
  unique(manifest.controls.map((control) => control.controlId), 'control');
29
47
  unique([...manifest.charts, ...manifest.tables].map((view) => view.id), 'view');
30
48
  if (manifest.bindings.length && !manifest.presentation && !manifest.charts.length && !manifest.tables.length)
@@ -68,7 +86,7 @@ exports.dashboardSchema = v3_1.z.object({
68
86
  });
69
87
  /** Updates never choose identity or source scope; those come from the authorized saved version. */
70
88
  exports.dashboardChangesSchema = exports.dashboardSchema.innerType().pick({ title: true, bindings: true,
71
- controls: true, charts: true, tables: true, presentation: true }).partial().strict();
89
+ controls: true, charts: true, tables: true, presentation: true, sourceMetadata: true, components: true }).partial().strict();
72
90
  exports.controlsSchema = v3_1.z.record(v3_1.z.union([v3_1.z.string().max(4000), v3_1.z.number().finite(), v3_1.z.boolean(), v3_1.z.null()]));
73
91
  exports.saveSchema = v3_1.z.object({ manifest: exports.dashboardSchema, expectedVersionId: v3_1.z.string().uuid().nullable(), expectedRevision: v3_1.z.number().int().nonnegative().default(0) }).strict();
74
92
  exports.querySchema = v3_1.z.object({ artifactId: v3_1.z.string().uuid(), versionId: v3_1.z.string().uuid().optional(), draftRevision: v3_1.z.number().int().positive().optional(), controls: exports.controlsSchema.default({}) }).strict();
@@ -1,13 +1,23 @@
1
+ import { type DashboardConfig } from './capacity';
1
2
  import type { AnalyticsValue, AnalyticsDiscoveryRequest, AnalyticsQueryRequest, XpertResolvedViewHostContext } from '@xpert-ai/contracts';
2
3
  import { type RuntimeCapabilityRegistry } from '@xpert-ai/plugin-sdk';
3
4
  import { type DashboardManifest, type DashboardChanges } from './dashboard.schema';
5
+ import { type ArtifactDocumentDependencyAsset } from '@xpert-ai/plugin-sdk/artifact-document';
4
6
  import { HtmlDashboardValidation } from './html-validation';
5
7
  import type { DashboardAccess, DashboardMode } from './project';
6
8
  export declare const PLUGIN_NAME = "@xpert-ai/plugin-live-dashboard";
9
+ export type SaveProgress = (stage: string, completed: number, total: number) => Promise<void>;
10
+ export type SaveOptions = {
11
+ requestId: string;
12
+ progress: SaveProgress;
13
+ };
7
14
  export declare class DashboardService {
8
15
  private readonly runtime;
9
16
  private readonly htmlValidation;
10
- constructor(runtime: RuntimeCapabilityRegistry, htmlValidation: HtmlDashboardValidation);
17
+ private readonly config?;
18
+ constructor(runtime: RuntimeCapabilityRegistry, htmlValidation: HtmlDashboardValidation, config?: DashboardConfig | undefined);
19
+ get maxBindings(): number;
20
+ assertCapacity(count: number, previousCount?: number): void;
11
21
  sources(context: XpertResolvedViewHostContext): Promise<import("@xpert-ai/contracts").AnalyticsSourceSummary[]>;
12
22
  discover(context: XpertResolvedViewHostContext, input: AnalyticsDiscoveryRequest): Promise<import("@xpert-ai/contracts").AnalyticsDiscoveryResult>;
13
23
  preview(context: XpertResolvedViewHostContext, input: AnalyticsQueryRequest): Promise<import("@xpert-ai/contracts").AnalyticalDataset>;
@@ -63,7 +73,17 @@ export declare class DashboardService {
63
73
  trash(context: XpertResolvedViewHostContext, artifactId: string, expectedRevision: number): Promise<{
64
74
  artifactId: string;
65
75
  }>;
66
- save(context: XpertResolvedViewHostContext, manifest: DashboardManifest, expectedVersionId: string | null, expectedRevision?: number): Promise<{
76
+ previousBindingCount(context: XpertResolvedViewHostContext, manifest: DashboardManifest): Promise<number>;
77
+ /** Read-only reconciliation of a completed CAS after a worker/status-store interruption. */
78
+ savedRequestReceipt(context: XpertResolvedViewHostContext, manifestId: string, requestId: string, expectedRevision: number): Promise<{
79
+ artifactId: string;
80
+ draftId: string;
81
+ draftRevision: number;
82
+ baseVersionId: string | null;
83
+ status: "draft";
84
+ title: string;
85
+ } | null>;
86
+ save(context: XpertResolvedViewHostContext, manifest: DashboardManifest, expectedVersionId: string | null, expectedRevision?: number, importedDependencies?: ArtifactDocumentDependencyAsset[], task?: SaveOptions): Promise<{
67
87
  artifactId: string;
68
88
  draftId: string;
69
89
  draftRevision: number;
@@ -73,9 +93,9 @@ export declare class DashboardService {
73
93
  }>;
74
94
  open(context: XpertResolvedViewHostContext, artifactId: string, versionId?: string, draftRevision?: number, mode?: DashboardMode): Promise<{
75
95
  manifest: {
76
- schema: "xpert.live-dashboard";
77
- id: string;
78
96
  title: string;
97
+ id: string;
98
+ schema: "xpert.live-dashboard";
79
99
  bindings: import("@xpert-ai/contracts").AnalyticsBinding[];
80
100
  controls: {
81
101
  type: "string" | "number" | "boolean" | "date";
@@ -86,19 +106,34 @@ export declare class DashboardService {
86
106
  }[];
87
107
  charts: {
88
108
  type: "bar" | "line" | "area" | "pie" | "scatter";
89
- id: string;
90
109
  title: string;
110
+ id: string;
91
111
  datasetId: string;
92
112
  x: string;
93
113
  y: string;
94
114
  color?: string | undefined;
95
115
  }[];
96
116
  tables: {
97
- id: string;
98
117
  title: string;
118
+ id: string;
99
119
  datasetId: string;
100
120
  fields: string[];
101
121
  }[];
122
+ sourceMetadata?: {
123
+ bindingId: string;
124
+ title: string;
125
+ description?: string | undefined;
126
+ }[] | undefined;
127
+ components?: {
128
+ title: string;
129
+ id: string;
130
+ elementId: string;
131
+ kind: "filter" | "metric" | "chart" | "table" | "text";
132
+ sources: {
133
+ bindingId: string;
134
+ fieldIds?: string[] | undefined;
135
+ }[];
136
+ }[] | undefined;
102
137
  presentation?: {
103
138
  type: "html";
104
139
  html: string;
@@ -119,9 +154,9 @@ export declare class DashboardService {
119
154
  /** Trusted View transport only. Agent read tools receive locks, never megabytes of library code. */
120
155
  openDocument(context: XpertResolvedViewHostContext, artifactId: string, versionId?: string, draftRevision?: number, mode?: DashboardMode): Promise<{
121
156
  manifest: {
122
- schema: "xpert.live-dashboard";
123
- id: string;
124
157
  title: string;
158
+ id: string;
159
+ schema: "xpert.live-dashboard";
125
160
  bindings: import("@xpert-ai/contracts").AnalyticsBinding[];
126
161
  controls: {
127
162
  type: "string" | "number" | "boolean" | "date";
@@ -132,26 +167,41 @@ export declare class DashboardService {
132
167
  }[];
133
168
  charts: {
134
169
  type: "bar" | "line" | "area" | "pie" | "scatter";
135
- id: string;
136
170
  title: string;
171
+ id: string;
137
172
  datasetId: string;
138
173
  x: string;
139
174
  y: string;
140
175
  color?: string | undefined;
141
176
  }[];
142
177
  tables: {
143
- id: string;
144
178
  title: string;
179
+ id: string;
145
180
  datasetId: string;
146
181
  fields: string[];
147
182
  }[];
183
+ sourceMetadata?: {
184
+ bindingId: string;
185
+ title: string;
186
+ description?: string | undefined;
187
+ }[] | undefined;
188
+ components?: {
189
+ title: string;
190
+ id: string;
191
+ elementId: string;
192
+ kind: "filter" | "metric" | "chart" | "table" | "text";
193
+ sources: {
194
+ bindingId: string;
195
+ fieldIds?: string[] | undefined;
196
+ }[];
197
+ }[] | undefined;
148
198
  presentation?: {
149
199
  type: "html";
150
200
  html: string;
151
201
  dependencies?: import("@xpert-ai/plugin-sdk/artifact-document").ArtifactDocumentDependencyLock[] | undefined;
152
202
  } | undefined;
153
203
  };
154
- dependencies: import("@xpert-ai/plugin-sdk/artifact-document").ArtifactDocumentDependencyAsset[];
204
+ dependencies: ArtifactDocumentDependencyAsset[];
155
205
  contentHash: string;
156
206
  draftId?: string | undefined;
157
207
  draftRevision?: number | undefined;
@@ -250,9 +300,9 @@ export declare class DashboardService {
250
300
  reason: "unavailable";
251
301
  }[];
252
302
  manifest: {
253
- schema: "xpert.live-dashboard";
254
- id: string;
255
303
  title: string;
304
+ id: string;
305
+ schema: "xpert.live-dashboard";
256
306
  bindings: import("@xpert-ai/contracts").AnalyticsBinding[];
257
307
  controls: {
258
308
  type: "string" | "number" | "boolean" | "date";
@@ -263,19 +313,34 @@ export declare class DashboardService {
263
313
  }[];
264
314
  charts: {
265
315
  type: "bar" | "line" | "area" | "pie" | "scatter";
266
- id: string;
267
316
  title: string;
317
+ id: string;
268
318
  datasetId: string;
269
319
  x: string;
270
320
  y: string;
271
321
  color?: string | undefined;
272
322
  }[];
273
323
  tables: {
274
- id: string;
275
324
  title: string;
325
+ id: string;
276
326
  datasetId: string;
277
327
  fields: string[];
278
328
  }[];
329
+ sourceMetadata?: {
330
+ bindingId: string;
331
+ title: string;
332
+ description?: string | undefined;
333
+ }[] | undefined;
334
+ components?: {
335
+ title: string;
336
+ id: string;
337
+ elementId: string;
338
+ kind: "filter" | "metric" | "chart" | "table" | "text";
339
+ sources: {
340
+ bindingId: string;
341
+ fieldIds?: string[] | undefined;
342
+ }[];
343
+ }[] | undefined;
279
344
  presentation?: {
280
345
  type: "html";
281
346
  html: string;
@@ -14,10 +14,14 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.DashboardService = exports.PLUGIN_NAME = void 0;
16
16
  exports.projectId = projectId;
17
+ const capacity_1 = require("./capacity");
18
+ const bounded_map_1 = require("./bounded-map");
17
19
  const common_1 = require("@nestjs/common");
18
20
  const node_crypto_1 = require("node:crypto");
19
21
  const plugin_sdk_1 = require("@xpert-ai/plugin-sdk");
20
22
  const dashboard_schema_1 = require("./dashboard.schema");
23
+ const source_validation_1 = require("./source-validation");
24
+ const artifact_document_1 = require("@xpert-ai/plugin-sdk/artifact-document");
21
25
  const renderer_1 = require("./renderer");
22
26
  const dashboard_queries_1 = require("./dashboard-queries");
23
27
  const html_validation_1 = require("./html-validation");
@@ -28,9 +32,19 @@ exports.PLUGIN_NAME = '@xpert-ai/plugin-live-dashboard';
28
32
  const RESOURCE_TYPE = 'live-dashboard';
29
33
  const MIME_TYPE = 'application/json';
30
34
  let DashboardService = class DashboardService {
31
- constructor(runtime, htmlValidation) {
35
+ constructor(runtime, htmlValidation, config) {
32
36
  this.runtime = runtime;
33
37
  this.htmlValidation = htmlValidation;
38
+ this.config = config;
39
+ }
40
+ get maxBindings() { return this.config?.maxBindings ?? capacity_1.DEFAULT_DASHBOARD_BINDINGS; }
41
+ assertCapacity(count, previousCount = 0) {
42
+ try {
43
+ (0, capacity_1.assertBindingCapacity)(count, previousCount, this.maxBindings);
44
+ }
45
+ catch (error) {
46
+ throw new common_1.BadRequestException(error instanceof Error ? error.message : 'DASHBOARD_BINDING_CAPACITY');
47
+ }
34
48
  }
35
49
  async sources(context) { await this.authorizeProject(context); return this.runtime.require(plugin_sdk_1.AnalyticsRuntimeCapability).list(actor(context)); }
36
50
  async discover(context, input) { await this.authorizeProject(context); return this.runtime.require(plugin_sdk_1.AnalyticsRuntimeCapability).discover(actor(context), input); }
@@ -98,10 +112,35 @@ let DashboardService = class DashboardService {
98
112
  await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).setRecycled({ artifactId, expectedRevision, recycled: true });
99
113
  return { artifactId };
100
114
  }
101
- async save(context, manifest, expectedVersionId, expectedRevision = 0) {
115
+ async previousBindingCount(context, manifest) {
116
+ await this.authorizeProject(context, 'write');
117
+ const existing = await this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability).findArtifactBySource({ pluginName: exports.PLUGIN_NAME, resourceType: RESOURCE_TYPE, resourceId: `${hostPrefix(context)}${manifest.id}` });
118
+ if (!existing)
119
+ return 0;
120
+ assertOwned(context, existing);
121
+ const draft = (await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).list([existing.id]))[0];
122
+ return existing.currentVersionId || draft ? (await this.open(context, existing.id)).manifest.bindings.length : 0;
123
+ }
124
+ /** Read-only reconciliation of a completed CAS after a worker/status-store interruption. */
125
+ async savedRequestReceipt(context, manifestId, requestId, expectedRevision) {
126
+ await this.authorizeProject(context, 'write');
127
+ const artifact = await this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability).findArtifactBySource({ pluginName: exports.PLUGIN_NAME, resourceType: RESOURCE_TYPE, resourceId: `${hostPrefix(context)}${manifestId}` });
128
+ if (!artifact)
129
+ return null;
130
+ assertOwned(context, artifact);
131
+ if (artifact.status !== 'active')
132
+ throw new common_1.ForbiddenException('DASHBOARD_ARCHIVED');
133
+ const draft = await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).get(artifact.id);
134
+ if (draft?.metadata?.saveRequestId !== requestId || draft.revision !== expectedRevision + 1)
135
+ return null;
136
+ return { artifactId: artifact.id, draftId: draft.id, draftRevision: draft.revision, baseVersionId: draft.baseVersionId, status: 'draft', title: draft.title ?? artifact.title ?? '' };
137
+ }
138
+ async save(context, manifest, expectedVersionId, expectedRevision = 0, importedDependencies, task) {
139
+ await task?.progress('authorization', 0, manifest.bindings.length);
102
140
  await this.authorizeProject(context, 'write');
103
141
  const scopedActor = actor(context);
104
142
  const parsed = dashboard_schema_1.dashboardSchema.parse(manifest);
143
+ (0, source_validation_1.assertComponentElements)(parsed);
105
144
  const artifacts = this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability);
106
145
  const source = { pluginName: exports.PLUGIN_NAME, resourceType: RESOURCE_TYPE, resourceId: `${hostPrefix(context)}${parsed.id}` };
107
146
  const existing = await artifacts.findArtifactBySource(source);
@@ -112,26 +151,40 @@ let DashboardService = class DashboardService {
112
151
  }
113
152
  // A failed first upload can leave an Artifact with no saved content; revision 0 initializes it.
114
153
  const previous = existing && (existing.currentVersionId || expectedRevision > 0) ? await this.openDocument(context, existing.id) : null;
115
- const dependencies = await (0, document_libraries_1.resolveLibraryAssets)(parsed.presentation?.dependencies ?? [], previous?.dependencies);
154
+ this.assertCapacity(parsed.bindings.length, previous?.manifest.bindings.length ?? 0);
155
+ if (importedDependencies && existing)
156
+ throw new common_1.BadRequestException('DASHBOARD_IMPORT_NEW_ID_REQUIRED');
157
+ const dependencies = importedDependencies ? (0, artifact_document_1.verifyArtifactDocumentDependencies)(importedDependencies, parsed.presentation?.dependencies ?? [])
158
+ : await (0, document_libraries_1.resolveLibraryAssets)(parsed.presentation?.dependencies ?? [], previous?.dependencies);
116
159
  const buffer = (0, document_package_1.encodeDashboardPackage)(parsed, dependencies);
117
160
  if (buffer.byteLength > document_package_1.MAX_DASHBOARD_PACKAGE_BYTES)
118
161
  throw new common_1.BadRequestException('DASHBOARD_MANIFEST_TOO_LARGE');
119
162
  const digest = (0, node_crypto_1.createHash)('sha256').update(buffer).digest('hex');
163
+ if (existing && task) {
164
+ const draft = await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).get(existing.id);
165
+ if (draft?.metadata?.saveRequestId === task.requestId && draft.sha256 === digest && draft.revision === expectedRevision + 1)
166
+ return { artifactId: existing.id, draftId: draft.id, draftRevision: draft.revision, baseVersionId: draft.baseVersionId, status: 'draft', title: parsed.title };
167
+ }
120
168
  if ((existing?.currentVersionId ?? null) !== expectedVersionId)
121
169
  throw new common_1.ConflictException('DASHBOARD_VERSION_CONFLICT');
122
170
  // Recheck current data access even for an idempotent retry; the platform enforces the final CAS under a row lock.
123
- const preview = await this.execute(context, parsed, Object.fromEntries(parsed.controls.filter((control) => control.defaultValue !== undefined).map((control) => [control.controlId, control.defaultValue ?? null])));
171
+ const preview = await (0, dashboard_queries_1.queryDashboard)(this.runtime, actor(context), parsed, Object.fromEntries(parsed.controls.filter((control) => control.defaultValue !== undefined).map((control) => [control.controlId, control.defaultValue ?? null])), task ? completed => task.progress('queries', completed, parsed.bindings.length) : undefined);
124
172
  if (!parsed.presentation)
125
173
  (0, renderer_1.toDataAnalyticsPayload)(parsed, preview);
126
- for (const binding of parsed.bindings)
127
- await this.probeControls(context, binding, Object.fromEntries(parsed.controls.filter((control) => control.defaultValue !== undefined).map((control) => [control.controlId, control.defaultValue ?? null])));
174
+ let completedFilters = 0;
175
+ await (0, bounded_map_1.boundedMap)(parsed.bindings, async (binding) => {
176
+ await this.probeControls(context, binding, Object.fromEntries(parsed.controls.filter((control) => control.defaultValue !== undefined).map((control) => [control.controlId, control.defaultValue ?? null])), task ? () => task.progress('filters', completedFilters, parsed.bindings.length) : undefined);
177
+ await task?.progress('filters', ++completedFilters, parsed.bindings.length);
178
+ });
179
+ await task?.progress('html', 0, 1);
128
180
  const htmlValidation = await this.htmlValidation.validate(context, parsed, preview, dependencies);
181
+ await task?.progress('save', 0, 1);
129
182
  const artifact = existing ?? await artifacts.createArtifact({ source, type: { pluginName: exports.PLUGIN_NAME, resourceType: RESOURCE_TYPE }, kind: 'file', title: parsed.title,
130
183
  scope: { ...scopedActor, ...(projectId(context) ? { projectId: projectId(context), userId: null } : { xpertId: context.hostId }) } });
131
184
  const reference = await this.runtime.require(plugin_sdk_1.ArtifactContentRuntimeCapability).write({ artifactId: artifact.id, buffer });
132
185
  const result = await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).save({ artifactId: artifact.id, expectedRevision,
133
186
  expectedCurrentVersionId: expectedVersionId, mimeType: MIME_TYPE, title: parsed.title, fileName: 'dashboard.json', sha256: digest,
134
- ...(htmlValidation ? { metadata: { htmlValidation } } : {}), workspaceFileRef: reference });
187
+ ...((htmlValidation || task) ? { metadata: { ...(htmlValidation ? { htmlValidation } : {}), ...(task ? { saveRequestId: task.requestId } : {}) } } : {}), workspaceFileRef: reference });
135
188
  return { artifactId: artifact.id, draftId: result.id, draftRevision: result.revision, baseVersionId: result.baseVersionId, status: 'draft', title: parsed.title };
136
189
  }
137
190
  async open(context, artifactId, versionId, draftRevision, mode = 'draft') {
@@ -236,7 +289,7 @@ let DashboardService = class DashboardService {
236
289
  execute(context, manifest, input) {
237
290
  return (0, dashboard_queries_1.queryDashboard)(this.runtime, actor(context), manifest, input);
238
291
  }
239
- async probeControls(context, binding, input) {
292
+ async probeControls(context, binding, input, checkpoint) {
240
293
  const controls = Object.fromEntries(binding.parameters.map((parameter) => [parameter.controlId,
241
294
  Object.hasOwn(input, parameter.controlId) ? input[parameter.controlId] : parameter.defaultValue ?? null]));
242
295
  // A null default can bypass a native predicate entirely. Verify a real selection too,
@@ -247,6 +300,7 @@ let DashboardService = class DashboardService {
247
300
  const samples = [...(alternative !== undefined ? [alternative] : []),
248
301
  ...(current !== null && parameter.allowedValues?.includes(null) ? [null] : [])];
249
302
  for (const value of samples) {
303
+ await checkpoint?.();
250
304
  // Analytics already redacts provider failures. Preserve its actionable validation
251
305
  // errors: a duplicate grain is not a malformed filter and needs a different repair.
252
306
  await this.runtime.require(plugin_sdk_1.AnalyticsRuntimeCapability).query(actor(context), { binding, controls: { ...controls, [parameter.controlId]: value } });
@@ -258,7 +312,9 @@ exports.DashboardService = DashboardService;
258
312
  exports.DashboardService = DashboardService = __decorate([
259
313
  (0, common_1.Injectable)(),
260
314
  __param(0, (0, common_1.Inject)(plugin_sdk_1.XPERT_RUNTIME_CAPABILITIES_TOKEN)),
261
- __metadata("design:paramtypes", [Object, html_validation_1.HtmlDashboardValidation])
315
+ __param(2, (0, common_1.Optional)()),
316
+ __param(2, (0, common_1.Inject)(capacity_1.DASHBOARD_CONFIG)),
317
+ __metadata("design:paramtypes", [Object, html_validation_1.HtmlDashboardValidation, Object])
262
318
  ], DashboardService);
263
319
  function actor(context) {
264
320
  if (!context.tenantId || !context.organizationId || !context.userId || !['agent', 'project'].includes(context.hostType))
@@ -31,26 +31,26 @@ export declare function createDefinitionTools(context: IAgentMiddlewareContext,
31
31
  definitionJson: z.ZodString;
32
32
  }, "strict", z.ZodTypeAny, {
33
33
  name: string;
34
+ requestId: string;
34
35
  providerId: string;
35
36
  connectionKey: string;
36
- requestId: string;
37
37
  definitionJson: string;
38
38
  }, {
39
39
  name: string;
40
+ requestId: string;
40
41
  providerId: string;
41
42
  connectionKey: string;
42
- requestId: string;
43
43
  definitionJson: string;
44
44
  }>, {
45
45
  name: string;
46
+ requestId: string;
46
47
  providerId: string;
47
48
  connectionKey: string;
48
- requestId: string;
49
49
  definitionJson: string;
50
50
  }, {
51
51
  name: string;
52
+ requestId: string;
52
53
  providerId: string;
53
54
  connectionKey: string;
54
- requestId: string;
55
55
  definitionJson: string;
56
56
  }, string>)[];
@@ -6,7 +6,7 @@ const artifact_document_1 = require("@xpert-ai/plugin-sdk/artifact-document");
6
6
  const document_network_1 = require("./document-network");
7
7
  /** The HTML never supplies or overrides its network permissions. */
8
8
  function mountDashboardDocument(frame, options) {
9
- return (0, artifact_document_1.mountArtifactDocument)(frame, { ...options, networkPolicy: document_network_1.DASHBOARD_NETWORK_POLICY });
9
+ return (0, artifact_document_1.mountArtifactDocument)(frame, { ...options, dispatchesPerMinute: 60, networkPolicy: document_network_1.DASHBOARD_NETWORK_POLICY });
10
10
  }
11
11
  function isDashboardResourceAllowed(resourceType, url) {
12
12
  return (0, artifact_document_1.isArtifactDocumentResourceAllowed)(document_network_1.DASHBOARD_NETWORK_POLICY, resourceType, url);
@@ -5,9 +5,9 @@ export declare const MAX_DASHBOARD_PACKAGE_BYTES = 9000000;
5
5
  export declare function encodeDashboardPackage(manifest: DashboardManifest, dependencies: ArtifactDocumentDependencyAsset[]): Buffer<ArrayBuffer>;
6
6
  export declare function decodeDashboardPackage(bytes: Buffer): {
7
7
  manifest: {
8
- schema: "xpert.live-dashboard";
9
- id: string;
10
8
  title: string;
9
+ id: string;
10
+ schema: "xpert.live-dashboard";
11
11
  bindings: import("@xpert-ai/contracts").AnalyticsBinding[];
12
12
  controls: {
13
13
  type: "string" | "number" | "boolean" | "date";
@@ -18,19 +18,34 @@ export declare function decodeDashboardPackage(bytes: Buffer): {
18
18
  }[];
19
19
  charts: {
20
20
  type: "bar" | "line" | "area" | "pie" | "scatter";
21
- id: string;
22
21
  title: string;
22
+ id: string;
23
23
  datasetId: string;
24
24
  x: string;
25
25
  y: string;
26
26
  color?: string | undefined;
27
27
  }[];
28
28
  tables: {
29
- id: string;
30
29
  title: string;
30
+ id: string;
31
31
  datasetId: string;
32
32
  fields: string[];
33
33
  }[];
34
+ sourceMetadata?: {
35
+ bindingId: string;
36
+ title: string;
37
+ description?: string | undefined;
38
+ }[] | undefined;
39
+ components?: {
40
+ title: string;
41
+ id: string;
42
+ elementId: string;
43
+ kind: "filter" | "metric" | "chart" | "table" | "text";
44
+ sources: {
45
+ bindingId: string;
46
+ fieldIds?: string[] | undefined;
47
+ }[];
48
+ }[] | undefined;
34
49
  presentation?: {
35
50
  type: "html";
36
51
  html: string;
@@ -5,16 +5,19 @@ export declare const removalsSchema: z.ZodOptional<z.ZodObject<{
5
5
  controlIds: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
6
6
  viewIds: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
7
7
  htmlElementIds: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
8
+ componentIds: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
8
9
  }, "strict", z.ZodTypeAny, {
9
10
  bindingIds: string[];
10
11
  controlIds: string[];
11
12
  viewIds: string[];
12
13
  htmlElementIds: string[];
14
+ componentIds: string[];
13
15
  }, {
14
16
  bindingIds?: string[] | undefined;
15
17
  controlIds?: string[] | undefined;
16
18
  viewIds?: string[] | undefined;
17
19
  htmlElementIds?: string[] | undefined;
20
+ componentIds?: string[] | undefined;
18
21
  }>>;
19
22
  export declare function upsertComponents<T>(existing: T[], changes: T[] | undefined, id: (item: T) => string, removed?: string[]): T[];
20
23
  export declare function htmlElementIds(html: string): Set<string>;
@@ -4,11 +4,13 @@ exports.removalsSchema = void 0;
4
4
  exports.upsertComponents = upsertComponents;
5
5
  exports.htmlElementIds = htmlElementIds;
6
6
  exports.assertHtmlPreserved = assertHtmlPreserved;
7
+ const capacity_1 = require("./capacity");
7
8
  const parse5_1 = require("parse5");
8
9
  const v3_1 = require("zod/v3");
9
10
  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([])
11
+ bindingIds: v3_1.z.array(v3_1.z.string()).max(capacity_1.MAX_DASHBOARD_BINDINGS).default([]), controlIds: v3_1.z.array(v3_1.z.string()).max(24).default([]),
12
+ viewIds: v3_1.z.array(v3_1.z.string()).max(40).default([]), htmlElementIds: v3_1.z.array(v3_1.z.string()).max(500).default([]),
13
+ componentIds: v3_1.z.array(v3_1.z.string()).max(160).default([])
12
14
  }).strict().optional().describe('Only explicitly requested removals. Arrays of changed components otherwise upsert by stable ID and preserve omitted components.');
13
15
  function upsertComponents(existing, changes, id, removed = []) {
14
16
  if (new Set(changes?.map(id)).size !== (changes?.length ?? 0))
@@ -84,41 +84,41 @@ export declare const dashboardStateSchema: z.ZodObject<{
84
84
  draftRevision?: number | undefined;
85
85
  }>>;
86
86
  }, "strip", z.ZodTypeAny, {
87
+ scope: string;
87
88
  target: {
88
89
  artifactId: string;
89
90
  schemaVersion: 1;
90
91
  versionId?: string | undefined;
91
92
  draftRevision?: number | undefined;
92
93
  } | null;
93
- scope: string;
94
94
  }, {
95
+ scope: string;
95
96
  target: {
96
97
  artifactId: string;
97
98
  schemaVersion: 1;
98
99
  versionId?: string | undefined;
99
100
  draftRevision?: number | undefined;
100
101
  } | null;
101
- scope: string;
102
102
  }>>>;
103
103
  }, "strip", z.ZodTypeAny, {
104
104
  dashboardEditing?: {
105
+ scope: string;
105
106
  target: {
106
107
  artifactId: string;
107
108
  schemaVersion: 1;
108
109
  versionId?: string | undefined;
109
110
  draftRevision?: number | undefined;
110
111
  } | null;
111
- scope: string;
112
112
  } | null | undefined;
113
113
  }, {
114
114
  dashboardEditing?: {
115
+ scope: string;
115
116
  target: {
116
117
  artifactId: string;
117
118
  schemaVersion: 1;
118
119
  versionId?: string | undefined;
119
120
  draftRevision?: number | undefined;
120
121
  } | null;
121
- scope: string;
122
122
  } | null | undefined;
123
123
  }>;
124
124
  /** Checkpoints belong to an actor and conversation. Copied state must never choose another user's target. */