@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
@@ -0,0 +1,169 @@
1
+ import { DashboardSaveRequests } from './save-requests';
2
+ import { z } from 'zod/v3';
3
+ import type { XpertResolvedViewHostContext } from '@xpert-ai/contracts';
4
+ import { type RuntimeCapabilityRegistry, type WorkspacePortableFileReference } from '@xpert-ai/plugin-sdk';
5
+ import { DashboardService } from './dashboard.service';
6
+ export declare const importInputSchema: z.ZodObject<{
7
+ importId: z.ZodString;
8
+ bindings: z.ZodArray<z.ZodObject<{
9
+ bindingId: z.ZodString;
10
+ source: z.ZodType<import("@xpert-ai/contracts").AnalyticsSourceRef, z.ZodTypeDef, import("@xpert-ai/contracts").AnalyticsSourceRef>;
11
+ payload: z.ZodType<import("@xpert-ai/contracts").JSONValue, z.ZodTypeDef, import("@xpert-ai/contracts").JSONValue>;
12
+ }, "strict", z.ZodTypeAny, {
13
+ bindingId: string;
14
+ source: import("@xpert-ai/contracts").AnalyticsSourceRef;
15
+ payload: import("@xpert-ai/contracts").JSONValue;
16
+ }, {
17
+ bindingId: string;
18
+ source: import("@xpert-ai/contracts").AnalyticsSourceRef;
19
+ payload: import("@xpert-ai/contracts").JSONValue;
20
+ }>, "many">;
21
+ }, "strict", z.ZodTypeAny, {
22
+ bindings: {
23
+ bindingId: string;
24
+ source: import("@xpert-ai/contracts").AnalyticsSourceRef;
25
+ payload: import("@xpert-ai/contracts").JSONValue;
26
+ }[];
27
+ importId: string;
28
+ }, {
29
+ bindings: {
30
+ bindingId: string;
31
+ source: import("@xpert-ai/contracts").AnalyticsSourceRef;
32
+ payload: import("@xpert-ai/contracts").JSONValue;
33
+ }[];
34
+ importId: string;
35
+ }>;
36
+ declare const importBrowseSchema: z.ZodObject<{
37
+ importId: z.ZodString;
38
+ bindingId: z.ZodString;
39
+ source: z.ZodType<import("@xpert-ai/contracts").AnalyticsSourceRef, z.ZodTypeDef, import("@xpert-ai/contracts").AnalyticsSourceRef>;
40
+ request: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodType<import("@xpert-ai/contracts").JSONValue, z.ZodTypeDef, import("@xpert-ai/contracts").JSONValue>>>;
41
+ }, "strict", z.ZodTypeAny, {
42
+ bindingId: string;
43
+ source: import("@xpert-ai/contracts").AnalyticsSourceRef;
44
+ importId: string;
45
+ request: Record<string, import("@xpert-ai/contracts").JSONValue>;
46
+ }, {
47
+ bindingId: string;
48
+ source: import("@xpert-ai/contracts").AnalyticsSourceRef;
49
+ importId: string;
50
+ request?: Record<string, import("@xpert-ai/contracts").JSONValue> | undefined;
51
+ }>;
52
+ type ImportInput = z.infer<typeof importInputSchema>;
53
+ export declare class DashboardArchiveService {
54
+ private readonly dashboards;
55
+ private readonly runtime;
56
+ private readonly saves?;
57
+ private readonly imports;
58
+ constructor(dashboards: DashboardService, runtime: RuntimeCapabilityRegistry, saves?: DashboardSaveRequests | undefined);
59
+ private authorize;
60
+ private session;
61
+ prepare(context: XpertResolvedViewHostContext, bytes: Buffer): Promise<{
62
+ importId: `${string}-${string}-${string}-${string}-${string}`;
63
+ manifest: {
64
+ title: string;
65
+ id: string;
66
+ schema: "xpert.live-dashboard";
67
+ bindings: import("@xpert-ai/contracts").AnalyticsBinding[];
68
+ controls: {
69
+ type: "string" | "number" | "boolean" | "date";
70
+ controlId: string;
71
+ label: string;
72
+ defaultValue?: string | number | boolean | null | undefined;
73
+ allowedValues?: (string | number | boolean | null)[] | undefined;
74
+ }[];
75
+ charts: {
76
+ type: "bar" | "line" | "area" | "pie" | "scatter";
77
+ title: string;
78
+ id: string;
79
+ datasetId: string;
80
+ x: string;
81
+ y: string;
82
+ color?: string | undefined;
83
+ }[];
84
+ tables: {
85
+ title: string;
86
+ id: string;
87
+ datasetId: string;
88
+ fields: string[];
89
+ }[];
90
+ sourceMetadata?: {
91
+ bindingId: string;
92
+ title: string;
93
+ description?: string | undefined;
94
+ }[] | undefined;
95
+ components?: {
96
+ title: string;
97
+ id: string;
98
+ elementId: string;
99
+ kind: "filter" | "metric" | "chart" | "table" | "text";
100
+ sources: {
101
+ bindingId: string;
102
+ fieldIds?: string[] | undefined;
103
+ }[];
104
+ }[] | undefined;
105
+ presentation?: {
106
+ type: "html";
107
+ html: string;
108
+ dependencies?: import("@xpert-ai/plugin-sdk/artifact-document").ArtifactDocumentDependencyLock[] | undefined;
109
+ } | undefined;
110
+ };
111
+ requirements: {
112
+ schema: string;
113
+ version: number;
114
+ bindings: {
115
+ id: string;
116
+ providerId: string;
117
+ operation: string;
118
+ contractVersion: number;
119
+ source: import("@xpert-ai/contracts").AnalyticsSourceRef;
120
+ }[];
121
+ externalResources: string[];
122
+ dynamicResourcesMayExist: boolean;
123
+ includesQueryResults: boolean;
124
+ requiresAuthorizedHost: boolean;
125
+ };
126
+ sources: import("@xpert-ai/contracts").AnalyticsSourceSummary[];
127
+ }>;
128
+ browse(context: XpertResolvedViewHostContext, input: z.infer<typeof importBrowseSchema>): Promise<import("@xpert-ai/contracts").AnalyticsDiscoveryResult>;
129
+ commit(context: XpertResolvedViewHostContext, input: ImportInput): Promise<{
130
+ artifactId: string;
131
+ draftId: string;
132
+ draftRevision: number;
133
+ baseVersionId: string | null;
134
+ status: "draft";
135
+ title: string;
136
+ } | {
137
+ cursor: string;
138
+ nextAction: string;
139
+ error?: string | undefined;
140
+ sourceFile?: {
141
+ path: string;
142
+ sha256: string;
143
+ } | undefined;
144
+ dashboard?: {
145
+ status: "draft";
146
+ title: string;
147
+ artifactId: string;
148
+ draftRevision: number;
149
+ draftId: string;
150
+ baseVersionId: string | null;
151
+ } | undefined;
152
+ requestId: string;
153
+ state: "failed" | "running" | "succeeded" | "queued";
154
+ stage: string;
155
+ completed: number;
156
+ total: number;
157
+ terminal: boolean;
158
+ }>;
159
+ discard(context: XpertResolvedViewHostContext, id: string): Promise<{
160
+ discarded: boolean;
161
+ }>;
162
+ exportFile(context: XpertResolvedViewHostContext, fileKey: string): Promise<{
163
+ reference: WorkspacePortableFileReference;
164
+ fileName: string;
165
+ mimeType: string;
166
+ size: number;
167
+ }>;
168
+ }
169
+ export { importBrowseSchema };
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.importBrowseSchema = exports.DashboardArchiveService = exports.importInputSchema = void 0;
16
+ const save_requests_1 = require("./save-requests");
17
+ const capacity_1 = require("./capacity");
18
+ const common_1 = require("@nestjs/common");
19
+ const node_crypto_1 = require("node:crypto");
20
+ const v3_1 = require("zod/v3");
21
+ const plugin_sdk_1 = require("@xpert-ai/plugin-sdk");
22
+ const analytics_1 = require("@xpert-ai/plugin-sdk/analytics");
23
+ const dashboard_service_1 = require("./dashboard.service");
24
+ const dashboard_schema_1 = require("./dashboard.schema");
25
+ const dashboard_archive_1 = require("./dashboard-archive");
26
+ const document_package_1 = require("./document-package");
27
+ exports.importInputSchema = v3_1.z.object({ importId: v3_1.z.string().uuid(),
28
+ bindings: v3_1.z.array(v3_1.z.object({ bindingId: v3_1.z.string(), source: analytics_1.analyticsSourceRefSchema, payload: analytics_1.analyticsBindingObjectSchema.shape.payload }).strict()).max(capacity_1.MAX_DASHBOARD_BINDINGS)
29
+ }).strict();
30
+ const importBrowseSchema = v3_1.z.object({ importId: v3_1.z.string().uuid(), bindingId: v3_1.z.string(), source: analytics_1.analyticsSourceRefSchema, request: analytics_1.analyticsOptionsSchema.default({}) }).strict();
31
+ exports.importBrowseSchema = importBrowseSchema;
32
+ let DashboardArchiveService = class DashboardArchiveService {
33
+ constructor(dashboards, runtime, saves) {
34
+ this.dashboards = dashboards;
35
+ this.runtime = runtime;
36
+ this.saves = saves;
37
+ this.imports = new Map();
38
+ }
39
+ async authorize(context) {
40
+ if (!(await this.dashboards.access(context)).canEdit)
41
+ throw new common_1.ForbiddenException('DASHBOARD_DEFINITION_ACCESS_DENIED');
42
+ }
43
+ session(context, id) {
44
+ const session = this.imports.get(id);
45
+ if (!session || session.scope !== importScope(context) || Date.now() - session.created > 30 * 60_000)
46
+ throw new common_1.BadRequestException('DASHBOARD_IMPORT_EXPIRED');
47
+ return session;
48
+ }
49
+ async prepare(context, bytes) {
50
+ await this.authorize(context);
51
+ const document = (0, dashboard_archive_1.decodeDashboardArchive)(bytes);
52
+ for (const [key, session] of this.imports)
53
+ if (Date.now() - session.created > 30 * 60_000)
54
+ this.imports.delete(key);
55
+ if (this.imports.size >= 16)
56
+ throw new common_1.BadRequestException('DASHBOARD_IMPORT_LIMIT');
57
+ const importId = (0, node_crypto_1.randomUUID)();
58
+ this.imports.set(importId, { ...document, scope: importScope(context), created: Date.now(), newId: (0, node_crypto_1.randomUUID)(), busy: false });
59
+ return { importId, manifest: document.manifest, requirements: (0, dashboard_archive_1.dashboardRequirements)(document.manifest), sources: await this.dashboards.sources(context) };
60
+ }
61
+ async browse(context, input) {
62
+ await this.authorize(context);
63
+ const session = this.session(context, input.importId);
64
+ const binding = session.manifest.bindings.find(item => item.id === input.bindingId);
65
+ if (!binding)
66
+ throw new common_1.BadRequestException('DASHBOARD_BINDING_DENIED');
67
+ return this.dashboards.discover(context, { source: input.source, providerId: binding.providerId, request: input.request });
68
+ }
69
+ async commit(context, input) {
70
+ await this.authorize(context);
71
+ const session = this.session(context, input.importId);
72
+ if (session.busy)
73
+ throw new common_1.BadRequestException('DASHBOARD_IMPORT_RUNNING');
74
+ if (new Set(input.bindings.map(item => item.bindingId)).size !== input.bindings.length || input.bindings.some(item => !session.manifest.bindings.some(binding => binding.id === item.bindingId)))
75
+ throw new common_1.BadRequestException('DASHBOARD_IMPORT_BINDINGS_INVALID');
76
+ const manifest = dashboard_schema_1.dashboardSchema.parse({ ...session.manifest, id: session.newId,
77
+ bindings: session.manifest.bindings.map(binding => {
78
+ const replacement = input.bindings.find(item => item.bindingId === binding.id);
79
+ return replacement ? { ...binding, source: replacement.source, payload: replacement.payload } : binding;
80
+ }) });
81
+ const hash = (0, node_crypto_1.createHash)('sha256').update((0, document_package_1.encodeDashboardPackage)(manifest, session.dependencies)).digest('hex');
82
+ if (session.submitted && this.saves) {
83
+ if (session.submitted.hash !== hash)
84
+ throw new common_1.BadRequestException('DASHBOARD_IMPORT_ALREADY_SUBMITTED');
85
+ return this.saves.get(context, session.submitted.requestId);
86
+ }
87
+ if (session.committed) {
88
+ if (session.committed.hash !== hash)
89
+ throw new common_1.BadRequestException('DASHBOARD_IMPORT_ALREADY_COMPLETED');
90
+ // Revalidate ownership and current access before returning an idempotent receipt.
91
+ await this.dashboards.open(context, session.committed.receipt.artifactId);
92
+ return session.committed.receipt;
93
+ }
94
+ session.busy = true;
95
+ try {
96
+ if (this.saves && manifest.bindings.length > 8) {
97
+ const request = await this.saves.submit(context, manifest, null, 0, `import/${input.importId}/${hash}`, undefined, session.dependencies);
98
+ session.submitted = { hash, requestId: request.requestId };
99
+ return request;
100
+ }
101
+ const receipt = await this.dashboards.save(context, manifest, null, 0, session.dependencies);
102
+ session.committed = { hash, receipt };
103
+ return receipt;
104
+ }
105
+ finally {
106
+ session.busy = false;
107
+ }
108
+ }
109
+ async discard(context, id) {
110
+ await this.authorize(context);
111
+ const session = this.session(context, id);
112
+ if (session.busy)
113
+ throw new common_1.BadRequestException('DASHBOARD_IMPORT_RUNNING');
114
+ this.imports.delete(id);
115
+ return { discarded: true };
116
+ }
117
+ async exportFile(context, fileKey) {
118
+ await this.authorize(context);
119
+ const match = /^archive\/([a-f0-9-]{36})\/(draft|version)\/([a-f0-9-]+)$/.exec(fileKey);
120
+ if (!match)
121
+ throw new common_1.BadRequestException('DASHBOARD_EXPORT_TARGET_INVALID');
122
+ const artifactId = v3_1.z.string().uuid().parse(match[1]);
123
+ const versionId = match[2] === 'version' ? v3_1.z.string().uuid().parse(match[3]) : undefined;
124
+ const revision = match[2] === 'draft' ? v3_1.z.coerce.number().int().positive().parse(match[3]) : undefined;
125
+ const saved = await this.dashboards.openDocument(context, artifactId, versionId, revision, versionId ? 'published' : 'draft');
126
+ const buffer = (0, dashboard_archive_1.encodeDashboardArchive)(saved.manifest, saved.dependencies);
127
+ const fileName = `${saved.manifest.title.replace(/[\\/\u0000-\u001f]/g, '_').slice(0, 100)}.xpert-dashboard.zip`;
128
+ const project = (0, dashboard_service_1.projectId)(context), hash = (0, node_crypto_1.createHash)('sha256').update(buffer).digest('hex');
129
+ const scope = { tenantId: context.tenantId, organizationId: context.organizationId, userId: context.userId,
130
+ ...(project ? { catalog: 'projects', scopeId: project, projectId: project } : { catalog: 'xperts', scopeId: context.hostId, xpertId: context.hostId }) };
131
+ const uploaded = await this.runtime.require(plugin_sdk_1.WorkspaceFilesRuntimeCapability).uploadBuffer({ ...scope, buffer, originalName: fileName,
132
+ fileName: `${hash}.zip`, folder: `dashboards/exports/${artifactId}`, mimeType: 'application/zip' });
133
+ const reference = { ...scope, source: 'platform.workspace.files', filePath: uploaded.filePath,
134
+ workspacePath: uploaded.filePath, originalName: fileName, mimeType: 'application/zip', size: buffer.length };
135
+ return { reference, fileName, mimeType: 'application/zip', size: buffer.length };
136
+ }
137
+ };
138
+ exports.DashboardArchiveService = DashboardArchiveService;
139
+ exports.DashboardArchiveService = DashboardArchiveService = __decorate([
140
+ (0, common_1.Injectable)(),
141
+ __param(1, (0, common_1.Inject)(plugin_sdk_1.XPERT_RUNTIME_CAPABILITIES_TOKEN)),
142
+ __param(2, (0, common_1.Optional)()),
143
+ __metadata("design:paramtypes", [dashboard_service_1.DashboardService, Object, save_requests_1.DashboardSaveRequests])
144
+ ], DashboardArchiveService);
145
+ function importScope(context) {
146
+ return JSON.stringify([context.tenantId, context.organizationId, context.userId, context.hostType, context.hostId, (0, dashboard_service_1.projectId)(context)]);
147
+ }
@@ -0,0 +1,2 @@
1
+ /** Drain every started task before returning; never exceed the worker budget on rejection. */
2
+ export declare function boundedMap<T, R>(items: readonly T[], run: (item: T, index: number) => Promise<R>, concurrency?: number): Promise<R[]>;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.boundedMap = boundedMap;
4
+ /** Drain every started task before returning; never exceed the worker budget on rejection. */
5
+ async function boundedMap(items, run, concurrency = 4) {
6
+ const results = new Array(items.length);
7
+ let next = 0, failed = false, failure;
8
+ await Promise.all(Array.from({ length: Math.min(Math.max(1, Math.min(8, concurrency)), items.length) }, async () => {
9
+ while (!failed && next < items.length) {
10
+ const index = next++;
11
+ try {
12
+ results[index] = await run(items[index], index);
13
+ }
14
+ catch (error) {
15
+ if (!failed) {
16
+ failed = true;
17
+ failure = error;
18
+ }
19
+ }
20
+ }
21
+ }));
22
+ if (failed)
23
+ throw failure;
24
+ return results;
25
+ }
@@ -0,0 +1,14 @@
1
+ import { z } from 'zod/v3';
2
+ /** Storage compatibility is independent of the administrator's authoring policy. */
3
+ export declare const MAX_DASHBOARD_BINDINGS = 64;
4
+ export declare const DEFAULT_DASHBOARD_BINDINGS = 32;
5
+ export declare const DASHBOARD_CONFIG = "live-dashboard.config";
6
+ export declare const dashboardConfigSchema: z.ZodDefault<z.ZodObject<{
7
+ maxBindings: z.ZodDefault<z.ZodNumber>;
8
+ }, "strict", z.ZodTypeAny, {
9
+ maxBindings: number;
10
+ }, {
11
+ maxBindings?: number | undefined;
12
+ }>>;
13
+ export type DashboardConfig = z.infer<typeof dashboardConfigSchema>;
14
+ export declare function assertBindingCapacity(count: number, previousCount?: number, limit?: number): void;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dashboardConfigSchema = exports.DASHBOARD_CONFIG = exports.DEFAULT_DASHBOARD_BINDINGS = exports.MAX_DASHBOARD_BINDINGS = void 0;
4
+ exports.assertBindingCapacity = assertBindingCapacity;
5
+ const v3_1 = require("zod/v3");
6
+ /** Storage compatibility is independent of the administrator's authoring policy. */
7
+ exports.MAX_DASHBOARD_BINDINGS = 64;
8
+ exports.DEFAULT_DASHBOARD_BINDINGS = 32;
9
+ exports.DASHBOARD_CONFIG = 'live-dashboard.config';
10
+ exports.dashboardConfigSchema = v3_1.z.object({
11
+ maxBindings: v3_1.z.number().int().min(1).max(exports.MAX_DASHBOARD_BINDINGS).default(exports.DEFAULT_DASHBOARD_BINDINGS)
12
+ }).strict().default({});
13
+ function assertBindingCapacity(count, previousCount = 0, limit = exports.DEFAULT_DASHBOARD_BINDINGS) {
14
+ if (count > exports.MAX_DASHBOARD_BINDINGS || count > limit && count > previousCount)
15
+ throw new Error(`DASHBOARD_BINDING_CAPACITY: ${count} bindings; authoring limit ${limit}, storage limit ${exports.MAX_DASHBOARD_BINDINGS}. Reuse existing queries or increase the installation policy.`);
16
+ }
@@ -15,7 +15,7 @@ export declare class DashboardContentProvider implements IContentResourceProvide
15
15
  providerKey: string;
