@xpert-ai/plugin-live-dashboard 0.1.4 → 0.3.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 (54) hide show
  1. package/.xpertai-plugin/plugin.json +1 -1
  2. package/README.md +77 -4
  3. package/dist/lib/content-capability.d.ts +22 -0
  4. package/dist/lib/content-capability.js +33 -0
  5. package/dist/lib/content.provider.d.ts +23 -0
  6. package/dist/lib/content.provider.js +163 -0
  7. package/dist/lib/dashboard-navigation.d.ts +24 -0
  8. package/dist/lib/dashboard-navigation.js +1 -1
  9. package/dist/lib/dashboard-queries.d.ts +11 -0
  10. package/dist/lib/dashboard-queries.js +20 -0
  11. package/dist/lib/dashboard-search.d.ts +5 -0
  12. package/dist/lib/dashboard-search.js +12 -0
  13. package/dist/lib/dashboard-view.schema.d.ts +27 -0
  14. package/dist/lib/dashboard-view.schema.js +1 -1
  15. package/dist/lib/dashboard.schema.js +4 -2
  16. package/dist/lib/dashboard.service.d.ts +13 -0
  17. package/dist/lib/dashboard.service.js +69 -16
  18. package/dist/lib/document-host.d.ts +4 -0
  19. package/dist/lib/document-host.js +13 -0
  20. package/dist/lib/document-network.d.ts +3 -0
  21. package/dist/lib/document-network.js +9 -0
  22. package/dist/lib/html-guidance.d.ts +1 -1
  23. package/dist/lib/html-guidance.js +1 -1
  24. package/dist/lib/middleware.js +49 -15
  25. package/dist/lib/plugin.js +3 -1
  26. package/dist/lib/presentation.provider.d.ts +5 -0
  27. package/dist/lib/presentation.provider.js +2 -2
  28. package/dist/lib/preview-retry.d.ts +7 -0
  29. package/dist/lib/preview-retry.js +52 -0
  30. package/dist/lib/prompt-workflows.d.ts +3 -0
  31. package/dist/lib/prompt-workflows.js +118 -0
  32. package/dist/lib/renderer.js +5 -0
  33. package/dist/lib/save-evidence.d.ts +1 -1
  34. package/dist/lib/sharing.service.js +2 -0
  35. package/dist/lib/view.provider.d.ts +2 -117
  36. package/dist/lib/view.provider.js +12 -4
  37. package/dist/remote/app.css +1 -1
  38. package/dist/remote/app.js +36 -36
  39. package/dist/remote/build-manifest.json +38 -28
  40. package/dist/sandbox-actions/action.json +1 -1
  41. package/dist/sandbox-actions/bundle/host.js +2 -2
  42. package/dist/sandbox-actions/bundle/resource-cache.mjs +39 -0
  43. package/dist/sandbox-actions/bundle/validate.mjs +106 -17
  44. package/dist/validation/host-entry.d.ts +3 -1
  45. package/dist/validation/host-entry.js +5 -2
  46. package/docs/drafts.mdx +1 -1
  47. package/docs/frontend-libraries.mdx +2 -2
  48. package/docs/html-mode.md +3 -3
  49. package/docs/team-sharing.mdx +1 -1
  50. package/docs/verification.mdx +3 -1
  51. package/package.json +6 -6
  52. package/skills/live-dashboard/SKILL.md +1 -1
  53. package/skills/live-dashboard/references/data-validation.md +4 -0
  54. package/skills/live-dashboard/references/html-runtime.md +1 -1
