@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.
- package/.xpertai-plugin/plugin.json +1 -1
- package/README.md +8 -2
- package/dist/index.js +3 -1
- package/dist/lib/agent.schema.d.ts +177 -23
- package/dist/lib/agent.schema.js +11 -9
- package/dist/lib/archive.service.d.ts +169 -0
- package/dist/lib/archive.service.js +147 -0
- package/dist/lib/bounded-map.d.ts +2 -0
- package/dist/lib/bounded-map.js +25 -0
- package/dist/lib/capacity.d.ts +14 -0
- package/dist/lib/capacity.js +16 -0
- package/dist/lib/content.provider.d.ts +1 -1
- package/dist/lib/dashboard-archive.d.ts +70 -0
- package/dist/lib/dashboard-archive.js +118 -0
- package/dist/lib/dashboard-queries.d.ts +1 -1
- package/dist/lib/dashboard-queries.js +11 -6
- package/dist/lib/dashboard-receipt.js +1 -1
- package/dist/lib/dashboard-sources.d.ts +69 -0
- package/dist/lib/dashboard-sources.js +38 -0
- package/dist/lib/dashboard-view.schema.d.ts +422 -88
- package/dist/lib/dashboard.schema.d.ts +379 -58
- package/dist/lib/dashboard.schema.js +20 -2
- package/dist/lib/dashboard.service.d.ts +80 -15
- package/dist/lib/dashboard.service.js +65 -9
- package/dist/lib/definition-tools.d.ts +4 -4
- package/dist/lib/document-host.js +1 -1
- package/dist/lib/document-package.d.ts +19 -4
- package/dist/lib/editing-patch.d.ts +3 -0
- package/dist/lib/editing-patch.js +4 -2
- package/dist/lib/editing-session.d.ts +4 -4
- package/dist/lib/editing.d.ts +204 -20
- package/dist/lib/editing.js +10 -3
- package/dist/lib/filter-evidence.d.ts +28 -0
- package/dist/lib/filter-evidence.js +3 -2
- package/dist/lib/html-guidance.d.ts +1 -0
- package/dist/lib/html-guidance.js +2 -1
- package/dist/lib/html-validation-feedback.d.ts +7 -0
- package/dist/lib/html-validation-feedback.js +19 -0
- package/dist/lib/html-validation.js +3 -2
- package/dist/lib/inspection-host.d.ts +7 -0
- package/dist/lib/inspection-host.js +48 -0
- package/dist/lib/inspection-runtime.d.ts +2 -0
- package/dist/lib/inspection-runtime.js +77 -0
- package/dist/lib/json-input.d.ts +4 -0
- package/dist/lib/json-input.js +21 -0
- package/dist/lib/middleware.d.ts +3 -1
- package/dist/lib/middleware.js +92 -13
- package/dist/lib/plugin.js +3 -1
- package/dist/lib/presentation.provider.d.ts +24 -9
- package/dist/lib/presentation.provider.js +2 -1
- package/dist/lib/preview-retry.d.ts +4 -3
- package/dist/lib/preview-retry.js +13 -7
- package/dist/lib/prompt-workflows.js +1 -1
- package/dist/lib/query-session.d.ts +9 -0
- package/dist/lib/query-session.js +64 -0
- package/dist/lib/save-requests.d.ts +118 -0
- package/dist/lib/save-requests.js +199 -0
- package/dist/lib/source-executions.d.ts +437 -0
- package/dist/lib/source-executions.js +49 -0
- package/dist/lib/source-validation.d.ts +2 -0
- package/dist/lib/source-validation.js +21 -0
- package/dist/lib/view.provider.d.ts +213 -8
- package/dist/lib/view.provider.js +47 -5
- package/dist/remote/app.css +1 -1
- package/dist/remote/app.js +185 -63
- package/dist/remote/build-manifest.json +52 -33
- package/dist/sandbox-actions/action.json +1 -1
- package/dist/sandbox-actions/bundle/host.js +63 -14
- package/dist/sandbox-actions/bundle/validate.mjs +2 -2
- package/dist/validation/host-entry.d.ts +5 -1
- package/dist/validation/host-entry.js +21 -4
- package/docs/docs.json +5 -1
- package/docs/large-dashboards-acceptance.mdx +74 -0
- package/docs/large-dashboards.mdx +80 -0
- package/docs/sources-inspect-portability-acceptance.mdx +67 -0
- package/docs/sources-inspect-portability.mdx +115 -0
- package/package.json +8 -7
- package/skills/live-dashboard/SKILL.md +5 -4
- 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(
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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(
|
|
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. */
|