16
16
  resourceId: string;
17
17
  title: string;
18
- role: "owner" | "manager" | "editor" | "member";
18
+ role: "owner" | "manager" | "editor" | "member" | "assistant";
19
19
  canWrite: boolean;
20
20
  canManage: boolean;
21
21
  }>;
@@ -0,0 +1,70 @@
1
+ import type { DashboardManifest } from './dashboard.schema';
2
+ import type { ArtifactDocumentDependencyAsset } from '@xpert-ai/plugin-sdk/artifact-document';
3
+ export declare const MAX_DASHBOARD_ARCHIVE_BYTES = 12000000;
4
+ export declare function dashboardRequirements(manifest: DashboardManifest): {
5
+ schema: string;
6
+ version: number;
7
+ bindings: {
8
+ id: string;
9
+ providerId: string;
10
+ operation: string;
11
+ contractVersion: number;
12
+ source: import("@xpert-ai/contracts").AnalyticsSourceRef;
13
+ }[];
14
+ externalResources: string[];
15
+ dynamicResourcesMayExist: boolean;
16
+ includesQueryResults: boolean;
17
+ requiresAuthorizedHost: boolean;
18
+ };
19
+ export declare function encodeDashboardArchive(manifest: DashboardManifest, dependencies: ArtifactDocumentDependencyAsset[]): Buffer<ArrayBuffer>;
20
+ export declare function decodeDashboardArchive(bytes: Buffer): {
21
+ manifest: {
22
+ title: string;
23
+ id: string;
24
+ schema: "xpert.live-dashboard";
25
+ bindings: import("@xpert-ai/contracts").AnalyticsBinding[];
26
+ controls: {
27
+ type: "string" | "number" | "boolean" | "date";
28
+ controlId: string;
29
+ label: string;
30
+ defaultValue?: string | number | boolean | null | undefined;
31
+ allowedValues?: (string | number | boolean | null)[] | undefined;
32
+ }[];
33
+ charts: {
34
+ type: "bar" | "line" | "area" | "pie" | "scatter";
35
+ title: string;
36
+ id: string;
37
+ datasetId: string;
38
+ x: string;
39
+ y: string;
40
+ color?: string | undefined;
41
+ }[];
42
+ tables: {
43
+ title: string;
44
+ id: string;
45
+ datasetId: string;
46
+ fields: string[];
47
+ }[];
48
+ sourceMetadata?: {
49
+ bindingId: string;
50
+ title: string;
51
+ description?: string | undefined;
52
+ }[] | undefined;
53
+ components?: {
54
+ title: string;
55
+ id: string;
56
+ elementId: string;
57
+ kind: "filter" | "metric" | "chart" | "table" | "text";
58
+ sources: {
59
+ bindingId: string;
60
+ fieldIds?: string[] | undefined;
61
+ }[];
62
+ }[] | undefined;
63
+ presentation?: {
64
+ type: "html";
65
+ html: string;
66
+ dependencies?: import("@xpert-ai/plugin-sdk/artifact-document").ArtifactDocumentDependencyLock[] | undefined;
67
+ } | undefined;
68
+ };
69
+ dependencies: ArtifactDocumentDependencyAsset[];
70
+ };
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_DASHBOARD_ARCHIVE_BYTES = void 0;
4
+ exports.dashboardRequirements = dashboardRequirements;
5
+ exports.encodeDashboardArchive = encodeDashboardArchive;
6
+ exports.decodeDashboardArchive = decodeDashboardArchive;
7
+ const node_crypto_1 = require("node:crypto");
8
+ const fflate_1 = require("fflate");
9
+ const v3_1 = require("zod/v3");
10
+ const parse5_1 = require("parse5");
11
+ const document_package_1 = require("./document-package");
12
+ exports.MAX_DASHBOARD_ARCHIVE_BYTES = 12_000_000;
13
+ const entryNames = ['dashboard.json', 'dashboard.html', 'requirements.json'];
14
+ const archiveIndex = v3_1.z.object({
15
+ schema: v3_1.z.literal('xpert.live-dashboard.archive'), version: v3_1.z.literal(1),
16
+ files: v3_1.z.array(v3_1.z.object({ name: v3_1.z.enum(entryNames), bytes: v3_1.z.number().int().nonnegative().max(document_package_1.MAX_DASHBOARD_PACKAGE_BYTES), sha256: v3_1.z.string().regex(/^[a-f0-9]{64}$/) }).strict()).min(2).max(3)
17
+ }).strict();
18
+ const hash = (value) => (0, node_crypto_1.createHash)('sha256').update(value).digest('hex');
19
+ function dashboardRequirements(manifest) {
20
+ const externalResources = new Set();
21
+ const visit = (node) => {
22
+ if ('attrs' in node)
23
+ for (const attr of node.attrs) {
24
+ if (['src', 'href'].includes(attr.name) && /^(https?:)?\/\//i.test(attr.value))
25
+ externalResources.add(attr.value);
26
+ }
27
+ if ('childNodes' in node)
28
+ node.childNodes.forEach(visit);
29
+ };
30
+ if (manifest.presentation)
31
+ visit((0, parse5_1.parse)(manifest.presentation.html));
32
+ return { schema: 'xpert.live-dashboard.requirements', version: 1,
33
+ bindings: manifest.bindings.map(({ id, providerId, operation, contractVersion, source }) => ({ id, providerId, operation, contractVersion, source })),
34
+ externalResources: [...externalResources], dynamicResourcesMayExist: !!manifest.presentation,
35
+ includesQueryResults: false, requiresAuthorizedHost: true };
36
+ }
37
+ function encodeDashboardArchive(manifest, dependencies) {
38
+ const entries = {
39
+ 'dashboard.json': (0, document_package_1.encodeDashboardPackage)(manifest, dependencies),
40
+ 'requirements.json': Buffer.from(JSON.stringify(dashboardRequirements(manifest), null, 2))
41
+ };
42
+ if (entries['dashboard.json'].length > document_package_1.MAX_DASHBOARD_PACKAGE_BYTES)
43
+ throw new Error('DASHBOARD_PACKAGE_TOO_LARGE');
44
+ if (manifest.presentation)
45
+ entries['dashboard.html'] = Buffer.from(manifest.presentation.html);
46
+ const index = { schema: 'xpert.live-dashboard.archive', version: 1,
47
+ files: Object.entries(entries).map(([name, bytes]) => ({ name, bytes: bytes.length, sha256: hash(bytes) })) };
48
+ entries['package.json'] = Buffer.from(JSON.stringify(index, null, 2));
49
+ const bytes = Buffer.from((0, fflate_1.zipSync)(entries, { level: 6 }));
50
+ if (bytes.length > exports.MAX_DASHBOARD_ARCHIVE_BYTES)
51
+ throw new Error('DASHBOARD_ARCHIVE_TOO_LARGE');
52
+ return bytes;
53
+ }
54
+ /** Never extracts paths to disk. Bound both advertised and actually inflated bytes. */
55
+ function unzipBounded(bytes) {
56
+ const files = new Map(), seen = new Set();
57
+ let total = 0, active = 0, failed;
58
+ const unzip = new fflate_1.Unzip(file => {
59
+ if (seen.has(file.name) || !['package.json', ...entryNames].includes(file.name) || (file.originalSize ?? 0) > document_package_1.MAX_DASHBOARD_PACKAGE_BYTES)
60
+ throw new Error('DASHBOARD_ARCHIVE_ENTRY_INVALID');
61
+ seen.add(file.name);
62
+ active++;
63
+ const chunks = [];
64
+ let size = 0;
65
+ file.ondata = (error, data, final) => {
66
+ if (error) {
67
+ failed = error;
68
+ file.terminate();
69
+ return;
70
+ }
71
+ size += data.length;
72
+ total += data.length;
73
+ if (size > document_package_1.MAX_DASHBOARD_PACKAGE_BYTES || total > exports.MAX_DASHBOARD_ARCHIVE_BYTES) {
74
+ failed = new Error('DASHBOARD_ARCHIVE_TOO_LARGE');
75
+ file.terminate();
76
+ return;
77
+ }
78
+ chunks.push(data);
79
+ if (final) {
80
+ files.set(file.name, Buffer.concat(chunks));
81
+ active--;
82
+ }
83
+ };
84
+ file.start();
85
+ });
86
+ unzip.register(fflate_1.UnzipInflate);
87
+ for (let offset = 0; offset < bytes.length; offset += 1024) {
88
+ unzip.push(bytes.subarray(offset, offset + 1024), offset + 1024 >= bytes.length);
89
+ if (failed)
90
+ throw failed;
91
+ }
92
+ if (active || !files.has('package.json') || !files.has('dashboard.json') || !files.has('requirements.json'))
93
+ throw new Error('DASHBOARD_ARCHIVE_INCOMPLETE');
94
+ return files;
95
+ }
96
+ function decodeDashboardArchive(bytes) {
97
+ if (bytes.length > exports.MAX_DASHBOARD_ARCHIVE_BYTES)
98
+ throw new Error('DASHBOARD_ARCHIVE_TOO_LARGE');
99
+ if (bytes[0] !== 0x50 || bytes[1] !== 0x4b)
100
+ return (0, document_package_1.decodeDashboardPackage)(bytes);
101
+ const files = unzipBounded(bytes);
102
+ const index = archiveIndex.parse(JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(files.get('package.json'))));
103
+ if (new Set(index.files.map(entry => entry.name)).size !== index.files.length || files.size !== index.files.length + 1)
104
+ throw new Error('DASHBOARD_ARCHIVE_ENTRY_INVALID');
105
+ for (const entry of index.files) {
106
+ const file = files.get(entry.name);
107
+ if (!file || file.length !== entry.bytes || hash(file) !== entry.sha256)
108
+ throw new Error('DASHBOARD_ARCHIVE_INTEGRITY_FAILED');
109
+ }
110
+ if (!index.files.some(entry => entry.name === 'dashboard.json') || !index.files.some(entry => entry.name === 'requirements.json'))
111
+ throw new Error('DASHBOARD_ARCHIVE_INCOMPLETE');
112
+ const result = (0, document_package_1.decodeDashboardPackage)(files.get('dashboard.json'));
113
+ if (result.manifest.presentation ? !files.get('dashboard.html')?.equals(Buffer.from(result.manifest.presentation.html)) : files.has('dashboard.html'))
114
+ throw new Error('DASHBOARD_ARCHIVE_HTML_MISMATCH');
115
+ if (!files.get('requirements.json')?.equals(Buffer.from(JSON.stringify(dashboardRequirements(result.manifest), null, 2))))
116
+ throw new Error('DASHBOARD_ARCHIVE_REQUIREMENTS_MISMATCH');
117
+ return result;
118
+ }
@@ -5,7 +5,7 @@ export declare function validateControls(manifest: DashboardManifest, input: Rec
5
5
  [x: string]: AnalyticsValue;
6
6
  };