@@ -6,6 +6,7 @@ export declare const dashboardSummarySchema: z.ZodObject<{
6
6
  title: z.ZodOptional<z.ZodNullable<z.ZodString>>;
7
7
  updatedAt: z.ZodOptional<z.ZodString>;
8
8
  draftRevision: z.ZodOptional<z.ZodNumber>;
9
+ canDelete: z.ZodOptional<z.ZodBoolean>;
9
10
  isDraft: z.ZodOptional<z.ZodBoolean>;
10
11
  baseVersionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
11
12
  }, "strip", z.ZodTypeAny, {
@@ -17,6 +18,7 @@ export declare const dashboardSummarySchema: z.ZodObject<{
17
18
  baseVersionId?: string | null | undefined;
18
19
  isDraft?: boolean | undefined;
19
20
  updatedAt?: string | undefined;
21
+ canDelete?: boolean | undefined;
20
22
  }, {
21
23
  artifactId: string;
22
24
  title?: string | null | undefined;
@@ -26,6 +28,7 @@ export declare const dashboardSummarySchema: z.ZodObject<{
26
28
  baseVersionId?: string | null | undefined;
27
29
  isDraft?: boolean | undefined;
28
30
  updatedAt?: string | undefined;
31
+ canDelete?: boolean | undefined;
29
32
  }>;
30
33
  export declare const dashboardSavedSchema: z.ZodObject<{
31
34
  artifactId: z.ZodString;
@@ -34,6 +37,7 @@ export declare const dashboardSavedSchema: z.ZodObject<{
34
37
  title: z.ZodOptional<z.ZodNullable<z.ZodString>>;
35
38
  updatedAt: z.ZodOptional<z.ZodString>;
36
39
  draftRevision: z.ZodOptional<z.ZodNumber>;
40
+ canDelete: z.ZodOptional<z.ZodBoolean>;
37
41
  isDraft: z.ZodOptional<z.ZodBoolean>;
38
42
  baseVersionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
39
43
  } & {
@@ -365,6 +369,7 @@ export declare const dashboardSavedSchema: z.ZodObject<{
365
369
  baseVersionId?: string | null | undefined;
366
370
  isDraft?: boolean | undefined;
367
371
  updatedAt?: string | undefined;
372
+ canDelete?: boolean | undefined;
368
373
  }, {
369
374
  manifest: {
370
375
  schema: "xpert.live-dashboard";
@@ -421,6 +426,7 @@ export declare const dashboardSavedSchema: z.ZodObject<{
421
426
  baseVersionId?: string | null | undefined;
422
427
  isDraft?: boolean | undefined;
423
428
  updatedAt?: string | undefined;
429
+ canDelete?: boolean | undefined;
424
430
  }>;
425
431
  export declare const dashboardViewDataSchema: z.ZodObject<{
426
432
  meta: z.ZodObject<{
@@ -453,6 +459,7 @@ export declare const dashboardViewDataSchema: z.ZodObject<{
453
459
  title: z.ZodOptional<z.ZodNullable<z.ZodString>>;
454
460
  updatedAt: z.ZodOptional<z.ZodString>;
455
461
  draftRevision: z.ZodOptional<z.ZodNumber>;
462
+ canDelete: z.ZodOptional<z.ZodBoolean>;
456
463
  isDraft: z.ZodOptional<z.ZodBoolean>;
457
464
  baseVersionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
458
465
  }, "strip", z.ZodTypeAny, {
@@ -464,6 +471,7 @@ export declare const dashboardViewDataSchema: z.ZodObject<{
464
471
  baseVersionId?: string | null | undefined;
465
472
  isDraft?: boolean | undefined;
466
473
  updatedAt?: string | undefined;
474
+ canDelete?: boolean | undefined;
467
475
  }, {
468
476
  artifactId: string;
469
477
  title?: string | null | undefined;
@@ -473,6 +481,7 @@ export declare const dashboardViewDataSchema: z.ZodObject<{
473
481
  baseVersionId?: string | null | undefined;
474
482
  isDraft?: boolean | undefined;
475
483
  updatedAt?: string | undefined;
484
+ canDelete?: boolean | undefined;
476
485
  }>, "many">;
477
486
  sources: z.ZodArray<z.ZodObject<{
478
487
  source: z.ZodType<import("@xpert-ai/contracts").AnalyticsSourceRef, z.ZodTypeDef, import("@xpert-ai/contracts").AnalyticsSourceRef>;
@@ -491,6 +500,7 @@ export declare const dashboardViewDataSchema: z.ZodObject<{
491
500
  title: z.ZodOptional<z.ZodNullable<z.ZodString>>;
492
501
  updatedAt: z.ZodOptional<z.ZodString>;
493
502
  draftRevision: z.ZodOptional<z.ZodNumber>;
503
+ canDelete: z.ZodOptional<z.ZodBoolean>;
494
504
  isDraft: z.ZodOptional<z.ZodBoolean>;
495
505
  baseVersionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
496
506
  } & {
@@ -822,6 +832,7 @@ export declare const dashboardViewDataSchema: z.ZodObject<{
822
832
  baseVersionId?: string | null | undefined;
823
833
  isDraft?: boolean | undefined;
824
834
  updatedAt?: string | undefined;
835
+ canDelete?: boolean | undefined;
825
836
  }, {
826
837
  manifest: {
827
838
  schema: "xpert.live-dashboard";
@@ -878,6 +889,7 @@ export declare const dashboardViewDataSchema: z.ZodObject<{
878
889
  baseVersionId?: string | null | undefined;
879
890
  isDraft?: boolean | undefined;
880
891
  updatedAt?: string | undefined;
892
+ canDelete?: boolean | undefined;
881
893
  }>>;
882
894
  versions: z.ZodOptional<z.ZodArray<z.ZodObject<{
883
895
  artifactId: z.ZodString;
@@ -886,6 +898,7 @@ export declare const dashboardViewDataSchema: z.ZodObject<{
886
898
  title: z.ZodOptional<z.ZodNullable<z.ZodString>>;
887
899
  updatedAt: z.ZodOptional<z.ZodString>;
888
900
  draftRevision: z.ZodOptional<z.ZodNumber>;
901
+ canDelete: z.ZodOptional<z.ZodBoolean>;
889
902
  isDraft: z.ZodOptional<z.ZodBoolean>;
890
903
  baseVersionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
891
904
  }, "strip", z.ZodTypeAny, {
@@ -897,6 +910,7 @@ export declare const dashboardViewDataSchema: z.ZodObject<{
897
910
  baseVersionId?: string | null | undefined;
898
911
  isDraft?: boolean | undefined;
899
912
  updatedAt?: string | undefined;
913
+ canDelete?: boolean | undefined;
900
914
  }, {
901
915
  artifactId: string;
902
916
  title?: string | null | undefined;
@@ -906,6 +920,7 @@ export declare const dashboardViewDataSchema: z.ZodObject<{
906
920
  baseVersionId?: string | null | undefined;
907
921
  isDraft?: boolean | undefined;
908
922
  updatedAt?: string | undefined;
923
+ canDelete?: boolean | undefined;
909
924
  }>, "many">>;
910
925
  }, "strip", z.ZodTypeAny, {
911
926
  sources: {
@@ -921,6 +936,7 @@ export declare const dashboardViewDataSchema: z.ZodObject<{
921
936
  baseVersionId?: string | null | undefined;
922
937
  isDraft?: boolean | undefined;
923
938
  updatedAt?: string | undefined;
939
+ canDelete?: boolean | undefined;
924
940
  }[];
925
941
  saved?: {
926
942
  manifest: {
@@ -965,6 +981,7 @@ export declare const dashboardViewDataSchema: z.ZodObject<{
965
981
  baseVersionId?: string | null | undefined;
966
982
  isDraft?: boolean | undefined;
967
983
  updatedAt?: string | undefined;
984
+ canDelete?: boolean | undefined;
968
985
  } | undefined;
969
986
  navigationTarget?: {
970
987
  target: "workbench.view";
@@ -983,6 +1000,7 @@ export declare const dashboardViewDataSchema: z.ZodObject<{
983
1000
  baseVersionId?: string | null | undefined;
984
1001
  isDraft?: boolean | undefined;
985
1002
  updatedAt?: string | undefined;
1003
+ canDelete?: boolean | undefined;
986
1004
  }[] | undefined;
987
1005
  }, {
988
1006
  sources: {
@@ -998,6 +1016,7 @@ export declare const dashboardViewDataSchema: z.ZodObject<{
998
1016
  baseVersionId?: string | null | undefined;
999
1017
  isDraft?: boolean | undefined;
1000
1018
  updatedAt?: string | undefined;
1019
+ canDelete?: boolean | undefined;
1001
1020
  }[];
1002
1021
  saved?: {
1003
1022
  manifest: {
@@ -1055,6 +1074,7 @@ export declare const dashboardViewDataSchema: z.ZodObject<{
1055
1074
  baseVersionId?: string | null | undefined;
1056
1075
  isDraft?: boolean | undefined;
1057
1076
  updatedAt?: string | undefined;
1077
+ canDelete?: boolean | undefined;
1058
1078
  } | undefined;
1059
1079
  navigationTarget?: {
1060
1080
  target: "workbench.view";
@@ -1073,6 +1093,7 @@ export declare const dashboardViewDataSchema: z.ZodObject<{
1073
1093
  baseVersionId?: string | null | undefined;
1074
1094
  isDraft?: boolean | undefined;
1075
1095
  updatedAt?: string | undefined;
1096
+ canDelete?: boolean | undefined;
1076
1097
  }[] | undefined;
1077
1098
  }>;
1078
1099
  }, "strip", z.ZodTypeAny, {
@@ -1090,6 +1111,7 @@ export declare const dashboardViewDataSchema: z.ZodObject<{
1090
1111
  baseVersionId?: string | null | undefined;
1091
1112
  isDraft?: boolean | undefined;
1092
1113
  updatedAt?: string | undefined;
1114
+ canDelete?: boolean | undefined;
1093
1115
  }[];
1094
1116
  saved?: {
1095
1117
  manifest: {
@@ -1134,6 +1156,7 @@ export declare const dashboardViewDataSchema: z.ZodObject<{
1134
1156
  baseVersionId?: string | null | undefined;
1135
1157
  isDraft?: boolean | undefined;
1136
1158
  updatedAt?: string | undefined;
1159
+ canDelete?: boolean | undefined;
1137
1160
  } | undefined;
1138
1161
  navigationTarget?: {
1139
1162
  target: "workbench.view";
@@ -1152,6 +1175,7 @@ export declare const dashboardViewDataSchema: z.ZodObject<{
1152
1175
  baseVersionId?: string | null | undefined;
1153
1176
  isDraft?: boolean | undefined;
1154
1177
  updatedAt?: string | undefined;
1178
+ canDelete?: boolean | undefined;
1155
1179
  }[] | undefined;
1156
1180
  };
1157
1181
  }, {
@@ -1169,6 +1193,7 @@ export declare const dashboardViewDataSchema: z.ZodObject<{
1169
1193
  baseVersionId?: string | null | undefined;
1170
1194
  isDraft?: boolean | undefined;
1171
1195
  updatedAt?: string | undefined;
1196
+ canDelete?: boolean | undefined;
1172
1197
  }[];
1173
1198
  saved?: {
1174
1199
  manifest: {
@@ -1226,6 +1251,7 @@ export declare const dashboardViewDataSchema: z.ZodObject<{
1226
1251
  baseVersionId?: string | null | undefined;
1227
1252
  isDraft?: boolean | undefined;
1228
1253
  updatedAt?: string | undefined;
1254
+ canDelete?: boolean | undefined;
1229
1255
  } | undefined;
1230
1256
  navigationTarget?: {
1231
1257
  target: "workbench.view";
@@ -1244,6 +1270,7 @@ export declare const dashboardViewDataSchema: z.ZodObject<{
1244
1270
  baseVersionId?: string | null | undefined;
1245
1271
  isDraft?: boolean | undefined;
1246
1272
  updatedAt?: string | undefined;
1273
+ canDelete?: boolean | undefined;
1247
1274
  }[] | undefined;
1248
1275
  };
1249
1276
  }>;
@@ -6,7 +6,7 @@ const analytics_1 = require("@xpert-ai/plugin-sdk/analytics");
6
6
  const artifact_document_1 = require("@xpert-ai/plugin-sdk/artifact-document");
7
7
  const dashboard_schema_1 = require("./dashboard.schema");
8
8
  const dashboard_navigation_1 = require("./dashboard-navigation");
9
- 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(), isDraft: v3_1.z.boolean().optional(), baseVersionId: v3_1.z.string().nullish() });
9
+ 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() });
10
10
  exports.dashboardSavedSchema = exports.dashboardSummarySchema.extend({ manifest: dashboard_schema_1.dashboardSchema, dependencies: artifact_document_1.artifactDocumentDependencyAssets.optional() });
11
11
  // Source identities belong to Analytics, including immutable Definitions and Artifacts.
12
12
  // The viewer consumes only the reference and label; DataSource fields are not universal.
@@ -7,7 +7,7 @@ const artifact_document_1 = require("@xpert-ai/plugin-sdk/artifact-document");
7
7
  const id = v3_1.z.string().regex(/^[A-Za-z][A-Za-z0-9_.-]{0,79}$/);
8
8
  exports.dashboardSchema = v3_1.z.object({
9
9
  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).min(1).max(8),
10
+ bindings: v3_1.z.array(analytics_1.analyticsBindingSchema).max(8),
11
11
  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
12
  controls: v3_1.z.array(v3_1.z.object({ controlId: id, type: v3_1.z.enum(['string', 'number', 'boolean', 'date']),
13
13
  defaultValue: v3_1.z.union([v3_1.z.string(), v3_1.z.number(), v3_1.z.boolean(), v3_1.z.null()]).optional(),
@@ -21,11 +21,13 @@ exports.dashboardSchema = v3_1.z.object({
21
21
  if (new Set(values).size !== values.length)
22
22
  ctx.addIssue({ code: 'custom', message: `Duplicate ${name}` });
23
23
  };
24
+ if (!manifest.bindings.length && (manifest.presentation || manifest.controls.length || manifest.charts.length || manifest.tables.length))
25
+ ctx.addIssue({ code: 'custom', message: 'An unconfigured draft must contain no views or controls' });
24
26
  unique(manifest.bindings.map((binding) => binding.id), 'binding');
25
27
  unique(manifest.bindings.map((binding) => binding.output.datasetId), 'dataset');
26
28
  unique(manifest.controls.map((control) => control.controlId), 'control');
27
29
  unique([...manifest.charts, ...manifest.tables].map((view) => view.id), 'view');
28
- if (!manifest.presentation && !manifest.charts.length && !manifest.tables.length)
30
+ if (manifest.bindings.length && !manifest.presentation && !manifest.charts.length && !manifest.tables.length)
29
31
  ctx.addIssue({ code: 'custom', message: 'At least one chart or table is required' });
30
32
  if (manifest.presentation && (manifest.charts.length || manifest.tables.length))
31
33
  ctx.addIssue({ code: 'custom', message: 'HTML presentation owns its charts; use empty charts/tables arrays' });
@@ -12,11 +12,16 @@ export declare class DashboardService {
12
12
  preview(context: XpertResolvedViewHostContext, input: AnalyticsQueryRequest): Promise<import("@xpert-ai/contracts").AnalyticalDataset>;
13
13
  list(context: XpertResolvedViewHostContext): Promise<{
14
14
  title: string | null | undefined;
15
+ draftRevision?: number | undefined;
16
+ canDelete: boolean;
15
17
  artifactId: string;
16
18
  versionId: string | null | undefined;
17
19
  versionNumber: number | undefined;
18
20
  updatedAt: string | Date | undefined;
19
21
  }[]>;
22
+ trash(context: XpertResolvedViewHostContext, artifactId: string, expectedRevision: number): Promise<{
23
+ artifactId: string;
24
+ }>;
20
25
  save(context: XpertResolvedViewHostContext, manifest: DashboardManifest, expectedVersionId: string | null, expectedRevision?: number): Promise<{
21
26
  artifactId: string;
22
27
  draftId: string;
@@ -194,6 +199,11 @@ export declare class DashboardService {
194
199
  };
195
200
  } | undefined;
196
201
  datasets: import("@xpert-ai/contracts").AnalyticalDataset[];
202
+ failures: {
203
+ bindingId: string;
204
+ datasetId: string;
205
+ reason: "unavailable";
206
+ }[];
197
207
  manifest: {
198
208
  schema: "xpert.live-dashboard";
199
209
  id: string;
@@ -238,6 +248,9 @@ export declare class DashboardService {
238
248
  versionNumber: number | undefined;
239
249
  }>;
240
250
  queryBinding(context: XpertResolvedViewHostContext, artifactId: string, versionId: string | undefined, bindingId: string, input: Record<string, AnalyticsValue>, draftRevision?: number): Promise<import("@xpert-ai/contracts").AnalyticalDataset>;
251
+ /** Every operation uses the current human's Project role; data queries still use actor(context). */
252
+ authorizeProject(context: XpertResolvedViewHostContext, operation?: 'read' | 'write' | 'manage'): Promise<import("@xpert-ai/plugin-sdk").ProjectHumanAccess | undefined>;
241
253
  private execute;
242
254
  private probeControls;
243
255
  }
256
+ export declare function projectId(context: XpertResolvedViewHostContext): string | null | undefined;
@@ -13,6 +13,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.DashboardService = exports.PLUGIN_NAME = void 0;
16
+ exports.projectId = projectId;
16
17
  const common_1 = require("@nestjs/common");
17
18
  const node_crypto_1 = require("node:crypto");
18
19
  const plugin_sdk_1 = require("@xpert-ai/plugin-sdk");
@@ -39,20 +40,38 @@ let DashboardService = class DashboardService {
39
40
  return dataset;
40
41
  }
41
42
  async list(context) {
42
- actor(context);
43
+ const access = await this.authorizeProject(context);
43
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 });
44
- const items = result.items.filter((item) => item.resourceId.startsWith(hostPrefix(context)) && item.userId === context.userId);
45
+ const items = result.items.filter((item) => item.resourceId.startsWith(hostPrefix(context)) && (item.userId === context.userId || !item.userId && item.projectId === projectId(context)));
45
46
  const drafts = new Map((await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).list(items.map(item => item.id))).map(draft => [draft.artifactId, draft]));
46
- return items.map(item => ({ ...summary(item), ...(drafts.has(item.id) ? { title: drafts.get(item.id).title ?? item.title } : {}) }));
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
+ });
52
+ }
53
+ async trash(context, artifactId, expectedRevision) {
54
+ await this.authorizeProject(context, 'manage');
55
+ const artifact = await this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability).getArtifact(artifactId);
56
+ assertOwned(context, artifact);
57
+ if (artifact.status !== 'active')
58
+ throw new common_1.ForbiddenException('DASHBOARD_ARCHIVED');
59
+ // Reuse the platform's atomic revision check, recycle state and share revocation.
60
+ await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).setRecycled({ artifactId, expectedRevision, recycled: true });
61
+ return { artifactId };
47
62
  }
48
63
  async save(context, manifest, expectedVersionId, expectedRevision = 0) {
64
+ await this.authorizeProject(context, 'write');
49
65
  const scopedActor = actor(context);
50
66
  const parsed = dashboard_schema_1.dashboardSchema.parse(manifest);
51
67
  const artifacts = this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability);
52
68
  const source = { pluginName: exports.PLUGIN_NAME, resourceType: RESOURCE_TYPE, resourceId: `${hostPrefix(context)}${parsed.id}` };
53
69
  const existing = await artifacts.findArtifactBySource(source);
54
- if (existing && existing.userId !== scopedActor.userId)
55
- throw new common_1.ForbiddenException('DASHBOARD_ACCESS_DENIED');
70
+ if (existing) {
71
+ assertOwned(context, existing);
72
+ if (existing.status !== 'active')
73
+ throw new common_1.ForbiddenException('DASHBOARD_ARCHIVED');
74
+ }
56
75
  // A failed first upload can leave an Artifact with no saved content; revision 0 initializes it.
57
76
  const previous = existing && (existing.currentVersionId || expectedRevision > 0) ? await this.openDocument(context, existing.id) : null;
58
77
  const dependencies = await (0, document_libraries_1.resolveLibraryAssets)(parsed.presentation?.dependencies ?? [], previous?.dependencies);
@@ -70,14 +89,22 @@ let DashboardService = class DashboardService {
70
89
  await this.probeControls(context, binding, Object.fromEntries(parsed.controls.filter((control) => control.defaultValue !== undefined).map((control) => [control.controlId, control.defaultValue ?? null])));
71
90
  const htmlValidation = await this.htmlValidation.validate(context, parsed, preview, dependencies);
72
91
  const artifact = existing ?? await artifacts.createArtifact({ source, kind: 'file', title: parsed.title,
73
- scope: { ...scopedActor, ...(context.hostType === 'project' ? { projectId: context.hostId } : { xpertId: context.hostId }) } });
92
+ scope: { ...scopedActor, ...(projectId(context) ? { projectId: projectId(context), userId: null } : { xpertId: context.hostId }) } });
74
93
  const files = this.runtime.require(plugin_sdk_1.WorkspaceFilesRuntimeCapability);
75
- const scope = { tenantId: scopedActor.tenantId, userId: scopedActor.userId, catalog: 'users' };
76
- const file = await files.uploadBuffer({ ...scope, buffer, originalName: 'dashboard.json', fileName: `${digest}.json`, folder: `live-dashboards/${parsed.id}`, mimeType: MIME_TYPE });
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
+ }));
77
105
  const result = await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).save({ artifactId: artifact.id, expectedRevision,
78
106
  expectedCurrentVersionId: expectedVersionId, mimeType: MIME_TYPE, title: parsed.title, fileName: 'dashboard.json', sha256: digest,
79
- ...(htmlValidation ? { metadata: { htmlValidation } } : {}),
80
- workspaceFileRef: { source: 'platform.workspace.files', ...scope, filePath: file.filePath, workspacePath: file.workspacePath } });
107
+ ...(htmlValidation ? { metadata: { htmlValidation } } : {}), workspaceFileRef: reference });
81
108
  return { artifactId: artifact.id, draftId: result.id, draftRevision: result.revision, baseVersionId: result.baseVersionId, status: 'draft', title: parsed.title };
82
109
  }
83
110
  async open(context, artifactId, versionId, draftRevision) {
@@ -86,10 +113,12 @@ let DashboardService = class DashboardService {
86
113
  }
87
114
  /** Trusted View transport only. Agent read tools receive locks, never megabytes of library code. */
88
115
  async openDocument(context, artifactId, versionId, draftRevision) {
89
- actor(context);
116
+ await this.authorizeProject(context);
90
117
  const artifacts = this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability);
91
118
  const artifact = await artifacts.getArtifact(artifactId);
92
119
  assertOwned(context, artifact);
120
+ if (artifact.status !== 'active')
121
+ throw new common_1.ForbiddenException('DASHBOARD_ARCHIVED');
93
122
  const draft = !versionId || draftRevision !== undefined ? await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).get(artifactId) : null;
94
123
  if (draftRevision !== undefined && draft?.revision !== draftRevision)
95
124
  throw new common_1.ConflictException('DASHBOARD_DRAFT_CONFLICT');
@@ -109,10 +138,12 @@ let DashboardService = class DashboardService {
109
138
  isDraft: draft.revision !== draft.publishedRevision, title: draft.title, updatedAt: draft.updatedAt } : {}), manifest, dependencies };
110
139
  }
111
140
  async history(context, artifactId) {
112
- actor(context);
141
+ await this.authorizeProject(context);
113
142
  const artifacts = this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability);
114
143
  const artifact = await artifacts.getArtifact(artifactId);
115
144
  assertOwned(context, artifact);
145
+ if (artifact.status !== 'active')
146
+ throw new common_1.ForbiddenException('DASHBOARD_ARCHIVED');
116
147
  const versions = await artifacts.listArtifactVersions({ artifactId, status: 'active' });
117
148
  return versions.map(version => ({ ...summary(artifact, version), current: version.id === artifact.currentVersionId }))
118
149
  .sort((a, b) => (b.versionNumber ?? 0) - (a.versionNumber ?? 0));
@@ -124,7 +155,10 @@ let DashboardService = class DashboardService {
124
155
  return this.save(context, dashboard_schema_1.dashboardSchema.parse({ ...saved.manifest, ...dashboard_schema_1.dashboardChangesSchema.parse(changes) }), saved.baseVersionId ?? saved.versionId ?? null, expectedRevision);
125
156
  }
126
157
  async publish(context, artifactId, expectedRevision, expectedCurrentVersionId) {
158
+ await this.authorizeProject(context, 'manage');
127
159
  const saved = await this.open(context, artifactId, undefined, expectedRevision);
160
+ if (!saved.manifest.bindings.length)
161
+ throw new common_1.BadRequestException('DASHBOARD_CONFIGURE_BEFORE_PUBLISHING');
128
162
  const share = await this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability).getArtifactShare({ artifactId, shareKey: dashboard_sharing_1.DASHBOARD_SHARE_KEY });
129
163
  if (share?.status === 'active' && share.versionMode === 'latest' && (!share.expiresAt || new Date(share.expiresAt).getTime() > Date.now())) {
130
164
  const issues = (0, dashboard_sharing_1.dashboardSharingIssues)(saved.manifest, await this.sources(context));
@@ -138,13 +172,31 @@ let DashboardService = class DashboardService {
138
172
  }
139
173
  async query(context, artifactId, versionId, controls, draftRevision) {
140
174
  const saved = await this.open(context, artifactId, versionId, draftRevision);
141
- const datasets = await this.execute(context, saved.manifest, controls);
142
- return { ...saved, datasets, ...(saved.manifest.presentation ? {} : { payload: (0, renderer_1.toDataAnalyticsPayload)(saved.manifest, datasets) }) };
175
+ const { datasets, failures } = await (0, dashboard_queries_1.queryDashboardPartial)(this.runtime, actor(context), saved.manifest, controls);
176
+ return { ...saved, datasets, failures, ...(saved.manifest.presentation ? {} : { payload: (0, renderer_1.toDataAnalyticsPayload)(saved.manifest, datasets) }) };
143
177
  }
144
178
  async queryBinding(context, artifactId, versionId, bindingId, input, draftRevision) {
145
179
  const saved = await this.open(context, artifactId, versionId, draftRevision);
146
180
  return (0, dashboard_queries_1.queryDashboardBinding)(this.runtime, actor(context), saved.manifest, bindingId, input);
147
181
  }
182
+ /** Every operation uses the current human's Project role; data queries still use actor(context). */
183
+ async authorizeProject(context, operation = 'read') {
184
+ const scopedActor = actor(context), project = projectId(context);
185
+ if (!project)
186
+ return;
187
+ const access = this.runtime.require(plugin_sdk_1.ProjectAccessRuntimeCapability);
188
+ const input = { actor: scopedActor, projectId: project };
189
+ if (context.hostType === 'agent')
190
+ await access.assertAssistant({ ...input, assistantId: context.hostId });
191
+ if (operation === 'write')
192
+ return access.assertEdit(input);
193
+ if (operation === 'manage')
194
+ return access.assertManage(input);
195
+ const readable = (await access.listReadable({ actor: scopedActor, projectIds: [project] })).find(item => item.projectId === project);
196
+ if (!readable)
197
+ throw new common_1.ForbiddenException('DASHBOARD_ACCESS_DENIED');
198
+ return readable;
199
+ }
148
200
  execute(context, manifest, input) {
149
201
  return (0, dashboard_queries_1.queryDashboard)(this.runtime, actor(context), manifest, input);
150
202
  }
@@ -177,10 +229,11 @@ function actor(context) {
177
229
  throw new common_1.ForbiddenException('DASHBOARD_ACCESS_DENIED');
178
230
  return { tenantId: context.tenantId, organizationId: context.organizationId, userId: context.userId };
179
231
  }
180
- function hostPrefix(context) { return `${context.hostType}/${context.hostId}/`; }
232
+ function projectId(context) { return context.hostType === 'project' ? context.hostId : context.runtimeScope?.projectId; }
233
+ function hostPrefix(context) { return projectId(context) ? `project/${projectId(context)}/` : `${context.hostType}/${context.hostId}/`; }
181
234
  function assertOwned(context, artifact) {
182
235
  if (artifact.pluginName !== exports.PLUGIN_NAME || artifact.resourceType !== RESOURCE_TYPE || !artifact.resourceId.startsWith(hostPrefix(context)) ||
183
- artifact.tenantId !== context.tenantId || artifact.organizationId !== context.organizationId || artifact.userId !== context.userId)
236
+ artifact.tenantId !== context.tenantId || artifact.organizationId !== context.organizationId || (artifact.userId ? artifact.userId !== context.userId : !projectId(context) || artifact.projectId !== projectId(context)))
184
237
  throw new common_1.ForbiddenException('DASHBOARD_ACCESS_DENIED');
185
238
  }
186
239
  function summary(artifact, version = artifact.currentVersion) {
@@ -0,0 +1,4 @@
1
+ import { type ArtifactDocumentHostOptions } from '@xpert-ai/plugin-sdk/artifact-document';
2
+ /** The HTML never supplies or overrides its network permissions. */
3
+ export declare function mountDashboardDocument<T>(frame: HTMLIFrameElement, options: ArtifactDocumentHostOptions<T>): import("@xpert-ai/plugin-sdk/artifact-document").ArtifactDocumentHost;
4
+ export declare function isDashboardResourceAllowed(resourceType: string, url: string): boolean;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mountDashboardDocument = mountDashboardDocument;
4
+ exports.isDashboardResourceAllowed = isDashboardResourceAllowed;
5
+ const artifact_document_1 = require("@xpert-ai/plugin-sdk/artifact-document");
6
+ const document_network_1 = require("./document-network");
7
+ /** The HTML never supplies or overrides its network permissions. */
8
+ function mountDashboardDocument(frame, options) {
9
+ return (0, artifact_document_1.mountArtifactDocument)(frame, { ...options, networkPolicy: document_network_1.DASHBOARD_NETWORK_POLICY });
10
+ }
11
+ function isDashboardResourceAllowed(resourceType, url) {
12
+ return (0, artifact_document_1.isArtifactDocumentResourceAllowed)(document_network_1.DASHBOARD_NETWORK_POLICY, resourceType, url);
13
+ }
@@ -0,0 +1,3 @@
1
+ import type { ArtifactDocumentNetworkPolicy } from '@xpert-ai/plugin-sdk/artifact-document';
2
+ /** Trusted plugin policy, shared by the displayed document and browser preflight. */
3
+ export declare const DASHBOARD_NETWORK_POLICY: ArtifactDocumentNetworkPolicy;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
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
+ };
@@ -1 +1 @@
1
- export declare const HTML_DASHBOARD_GUIDANCE = "For free-layout HTML dashboards, read the installed live-dashboard skill's references/html-runtime.md through read_skill_file, relative to its SKILL.md path in the skill catalog. Create/edit the HTML with SandboxFile; then dashboard_create_html or dashboard_update validates and saves it. Do not use generic present_files to save or preview a live dashboard. Its preview belongs to the Live dashboards Workbench. ECharts/Three.js are available via dashboard_libraries; all data access remains declarative Binding queries through window.xpertLiveArtifact. The page MUST declare const live = window.xpertLiveArtifact in its script (live is not a host global), register live.subscribe(bindingId, render), await live.ready(), and invoke live.query(bindingId, typedControls) for initial data and filter changes. window.__datasets and window.__refresh are not platform APIs and are never populated by the host. Read the runtime reference and its html-lifecycle.md example before writing HTML; preserve that lifecycle while freely changing the layout. Use one complete Dataset for KPI, trend and details when they share the same metric and filters; do not create a redundant unfiltered total Binding. Never let filtered and unfiltered subscriptions both write the same KPI: a refresh would race and overwrite the selected-region total. Distinct global totals need distinct, clearly labeled elements. Native data selects require data-dashboard-control=\"<controlId>\"; purely visual selects use data-dashboard-presentation. A data filter must query, not only filter cached browser rows. bindSelect controls ONE binding only. For a shared dimension filter across multiple bindings, follow the skill custom-select example and query every affected binding with the same typed controls; matching control IDs do not broadcast automatically. bindSelect derives options from Dataset fields and is only for dimension filters. Fixed query enums (days, page size, granularity) need options from parameter.allowedValues and a typed change listener calling live.query; never use date rows as numeric day-count options.";
1
+ export declare const HTML_DASHBOARD_GUIDANCE = "For free-layout HTML dashboards, read the installed live-dashboard skill's references/html-runtime.md through read_skill_file, relative to its SKILL.md path in the skill catalog. Create/edit the HTML with SandboxFile; then dashboard_create_html or dashboard_update validates and saves it. Do not use generic present_files to save or preview a live dashboard. Its preview belongs to the Live dashboards Workbench. Call dashboard_libraries for packaged ECharts/Three.js and the allowed HTTPS origins for online scripts, stylesheets and fonts. Tailwind CDN is supported through script src; pin a version when possible. Online resource failures must be repaired before saving. Do not add online URLs to the packaged libraries array; all data access remains declarative Binding queries through window.xpertLiveArtifact. The page MUST declare const live = window.xpertLiveArtifact in its script (live is not a host global), register live.subscribe(bindingId, render), await live.ready(), and invoke live.query(bindingId, typedControls) for initial data and filter changes. window.__datasets and window.__refresh are not platform APIs and are never populated by the host. Read the runtime reference and its html-lifecycle.md example before writing HTML; preserve that lifecycle while freely changing the layout. Use one complete Dataset for KPI, trend and details when they share the same metric and filters; do not create a redundant unfiltered total Binding. Never let filtered and unfiltered subscriptions both write the same KPI: a refresh would race and overwrite the selected-region total. Distinct global totals need distinct, clearly labeled elements. Native data selects require data-dashboard-control=\"<controlId>\"; purely visual selects use data-dashboard-presentation. A data filter must query, not only filter cached browser rows. bindSelect controls ONE binding only. For a shared dimension filter across multiple bindings, follow the skill custom-select example and query every affected binding with the same typed controls; matching control IDs do not broadcast automatically. bindSelect derives options from Dataset fields and is only for dimension filters. Fixed query enums (days, page size, granularity) need options from parameter.allowedValues and a typed change listener calling live.query; never use date rows as numeric day-count options.";
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.HTML_DASHBOARD_GUIDANCE = void 0;
4
- exports.HTML_DASHBOARD_GUIDANCE = `For free-layout HTML dashboards, read the installed live-dashboard skill's references/html-runtime.md through read_skill_file, relative to its SKILL.md path in the skill catalog. Create/edit the HTML with SandboxFile; then dashboard_create_html or dashboard_update validates and saves it. Do not use generic present_files to save or preview a live dashboard. Its preview belongs to the Live dashboards Workbench. ECharts/Three.js are available via dashboard_libraries; all data access remains declarative Binding queries through window.xpertLiveArtifact. The page MUST declare const live = window.xpertLiveArtifact in its script (live is not a host global), register live.subscribe(bindingId, render), await live.ready(), and invoke live.query(bindingId, typedControls) for initial data and filter changes. window.__datasets and window.__refresh are not platform APIs and are never populated by the host. Read the runtime reference and its html-lifecycle.md example before writing HTML; preserve that lifecycle while freely changing the layout. Use one complete Dataset for KPI, trend and details when they share the same metric and filters; do not create a redundant unfiltered total Binding. Never let filtered and unfiltered subscriptions both write the same KPI: a refresh would race and overwrite the selected-region total. Distinct global totals need distinct, clearly labeled elements. Native data selects require data-dashboard-control="<controlId>"; purely visual selects use data-dashboard-presentation. A data filter must query, not only filter cached browser rows. bindSelect controls ONE binding only. For a shared dimension filter across multiple bindings, follow the skill custom-select example and query every affected binding with the same typed controls; matching control IDs do not broadcast automatically. bindSelect derives options from Dataset fields and is only for dimension filters. Fixed query enums (days, page size, granularity) need options from parameter.allowedValues and a typed change listener calling live.query; never use date rows as numeric day-count options.`;
4
+ exports.HTML_DASHBOARD_GUIDANCE = `For free-layout HTML dashboards, read the installed live-dashboard skill's references/html-runtime.md through read_skill_file, relative to its SKILL.md path in the skill catalog. Create/edit the HTML with SandboxFile; then dashboard_create_html or dashboard_update validates and saves it. Do not use generic present_files to save or preview a live dashboard. Its preview belongs to the Live dashboards Workbench. Call dashboard_libraries for packaged ECharts/Three.js and the allowed HTTPS origins for online scripts, stylesheets and fonts. Tailwind CDN is supported through script src; pin a version when possible. Online resource failures must be repaired before saving. Do not add online URLs to the packaged libraries array; all data access remains declarative Binding queries through window.xpertLiveArtifact. The page MUST declare const live = window.xpertLiveArtifact in its script (live is not a host global), register live.subscribe(bindingId, render), await live.ready(), and invoke live.query(bindingId, typedControls) for initial data and filter changes. window.__datasets and window.__refresh are not platform APIs and are never populated by the host. Read the runtime reference and its html-lifecycle.md example before writing HTML; preserve that lifecycle while freely changing the layout. Use one complete Dataset for KPI, trend and details when they share the same metric and filters; do not create a redundant unfiltered total Binding. Never let filtered and unfiltered subscriptions both write the same KPI: a refresh would race and overwrite the selected-region total. Distinct global totals need distinct, clearly labeled elements. Native data selects require data-dashboard-control="<controlId>"; purely visual selects use data-dashboard-presentation. A data filter must query, not only filter cached browser rows. bindSelect controls ONE binding only. For a shared dimension filter across multiple bindings, follow the skill custom-select example and query every affected binding with the same typed controls; matching control IDs do not broadcast automatically. bindSelect derives options from Dataset fields and is only for dimension filters. Fixed query enums (days, page size, granularity) need options from parameter.allowedValues and a typed change listener calling live.query; never use date rows as numeric day-count options.`;