7
7
  export declare function queryDashboardBinding(runtime: RuntimeCapabilityRegistry, actor: DataSourceActor, manifest: DashboardManifest, bindingId: string, input: Record<string, AnalyticsValue>): Promise<AnalyticalDataset>;
8
- export declare function queryDashboard(runtime: RuntimeCapabilityRegistry, actor: DataSourceActor, manifest: DashboardManifest, input: Record<string, AnalyticsValue>): Promise<AnalyticalDataset[]>;
8
+ export declare function queryDashboard(runtime: RuntimeCapabilityRegistry, actor: DataSourceActor, manifest: DashboardManifest, input: Record<string, AnalyticsValue>, progress?: (completed: number) => Promise<void>): Promise<AnalyticalDataset[]>;
9
9
  /** Read-only rendering can show independent successful bindings. Saving/publishing still
10
10
  * uses queryDashboard and requires every binding to validate. Never return native error
11
11
  * details, which may contain private source configuration or query text. */
@@ -4,6 +4,7 @@ exports.validateControls = validateControls;
4
4
  exports.queryDashboardBinding = queryDashboardBinding;
5
5
  exports.queryDashboard = queryDashboard;
6
6
  exports.queryDashboardPartial = queryDashboardPartial;
7
+ const bounded_map_1 = require("./bounded-map");
7
8
  const common_1 = require("@nestjs/common");
8
9
  const plugin_sdk_1 = require("@xpert-ai/plugin-sdk");
9
10
  function validateControls(manifest, input) {
@@ -29,11 +30,15 @@ async function queryDashboardBinding(runtime, actor, manifest, bindingId, input)
29
30
  const selected = Object.fromEntries(Object.entries(controls).filter(([id]) => binding.parameters.some(parameter => parameter.controlId === id)));
30
31
  return runtime.require(plugin_sdk_1.AnalyticsRuntimeCapability).query(actor, { binding, controls: selected });
31
32
  }
32
- async function queryDashboard(runtime, actor, manifest, input) {
33
+ async function queryDashboard(runtime, actor, manifest, input, progress) {
33
34
  validateControls(manifest, input);
34
- const datasets = [];
35
- for (const binding of manifest.bindings)
36
- datasets.push(await queryDashboardBinding(runtime, actor, manifest, binding.id, input));
35
+ let completed = 0;
36
+ const datasets = await (0, bounded_map_1.boundedMap)(manifest.bindings, async (binding) => {
37
+ await progress?.(completed);
38
+ const result = await queryDashboardBinding(runtime, actor, manifest, binding.id, input);
39
+ await progress?.(++completed);
40
+ return result;
41
+ });
37
42
  if (Buffer.byteLength(JSON.stringify(datasets)) > 3_000_000)
38
43
  throw new common_1.BadRequestException('DASHBOARD_RESULT_TOO_LARGE');
39
44
  return datasets;
@@ -45,14 +50,14 @@ async function queryDashboardPartial(runtime, actor, manifest, input) {
45
50
  validateControls(manifest, input);
46
51
  const datasets = [];
47
52
  const failures = [];
48
- for (const binding of manifest.bindings) {
53
+ await (0, bounded_map_1.boundedMap)(manifest.bindings, async (binding) => {
49
54
  try {
50
55
  datasets.push(await queryDashboardBinding(runtime, actor, manifest, binding.id, input));
51
56
  }
52
57
  catch {
53
58
  failures.push({ bindingId: binding.id, datasetId: binding.output.datasetId, reason: 'unavailable' });
54
59
  }
55
- }
60
+ });
56
61
  if (Buffer.byteLength(JSON.stringify(datasets)) > 3_000_000)
57
62
  throw new common_1.BadRequestException('DASHBOARD_RESULT_TOO_LARGE');
58
63
  return { datasets, failures };
@@ -4,7 +4,7 @@ exports.DASHBOARD_MUTATION_TOOLS = void 0;
4
4
  exports.createdDashboard = createdDashboard;
5
5
  exports.savedDashboardSource = savedDashboardSource;
6
6
  const v3_1 = require("zod/v3");
7
- exports.DASHBOARD_MUTATION_TOOLS = ['dashboard_create', 'dashboard_create_html', 'dashboard_update'];
7
+ exports.DASHBOARD_MUTATION_TOOLS = ['dashboard_create', 'dashboard_create_html', 'dashboard_update', 'dashboard_wait_request', 'dashboard_get_request'];
8
8
  const receiptSchema = v3_1.z.object({ dashboard: v3_1.z.object({ artifactId: v3_1.z.string().uuid(), draftRevision: v3_1.z.number().int().positive(), status: v3_1.z.literal('draft') }) });
9
9
  /** Tool start/failed logs can share the host event channel; only a saved receipt opens a View. */
10
10
  function createdDashboard(output) {