@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.
- package/.xpertai-plugin/plugin.json +1 -1
- package/README.md +77 -4
- package/dist/lib/content-capability.d.ts +22 -0
- package/dist/lib/content-capability.js +33 -0
- package/dist/lib/content.provider.d.ts +23 -0
- package/dist/lib/content.provider.js +163 -0
- package/dist/lib/dashboard-navigation.d.ts +24 -0
- package/dist/lib/dashboard-navigation.js +1 -1
- package/dist/lib/dashboard-queries.d.ts +11 -0
- package/dist/lib/dashboard-queries.js +20 -0
- package/dist/lib/dashboard-search.d.ts +5 -0
- package/dist/lib/dashboard-search.js +12 -0
- package/dist/lib/dashboard-view.schema.d.ts +27 -0
- package/dist/lib/dashboard-view.schema.js +1 -1
- package/dist/lib/dashboard.schema.js +4 -2
- package/dist/lib/dashboard.service.d.ts +13 -0
- package/dist/lib/dashboard.service.js +69 -16
- package/dist/lib/document-host.d.ts +4 -0
- package/dist/lib/document-host.js +13 -0
- package/dist/lib/document-network.d.ts +3 -0
- package/dist/lib/document-network.js +9 -0
- package/dist/lib/html-guidance.d.ts +1 -1
- package/dist/lib/html-guidance.js +1 -1
- package/dist/lib/middleware.js +49 -15
- package/dist/lib/plugin.js +3 -1
- package/dist/lib/presentation.provider.d.ts +5 -0
- package/dist/lib/presentation.provider.js +2 -2
- package/dist/lib/preview-retry.d.ts +7 -0
- package/dist/lib/preview-retry.js +52 -0
- package/dist/lib/prompt-workflows.d.ts +3 -0
- package/dist/lib/prompt-workflows.js +118 -0
- package/dist/lib/renderer.js +5 -0
- package/dist/lib/save-evidence.d.ts +1 -1
- package/dist/lib/sharing.service.js +2 -0
- package/dist/lib/view.provider.d.ts +2 -117
- package/dist/lib/view.provider.js +12 -4
- package/dist/remote/app.css +1 -1
- package/dist/remote/app.js +36 -36
- package/dist/remote/build-manifest.json +38 -28
- package/dist/sandbox-actions/action.json +1 -1
- package/dist/sandbox-actions/bundle/host.js +2 -2
- package/dist/sandbox-actions/bundle/resource-cache.mjs +39 -0
- package/dist/sandbox-actions/bundle/validate.mjs +106 -17
- package/dist/validation/host-entry.d.ts +3 -1
- package/dist/validation/host-entry.js +5 -2
- package/docs/drafts.mdx +1 -1
- package/docs/frontend-libraries.mdx +2 -2
- package/docs/html-mode.md +3 -3
- package/docs/team-sharing.mdx +1 -1
- package/docs/verification.mdx +3 -1
- package/package.json +6 -6
- package/skills/live-dashboard/SKILL.md +1 -1
- package/skills/live-dashboard/references/data-validation.md +4 -0
- package/skills/live-dashboard/references/html-runtime.md +1 -1
package/dist/lib/middleware.js
CHANGED
|
@@ -20,6 +20,7 @@ const v3_1 = require("zod/v3");
|
|
|
20
20
|
const plugin_sdk_1 = require("@xpert-ai/plugin-sdk");
|
|
21
21
|
const artifact_document_1 = require("@xpert-ai/plugin-sdk/artifact-document");
|
|
22
22
|
const document_libraries_1 = require("./document-libraries");
|
|
23
|
+
const document_network_1 = require("./document-network");
|
|
23
24
|
const agent_schema_1 = require("./agent.schema");
|
|
24
25
|
const html_guidance_1 = require("./html-guidance");
|
|
25
26
|
const dashboard_service_1 = require("./dashboard.service");
|
|
@@ -31,6 +32,8 @@ const filter_evidence_1 = require("./filter-evidence");
|
|
|
31
32
|
const save_evidence_1 = require("./save-evidence");
|
|
32
33
|
const resource_card_1 = require("./resource-card");
|
|
33
34
|
const dashboard_icon_1 = require("./dashboard-icon");
|
|
35
|
+
const preview_retry_1 = require("./preview-retry");
|
|
36
|
+
const prompt_workflows_1 = require("./prompt-workflows");
|
|
34
37
|
const middlewareOptionsSchema = v3_1.z.object({ allowedFinalProviders: v3_1.z.array(v3_1.z.string().min(1)).min(1).max(20).optional() }).strict().default({});
|
|
35
38
|
exports.DASHBOARD_GUIDANCE = `For a new independent dashboard, use a new unused HTML file path. An existing filename or matching title is not authorization to edit another Artifact; do not switch from creation to dashboard_open/update because a file already exists. For a new dashboard, call dashboard_list_sources before searching raw files, reading old scripts or creating transformations. Named governed sources may already provide the required query; inspect their provider contracts first. File-backed sources do not imply that a script must be written. For creating or editing live dashboards, first read the installed live-dashboard skill using read_skill_file and follow only the references relevant to the task. Use SandboxFile tools for all source-file authoring; HTML alone is not a saved dashboard. Follow preview.validation.checks immediately before authoring HTML; each suggestedCall must actually succeed. Business definitions, real data previews and nullable-filter tests are required before saving new or changed Bindings. Do not invent business data or substitute a different metric without agreement. Report completion only from a successful dashboard_create/dashboard_create_html/dashboard_update draft receipt. Human publication is separate. Successful create, update and open tools emit a chat resource card automatically; resourceCard.status reports emission. Its Open button selects the exact dashboard in Workbench. Never fabricate a link, call present_files for dashboard HTML, or output card JSON as text. If card delivery is unavailable, report the saved receipt and use the Workbench picker.`;
|
|
36
39
|
let DashboardMiddleware = class DashboardMiddleware {
|
|
@@ -39,6 +42,7 @@ let DashboardMiddleware = class DashboardMiddleware {
|
|
|
39
42
|
this.meta = {
|
|
40
43
|
name: 'LiveDashboard', label: { en_US: 'Live dashboards', zh_Hans: '实时看板' },
|
|
41
44
|
description: { en_US: 'Discover, preview, create and update governed live dashboards.', zh_Hans: '发现业务数据、验证查询并持续修改实时看板。' },
|
|
45
|
+
slashCommands: prompt_workflows_1.dashboardPromptWorkflows,
|
|
42
46
|
// Tools inherit this self-contained icon through the platform middleware metadata.
|
|
43
47
|
icon: dashboard_icon_1.dashboardIcon, features: [view_provider_1.FEATURE], configSchema: { type: 'object', properties: {
|
|
44
48
|
allowedFinalProviders: { type: 'array', items: { type: 'string' }, title: { en_US: 'Allowed final dashboard providers', zh_Hans: '最终看板允许的 Provider' },
|
|
@@ -56,13 +60,37 @@ let DashboardMiddleware = class DashboardMiddleware {
|
|
|
56
60
|
};
|
|
57
61
|
// Evidence belongs to this run and actor, never a process-wide cache of business data.
|
|
58
62
|
const discovered = new Map(), previews = new Map();
|
|
63
|
+
const discovering = new Map();
|
|
59
64
|
let listedSources;
|
|
65
|
+
const assertSourceSelection = (input) => {
|
|
66
|
+
if (listedSources && !listedSources.some(source => (0, analytics_source_1.sameAnalyticsResource)(source.source, input.source) && source.providers.some(provider => provider.id === input.providerId)))
|
|
67
|
+
throw new Error(`DASHBOARD_SOURCE_SELECTION_INVALID: copy an exact source/providerId pair from dashboard_list_sources; do not invent or combine IDs. Known pairs: ${JSON.stringify(listedSources.map(source => ({ source: source.source, name: source.name, providerIds: source.providers.map(provider => provider.id) })))}. Refresh the list if the source changed. Initial requestJson is "{}"; then follow inspectRequest/nextRequest.`);
|
|
68
|
+
};
|
|
69
|
+
const discover = async (input) => {
|
|
70
|
+
assertSourceSelection(input);
|
|
71
|
+
const result = await this.service.discover(scope, input);
|
|
72
|
+
discovered.set(`${(0, analytics_1.analyticsSourceKey)(input.source)}/${input.providerId}`, result.queryContract);
|
|
73
|
+
return result;
|
|
74
|
+
};
|
|
75
|
+
const ensureContract = async (input) => {
|
|
76
|
+
assertSourceSelection(input);
|
|
77
|
+
const key = `${(0, analytics_1.analyticsSourceKey)(input.source)}/${input.providerId}`;
|
|
78
|
+
const cached = discovered.get(key);
|
|
79
|
+
if (cached)
|
|
80
|
+
return cached;
|
|
81
|
+
// Parallel previews share discovery, but failures are retryable and never become evidence.
|
|
82
|
+
let pending = discovering.get(key);
|
|
83
|
+
if (!pending) {
|
|
84
|
+
pending = discover({ source: input.source, providerId: input.providerId, request: {} })
|
|
85
|
+
.then(result => result.queryContract).finally(() => discovering.delete(key));
|
|
86
|
+
discovering.set(key, pending);
|
|
87
|
+
}
|
|
88
|
+
return pending;
|
|
89
|
+
};
|
|
60
90
|
const filterEvidence = (0, filter_evidence_1.createFilterEvidence)(this.service, scope, previews);
|
|
61
91
|
const created = new Map();
|
|
62
92
|
const preview = async (binding) => {
|
|
63
|
-
const contract =
|
|
64
|
-
if (!contract)
|
|
65
|
-
throw new Error('DASHBOARD_DISCOVER_REQUIRED');
|
|
93
|
+
const contract = await ensureContract(binding);
|
|
66
94
|
(0, plugin_sdk_1.validateAnalyticsPayload)(binding.payload, contract.payloadSchema);
|
|
67
95
|
const controls = Object.fromEntries(binding.parameters.map((parameter) => [parameter.controlId, parameter.defaultValue ?? null]));
|
|
68
96
|
const dataset = await this.service.preview(scope, { binding, controls });
|
|
@@ -124,6 +152,7 @@ let DashboardMiddleware = class DashboardMiddleware {
|
|
|
124
152
|
throw new Error('DASHBOARD_WORKSPACE_REQUIRED');
|
|
125
153
|
return files;
|
|
126
154
|
});
|
|
155
|
+
const previewRetries = (0, preview_retry_1.createPreviewRetryGuard)();
|
|
127
156
|
let modelCalls = 0;
|
|
128
157
|
return { name: 'LiveDashboard', contextSchema: editing_1.dashboardContextSchema,
|
|
129
158
|
wrapToolCall: async (request, handler) => {
|
|
@@ -134,11 +163,20 @@ let DashboardMiddleware = class DashboardMiddleware {
|
|
|
134
163
|
: name === 'dashboard_create' ? agent_schema_1.createToolSchema.safeParse(args) : undefined;
|
|
135
164
|
const save = dashboard_receipt_1.DASHBOARD_MUTATION_TOOLS.includes(name) ? evidence.beginSave(creation?.success ? creation.data.supersedesPaths : undefined, name === 'dashboard_create' ? 'draft' : 'source') : undefined;
|
|
136
165
|
const result = path && (evidence.has(path) || previews.size > 0 && agent_schema_1.htmlPathSchema.safeParse(path).success)
|
|
137
|
-
? await evidence.trackWrite(path, async () => handler(request)) : await
|
|
166
|
+
? await evidence.trackWrite(path, async () => handler(request)) : await previewRetries.run(request, handler);
|
|
138
167
|
if (save && 'content' in result && (!('status' in result) || result.status !== 'error'))
|
|
139
168
|
await evidence.accept(save, result.content);
|
|
140
169
|
return result;
|
|
141
170
|
}, wrapModelCall: async (request, handler) => {
|
|
171
|
+
if (previewRetries.exhausted) {
|
|
172
|
+
const summary = previewRetries.summary + (evidence.pending ? ` Unsaved Workspace files: ${JSON.stringify(evidence.pendingPaths)}.` : '');
|
|
173
|
+
const result = await handler({ ...request, tools: [], toolChoice: 'none', systemMessage: new messages_1.SystemMessage({ content: [
|
|
174
|
+
...(typeof request.systemMessage?.content === 'string' ? [{ type: 'text', text: request.systemMessage.content }] : request.systemMessage?.content ?? []),
|
|
175
|
+
{ type: 'text', text: summary }
|
|
176
|
+
] }) });
|
|
177
|
+
// A noncompliant model must not restart the failed loop even after tools were removed.
|
|
178
|
+
return result?.tool_calls?.length ? new messages_1.AIMessage(summary) : result;
|
|
179
|
+
}
|
|
142
180
|
const exhausted = ++modelCalls > 24;
|
|
143
181
|
if (exhausted && evidence.pending)
|
|
144
182
|
throw new Error('DASHBOARD_SAVE_NOT_CONFIRMED: no successful draft save receipt; Workspace changes have not been saved to the dashboard.');
|
|
@@ -153,6 +191,8 @@ let DashboardMiddleware = class DashboardMiddleware {
|
|
|
153
191
|
filterEvidence.tool,
|
|
154
192
|
...(0, definition_tools_1.createDefinitionTools)(context, () => { listedSources = undefined; }),
|
|
155
193
|
(0, tools_1.tool)(async () => JSON.stringify({ libraries: await (0, document_libraries_1.libraryLocks)(['echarts', 'three']),
|
|
194
|
+
online: { ...document_network_1.DASHBOARD_NETWORK_POLICY,
|
|
195
|
+
usage: 'HTML may load scripts, stylesheets and fonts directly from these exact HTTPS origins. Use script src and link rel="stylesheet"; pin exact versions where available. Tailwind CDN https://cdn.tailwindcss.com is supported. Online resources require connectivity when saving and viewing; loading failures fail preflight. Keep all business data queries on window.xpertLiveArtifact; fetch/XHR, external images, workers, iframes and eval remain unavailable. Online resources do not belong in the packaged libraries array.' },
|
|
156
196
|
pageRuntime: {
|
|
157
197
|
global: 'window.xpertLiveArtifact',
|
|
158
198
|
bindingIds: [...new Set([...previews.values()].map(binding => binding.id))],
|
|
@@ -162,7 +202,7 @@ let DashboardMiddleware = class DashboardMiddleware {
|
|
|
162
202
|
},
|
|
163
203
|
usage: { echarts: 'window.echarts: init(element, null, {renderer:"svg"}) then setOption(completeOption, {notMerge:true}). Canvas is also available. Derive active series/legend dimensions from each received Dataset, including filtered results; never retain excluded groups as zero-valued series. Aggregate repeated categories using all grain dimensions, clear empty results, and redraw KPI, chart and details together in subscribe. Use ResizeObserver to resize and reuse the instance on filter/refresh.',
|
|
164
204
|
three: 'window.THREE: bundled core including WebGLRenderer. Requires WebGL2; catch unavailable GPU contexts and show a clear fallback. External textures/models, loaders/addons, WebGPU and workers are not bundled.' },
|
|
165
|
-
lifecycle: 'Pass
|
|
205
|
+
lifecycle: 'Pass packaged library IDs to dashboard_create_html or dashboard_update. Actual version and bytes are pinned on save; omitted libraries on update preserves saved locks. Online resources are listed separately under online, are not pinned in the Artifact, and must load successfully when saving and viewing.' }), { name: 'dashboard_libraries', description: 'Discover packaged frontend libraries, exact versions and allowed HTTPS origins for online scripts, stylesheets and fonts. Read before using Tailwind CDN or ECharts/Three.js. Returns metadata, never library source.',
|
|
166
206
|
schema: v3_1.z.object({}).strict(), verboseParsingErrors: true, metadata: { toolName: { en_US: 'Discover dashboard libraries', zh_Hans: '查看看板前端图库' } } }),
|
|
167
207
|
...(0, editing_1.createEditingTools)(this.service, scope, context, resolvePreviews, validateFinal, (path, artifactId) => evidence.opened(path, artifactId)),
|
|
168
208
|
(0, tools_1.tool)(async () => {
|
|
@@ -173,24 +213,18 @@ let DashboardMiddleware = class DashboardMiddleware {
|
|
|
173
213
|
schema: v3_1.z.object({}).strict(), verboseParsingErrors: true, metadata: { toolName: { en_US: 'List analytics sources', zh_Hans: '发现分析数据源' } }
|
|
174
214
|
}),
|
|
175
215
|
(0, tools_1.tool)(async (input) => {
|
|
176
|
-
if (listedSources && !listedSources.some(source => (0, analytics_source_1.sameAnalyticsResource)(source.source, input.source) && source.providers.some(provider => provider.id === input.providerId)))
|
|
177
|
-
throw new Error(`DASHBOARD_SOURCE_SELECTION_INVALID: copy an exact source/providerId pair from dashboard_list_sources; do not invent or combine IDs. Known pairs: ${JSON.stringify(listedSources.map(source => ({ source: source.source, name: source.name, providerIds: source.providers.map(provider => provider.id) })))}. Refresh the list if the source changed. Initial requestJson is "{}"; then follow inspectRequest/nextRequest.`);
|
|
178
216
|
const parsed = { source: input.source, providerId: input.providerId, request: (0, agent_schema_1.parseObjectJson)(input.requestJson) };
|
|
179
|
-
|
|
180
|
-
discovered.set(`${(0, analytics_1.analyticsSourceKey)(parsed.source)}/${parsed.providerId}`, result.queryContract);
|
|
181
|
-
return JSON.stringify(result);
|
|
217
|
+
return JSON.stringify(await discover(parsed));
|
|
182
218
|
}, { name: 'dashboard_discover', description: 'Discover provider-owned business metadata. Start requestJson="{}" then serialize inspectRequest/nextRequest. Inspect dataset fields, comments, measures and native query format before preview.',
|
|
183
219
|
schema: agent_schema_1.discoverToolSchema, verboseParsingErrors: true, metadata: { toolName: { en_US: 'Inspect business data', zh_Hans: '理解业务数据与指标' } } }),
|
|
184
220
|
(0, tools_1.tool)(async (input) => {
|
|
185
|
-
const contract =
|
|
186
|
-
if (!contract)
|
|
187
|
-
throw new Error('DASHBOARD_DISCOVER_REQUIRED: discover the source before preview');
|
|
221
|
+
const contract = await ensureContract(input.binding);
|
|
188
222
|
const binding = (0, agent_schema_1.fromAgentBinding)(input.binding, contract);
|
|
189
223
|
return preview(binding);
|
|
190
|
-
}, { name: 'dashboard_preview', description: 'Run one read-only binding using parameters[].defaultValue. Returns validated Dataset fields, first 20 rows, full query completeness and audit. Required before create; changes to binding/defaults require another preview.',
|
|
224
|
+
}, { name: 'dashboard_preview', description: 'Run one read-only binding using parameters[].defaultValue. Automatically discovers the source/provider query contract when absent in this run; use dashboard_discover to inspect business metadata or repair invalid queries. Returns validated Dataset fields, first 20 rows, full query completeness and audit. Required before create; changes to binding/defaults require another preview.',
|
|
191
225
|
schema: agent_schema_1.previewToolSchema, verboseParsingErrors: true, metadata: { toolName: { en_US: 'Verify dashboard data', zh_Hans: '预览并验证看板数据' } } }),
|
|
192
226
|
(0, tools_1.tool)(async ({ bindingJson }) => preview(plugin_sdk_1.analyticsBindingSchema.parse(JSON.parse(bindingJson))), {
|
|
193
|
-
name: 'dashboard_preview_binding', description: 'Preview a complete canonical AnalyticsBinding returned by another tool. Serialize the complete canonical Binding (for example discovery items[].metadata.binding), including parameters[].controlId. bindingPayload is only the payload, not a Binding. If adding a parameter, include name, controlId, type, required and defaultValue. Do not convert it to the simplified dashboard_preview input.
|
|
227
|
+
name: 'dashboard_preview_binding', description: 'Preview a complete canonical AnalyticsBinding returned by another tool. Serialize the complete canonical Binding (for example discovery items[].metadata.binding), including parameters[].controlId. bindingPayload is only the payload, not a Binding. If adding a parameter, include name, controlId, type, required and defaultValue. Do not convert it to the simplified dashboard_preview input. Automatically discovers the source/provider query contract when absent in this run; use dashboard_discover for business metadata. Returns the same previewId receipt for dashboard_create.',
|
|
194
228
|
schema: v3_1.z.object({ bindingJson: v3_1.z.string().min(2).max(200000) }).strict(), verboseParsingErrors: true,
|
|
195
229
|
metadata: { toolName: { en_US: 'Verify registered dataset', zh_Hans: '验证已注册数据集' } }
|
|
196
230
|
}),
|
package/dist/lib/plugin.js
CHANGED
|
@@ -7,6 +7,8 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
};
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.LiveDashboardPlugin = void 0;
|
|
10
|
+
const content_capability_1 = require("./content-capability");
|
|
11
|
+
const content_provider_1 = require("./content.provider");
|
|
10
12
|
const plugin_sdk_1 = require("@xpert-ai/plugin-sdk");
|
|
11
13
|
const dashboard_service_1 = require("./dashboard.service");
|
|
12
14
|
const view_provider_1 = require("./view.provider");
|
|
@@ -18,5 +20,5 @@ let LiveDashboardPlugin = class LiveDashboardPlugin {
|
|
|
18
20
|
};
|
|
19
21
|
exports.LiveDashboardPlugin = LiveDashboardPlugin;
|
|
20
22
|
exports.LiveDashboardPlugin = LiveDashboardPlugin = __decorate([
|
|
21
|
-
(0, plugin_sdk_1.XpertServerPlugin)({ providers: [dashboard_service_1.DashboardService, view_provider_1.DashboardViewProvider, middleware_1.DashboardMiddleware, html_validation_1.HtmlDashboardValidation, presentation_provider_1.DashboardPresentationProvider, sharing_service_1.DashboardSharingService] })
|
|
23
|
+
(0, plugin_sdk_1.XpertServerPlugin)({ providers: [content_capability_1.DashboardCapability, content_provider_1.DashboardContentProvider, content_provider_1.DashboardContentEditor, dashboard_service_1.DashboardService, view_provider_1.DashboardViewProvider, middleware_1.DashboardMiddleware, html_validation_1.HtmlDashboardValidation, presentation_provider_1.DashboardPresentationProvider, sharing_service_1.DashboardSharingService] })
|
|
22
24
|
], LiveDashboardPlugin);
|
|
@@ -117,6 +117,11 @@ export declare class DashboardPresentationProvider implements IArtifactPresentat
|
|
|
117
117
|
};
|
|
118
118
|
} | undefined;
|
|
119
119
|
datasets: import("@xpert-ai/contracts").AnalyticalDataset[];
|
|
120
|
+
failures: {
|
|
121
|
+
bindingId: string;
|
|
122
|
+
datasetId: string;
|
|
123
|
+
reason: "unavailable";
|
|
124
|
+
}[];
|
|
120
125
|
}>;
|
|
121
126
|
private open;
|
|
122
127
|
}
|
|
@@ -44,8 +44,8 @@ let DashboardPresentationProvider = class DashboardPresentationProvider {
|
|
|
44
44
|
return (0, dashboard_queries_1.queryDashboardBinding)(this.runtime, context.actor, saved.manifest, input.bindingId, input.controls);
|
|
45
45
|
if (request.operation !== 'query')
|
|
46
46
|
throw new common_1.ForbiddenException('DASHBOARD_ACTION_UNSUPPORTED');
|
|
47
|
-
const datasets = await (0, dashboard_queries_1.
|
|
48
|
-
return { datasets, ...(saved.manifest.presentation ? {} : { payload: (0, renderer_1.toDataAnalyticsPayload)(saved.manifest, datasets) }) };
|
|
47
|
+
const { datasets, failures } = await (0, dashboard_queries_1.queryDashboardPartial)(this.runtime, context.actor, saved.manifest, input.controls);
|
|
48
|
+
return { datasets, failures, ...(saved.manifest.presentation ? {} : { payload: (0, renderer_1.toDataAnalyticsPayload)(saved.manifest, datasets) }) };
|
|
49
49
|
}
|
|
50
50
|
open(context) {
|
|
51
51
|
const { artifact, version, content } = context;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AgentBuiltInState, ToolCallHandler, ToolCallRequest } from '@xpert-ai/plugin-sdk';
|
|
2
|
+
/** Run-local bounds include tool-schema failures, before any discovery or query executes. */
|
|
3
|
+
export declare function createPreviewRetryGuard(): {
|
|
4
|
+
readonly exhausted: boolean;
|
|
5
|
+
readonly summary: string;
|
|
6
|
+
run(request: ToolCallRequest<AgentBuiltInState>, handler: ToolCallHandler): Promise<import("@langchain/core/messages").ToolMessage | import("@langchain/langgraph").Command<unknown, Record<string, unknown>, string>>;
|
|
7
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createPreviewRetryGuard = createPreviewRetryGuard;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
const PREVIEW_TOOLS = new Set(['dashboard_preview', 'dashboard_preview_binding', 'dashboard_test_filter']);
|
|
6
|
+
/** Run-local bounds include tool-schema failures, before any discovery or query executes. */
|
|
7
|
+
function createPreviewRetryGuard() {
|
|
8
|
+
const failures = new Map(), pending = new Set();
|
|
9
|
+
let consecutiveFailures = 0, lastError = '';
|
|
10
|
+
const recordFailure = (key, message) => {
|
|
11
|
+
failures.set(key, { count: (failures.get(key)?.count ?? 0) + 1, message: message.slice(0, 2000) });
|
|
12
|
+
consecutiveFailures++;
|
|
13
|
+
lastError = message.slice(0, 2000);
|
|
14
|
+
};
|
|
15
|
+
return {
|
|
16
|
+
get exhausted() { return consecutiveFailures >= 6; },
|
|
17
|
+
get summary() { return `DASHBOARD_PREVIEW_RETRY_LIMIT: preview validation repeatedly failed. This run has stopped making tool calls. Report the last error and the required repair; do not claim an unconfirmed save. Last error: ${lastError}`; },
|
|
18
|
+
async run(request, handler) {
|
|
19
|
+
if (!PREVIEW_TOOLS.has(request.toolCall.name))
|
|
20
|
+
return handler(request);
|
|
21
|
+
const key = (0, node_crypto_1.createHash)('sha256').update(JSON.stringify({ name: request.toolCall.name, args: request.toolCall.args }, (_key, value) => value && typeof value === 'object' && !Array.isArray(value) ? Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b))) : value)).digest('hex');
|
|
22
|
+
if (consecutiveFailures >= 6)
|
|
23
|
+
throw new Error(this.summary);
|
|
24
|
+
if ((failures.get(key)?.count ?? 0) >= 2) {
|
|
25
|
+
consecutiveFailures++;
|
|
26
|
+
throw new Error(`DASHBOARD_PREVIEW_REPEAT_BLOCKED: this exact tool input already failed twice; no query was executed. Correct the input using the previous diagnostic, or stop and report the blocker. Last error: ${failures.get(key).message}`);
|
|
27
|
+
}
|
|
28
|
+
// Concurrent duplicate calls must not multiply queries or bypass the per-input bound.
|
|
29
|
+
if (pending.has(key))
|
|
30
|
+
throw new Error('DASHBOARD_PREVIEW_IN_PROGRESS: wait for the identical preview already in progress; do not submit duplicates.');
|
|
31
|
+
pending.add(key);
|
|
32
|
+
try {
|
|
33
|
+
const result = await handler(request);
|
|
34
|
+
if ('status' in result && result.status === 'error' && 'content' in result) {
|
|
35
|
+
recordFailure(key, typeof result.content === 'string' ? result.content : 'Preview tool returned an error');
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
failures.delete(key);
|
|
39
|
+
consecutiveFailures = 0;
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
recordFailure(key, error instanceof Error ? error.message : 'Preview tool failed');
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
pending.delete(key);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.dashboardPromptWorkflows = void 0;
|
|
4
|
+
const dashboard_icon_1 = require("./dashboard-icon");
|
|
5
|
+
const context = `Work in the current authorized Assistant/project scope. Use the user's language for the page and response. Read the installed live-dashboard skill through read_skill_file and follow the references relevant to this task. Respect provider policy, access controls and tool schemas. Never change deployment, credentials, Assistant configuration or business-source data.
|
|
6
|
+
Resolve source/provider pairs, Artifact IDs, fields and file paths from current tools, not names or previous conversation claims. Use only real query results; no invented business data, targets, thresholds, comparison periods or activity feeds. Keep queries read-only and bounded. Report actual tool failures and diagnosticId when supplied. A missing capability or runtime binding is a blocker, not permission to bypass validation or switch presentation modes.`;
|
|
7
|
+
const metrics = `Confirm metric definitions, units, time range, grain and join keys from metadata and small real samples. Compute ratios from aggregated numerators and denominators, not sums/averages of percentages. Keep unknown/zero denominators as unavailable, not zero. Check join cardinality and avoid duplicate totals. Use chronological date ordering and comparable complete periods. State freshness and gaps; historical data must not be described as current operations. Avoid exposing unnecessary personal or sensitive row-level fields.`;
|
|
8
|
+
const discover = `Call dashboard_list_sources first, then dashboard_discover for exact authorized source/provider pairs. Follow returned inspectRequest/nextRequest and query contracts; do not scan unrelated schemas or guess SQL fields. Prefer existing governed definitions. If the requested source is missing, use analytics_definition_connections and analytics_definition_inspect; register a missing definition with analytics_definition_register only when needed for this requested dashboard, then refresh sources. Never duplicate an existing definition or ask the user to supply internal IDs.`;
|
|
9
|
+
const preview = `Before saving new or changed Bindings, call dashboard_preview or dashboard_preview_binding with the exact final queries and typed default controls in this run. Execute every preview.validation.checks[].suggestedCall with dashboard_test_filter; use observed non-default values and the all-values/null case when applicable. Follow each tool's schema, serialize JSON-string fields correctly, and copy returned bindingJson rather than nesting a Binding inside SQL. Changed queries/defaults need fresh evidence. Use at most eight final Bindings, sharing a query where its metric, grain and filters permit it. Never reuse historical previewIds or hard-code preview rows into the page.`;
|
|
10
|
+
const receipt = `Save a working draft and require a successful domain receipt with dashboard.artifactId, status "draft" and draftRevision. File writes, an existing resource card or an older published version are not proof of this save. Open the exact saved Artifact to inspect it; do not update an unchanged copy just to verify persistence. Verify real filter changes and refresh where available, distinguishing tool checks from unperformed viewer checks. Return the saved receipt, sources, metric definitions and a short manual acceptance checklist. Formal publication remains a separate human Workbench action.`;
|
|
11
|
+
const newDashboard = `Create a NEW independent dashboard. Do not modify, rename or replace an existing Artifact, including the currently selected dashboard. Choose a distinct business title. In HTML mode choose an unused workspace-relative named .html path; never reuse an old page just because it has a similar title. Reuse verified source definitions, not an old dashboard's identity. If this run already saved the requested dashboard, keep its Artifact rather than creating a duplicate.`;
|
|
12
|
+
const automaticTheme = `Choose one useful business theme from the authorized data, prioritizing an operational decision such as delivery delays, inventory backlog, supplier performance or production quality. Compare only a few plausible sources by completeness, freshness, metric clarity and actionable detail; then select the strongest one without repeatedly asking the user to choose. Explain who will use it, which decision it supports and which records require attention. If no source supports a defensible theme, report the missing data instead of making a decorative demo.`;
|
|
13
|
+
const specifiedTheme = `Use the business theme, audience and question supplied by the user. Translate them into a small coherent set of metrics, trends, comparisons and actionable detail. If the theme is missing or a material business definition is ambiguous, ask one focused question. Do not silently replace the requested theme or data source. Omit unsupported metrics and explain the gap.`;
|
|
14
|
+
const standard = `Use the STANDARD dashboard definition and native charts/tables/controls with dashboard_create for a new dashboard. Do not author HTML, load ECharts, call dashboard_create_html or convert the dashboard to HTML. Choose only components supported by the current tool schema; express summary metrics with supported charts/tables rather than inventing a KPI-card type. Include a useful time trend, a business-dimension comparison, a bounded detail table (normally at most 50 rows), and supported filters. Bind all views to real queries so filter changes and refresh requery the source.`;
|
|
15
|
+
const html = `Use a custom HTML dashboard. Call dashboard_libraries and read the installed skill's HTML runtime/lifecycle reference before authoring. Check the actual available libraries and pageRuntime contract; do not assume Browser Runtime is healthy. Author through SandboxFile tools and save a new page with dashboard_create_html. The page must declare const live = window.xpertLiveArtifact, subscribe before await live.ready(), and call live.query(bindingId, typedControls) for initial load and all affected bindings on filter application. Refresh each exact Binding ID with live.refresh(bindingId). Data comes only through these declared bindings, never direct database/REST requests or embedded preview rows. Follow allowed library origins, locks and runtime validation; do not add arbitrary dependencies.`;
|
|
16
|
+
const design = `Build a polished business workspace, not a wall of large charts. Use a restrained light enterprise theme unless the user supplies another style: quiet page background, clean panels, consistent spacing, readable typography and one main accent. At desktop widths, place three or four meaningful summary metrics above a main trend/comparison area with a narrower attention list; place actionable detail below. Adapt to the actual embedded viewport and stack on narrow widths without clipping. Use compact chart heights, clear units, restrained gridlines, legible tooltips and deliberate whitespace. Attention lists and comparison badges require real evidence; otherwise omit them. Support loading, no-data, error and partial-data states distinctly. Display the effective filters, data period and last successful refresh, never a fabricated refresh timestamp. Keep interactions functional and keyboard accessible.`;
|
|
17
|
+
const existing = `Use only the explicitly selected dashboard or a uniquely identified target from dashboard_list. Ask for a target if ambiguous; never choose by recency. Call dashboard_open, inspect its presentation.type and read the current definition/editing copy. Preserve Artifact identity, saved Bindings, business semantics and library locks unless the requested change requires changing them. Copy saveArguments and revision fields from the current open result, include the requested/current title, and save real changes with dashboard_update. On revision conflict retain the edits and reconcile with the latest draft; do not overwrite blindly.`;
|
|
18
|
+
const recovery = `Inspect the actual failed call, its full returned error and the current saved/unsaved state first. A previous response quoting an error is not a fresh execution. If an earlier save outcome is unknown, inspect matching saved Artifacts before attempting creation again. Repair only the evidenced argument, query, filter or page defect. Obtain fresh previews and filter evidence in this run for new/changed Bindings, then retry the appropriate domain save. Do not repeatedly guess. For RUNTIME_UNBOUND, missing tools, permission failures or unavailable Browser Runtime, stop with the precise blocker and preserved file/Artifact references; do not edit infrastructure or downgrade presentation. Report success only from a new successful save receipt.`;
|
|
19
|
+
function command(prompt) {
|
|
20
|
+
return {
|
|
21
|
+
name: prompt.name,
|
|
22
|
+
label: prompt.label,
|
|
23
|
+
description: prompt.description,
|
|
24
|
+
icon: dashboard_icon_1.dashboardIcon,
|
|
25
|
+
category: 'live-dashboard',
|
|
26
|
+
kind: 'prompt_workflow',
|
|
27
|
+
workflow: { type: 'prompt_workflow', name: prompt.name, label: prompt.label, description: prompt.description, tags: ['live-dashboard', prompt.group] },
|
|
28
|
+
argsHint: prompt.argsHint,
|
|
29
|
+
action: { type: 'insert_invocation', template: [context, ...prompt.instructions, 'User requirements / 用户需求:', '{{args}}'].join('\n\n') }
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** Commands are resolved from the installed middleware, including on existing Assistants. */
|
|
33
|
+
exports.dashboardPromptWorkflows = [
|
|
34
|
+
{
|
|
35
|
+
name: 'dashboard-standard-auto', group: 'standard',
|
|
36
|
+
label: { en_US: 'Standard · Choose a business theme', zh_Hans: '标准看板 · 自动选题新建' },
|
|
37
|
+
description: { en_US: 'Choose a useful theme from real data and create an independent native dashboard.', zh_Hans: '根据数据质量和业务价值选题,新建可筛选、刷新的标准看板。' },
|
|
38
|
+
argsHint: '数据源、使用人群、希望解决的问题、时间范围(可选)',
|
|
39
|
+
instructions: [newDashboard, discover, automaticTheme, metrics, standard, preview, receipt]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'dashboard-standard-new', group: 'standard',
|
|
43
|
+
label: { en_US: 'Standard · Create for a topic', zh_Hans: '标准看板 · 指定主题新建' },
|
|
44
|
+
description: { en_US: 'Create a native dashboard for a specific business question.', zh_Hans: '围绕指定业务问题设计指标、趋势、对比、明细和筛选。' },
|
|
45
|
+
argsHint: '业务主题、数据源、使用人群、时间范围、必需指标',
|
|
46
|
+
instructions: [newDashboard, specifiedTheme, discover, metrics, standard, preview, receipt]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'dashboard-standard-improve', group: 'standard',
|
|
50
|
+
label: { en_US: 'Standard · Improve a dashboard', zh_Hans: '标准看板 · 优化已有看板' },
|
|
51
|
+
description: { en_US: 'Improve the selected native dashboard while preserving its identity.', zh_Hans: '调整指定标准看板的图表、指标或筛选,保留原看板身份。' },
|
|
52
|
+
argsHint: '选中的看板或准确名称、需要调整的图表/指标/筛选',
|
|
53
|
+
instructions: [existing, 'Require a standard presentation. If the target is HTML, explain the mismatch rather than converting it. Edit only supported definition fields and omit htmlPath/libraries. Use dashboard_update, not a creation tool.', metrics, preview, receipt]
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'dashboard-standard-recover', group: 'standard',
|
|
57
|
+
label: { en_US: 'Standard · Recover a save', zh_Hans: '标准看板 · 恢复保存' },
|
|
58
|
+
description: { en_US: 'Diagnose a failed native draft save and resume with fresh evidence.', zh_Hans: '定位标准看板保存错误,按本轮真实验证结果恢复草稿保存。' },
|
|
59
|
+
argsHint: '失败工具及原始错误、新建还是修改、目标看板(如有)',
|
|
60
|
+
instructions: [recovery, 'Keep standard mode. For a failed new creation use dashboard_create after checking for an existing successful save. Do not create HTML files or call dashboard_create_html. If the existing target is HTML, report the mismatch instead of converting it.', `Only when recovering an existing Artifact: ${existing}`, metrics, preview, receipt]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'dashboard-html-auto', group: 'html',
|
|
64
|
+
label: { en_US: 'HTML · Choose a business theme', zh_Hans: 'HTML 看板 · 自动选题新建' },
|
|
65
|
+
description: { en_US: 'Choose an actionable business theme and build a polished independent HTML dashboard.', zh_Hans: '选择有真实业务价值的数据,创建布局精致、可实际使用的 HTML 看板。' },
|
|
66
|
+
argsHint: '数据源、使用人群、业务目标、时间范围、风格(可选)',
|
|
67
|
+
instructions: [newDashboard, discover, automaticTheme, metrics, preview, html, design, receipt]
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'dashboard-html-new', group: 'html',
|
|
71
|
+
label: { en_US: 'HTML · Create for a topic', zh_Hans: 'HTML 看板 · 指定主题新建' },
|
|
72
|
+
description: { en_US: 'Build a custom live HTML page for a specific business decision.', zh_Hans: '围绕指定业务问题制作自定义 HTML 看板,保留真实查询与交互。' },
|
|
73
|
+
argsHint: '业务主题、数据源、使用人群、日期、必需指标、风格/参考图',
|
|
74
|
+
instructions: [newDashboard, specifiedTheme, discover, metrics, preview, html, design, receipt]
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'dashboard-html-style', group: 'html',
|
|
78
|
+
label: { en_US: 'HTML · Style from a reference', zh_Hans: 'HTML 看板 · 参考图片美化' },
|
|
79
|
+
description: { en_US: 'Restyle a selected HTML dashboard using a reference without fabricating business content.', zh_Hans: '参考图片或风格描述美化指定 HTML 看板,保留指标、绑定和功能。' },
|
|
80
|
+
argsHint: '目标 HTML 看板、参考图片或风格描述、希望改善的地方',
|
|
81
|
+
instructions: [existing, 'Require an HTML target. Read the returned htmlPath editing copy and dashboard_libraries runtime contract; save with dashboard_update using that exact editing path. Use the reference for hierarchy, layout, typography and visual rhythm, not its example data, unsupported widgets or invented business events. If no usable reference/style was supplied, ask for the missing input. Preserve query/subscription/filter/refresh behavior and dependency locks for style-only work; do not rebuild unchanged queries or require fresh previews for unchanged Bindings.', design, receipt]
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'dashboard-html-recover', group: 'html',
|
|
85
|
+
label: { en_US: 'HTML · Recover a save', zh_Hans: 'HTML 看板 · 恢复保存' },
|
|
86
|
+
description: { en_US: 'Resume an unsaved HTML candidate or draft using actual validation diagnostics.', zh_Hans: '保留已有页面与真实查询,定位运行校验问题并恢复保存。' },
|
|
87
|
+
argsHint: '失败工具和原始错误、HTML 文件路径、目标看板(如有)',
|
|
88
|
+
instructions: [recovery, 'Keep HTML mode. Read the existing candidate instead of rewriting it. For a new unsaved candidate, use its verified path with dashboard_create_html after checking prior save outcomes. For an existing HTML Artifact, save the current editing copy with dashboard_update. If the target is standard, report the mismatch instead of converting it. Read dashboard_libraries and the skill HTML lifecycle reference before a targeted page repair.', `Only when recovering an existing Artifact: ${existing}`, metrics, preview, receipt]
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'dashboard-explore', group: 'checks',
|
|
92
|
+
label: { en_US: 'Inspect · Explore sources', zh_Hans: '通用检查 · 探索数据源' },
|
|
93
|
+
description: { en_US: 'Inspect available sources and propose useful themes without creating resources.', zh_Hans: '只读检查来源、字段、数据新鲜度,推荐可用的业务主题。' },
|
|
94
|
+
argsHint: '指定来源或业务方向、关注的问题(可选)',
|
|
95
|
+
instructions: ['This is inspection only: do not register definitions, write files, create or update dashboards. Call dashboard_list_sources, inspect relevant contracts with dashboard_discover and use small bounded previews. If a source is missing, inspect available analytics_definition_connections/analytics_definition_inspect without registration.', metrics, 'Return a short source inventory with supported queries, date coverage and gaps, then up to three ranked business themes with audience, decision, useful metrics and readiness. No exhaustive database scan.']
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: 'dashboard-test-source', group: 'checks',
|
|
99
|
+
label: { en_US: 'Inspect · Test a source', zh_Hans: '通用检查 · 验证查询' },
|
|
100
|
+
description: { en_US: 'Run a minimal real query and filter acceptance for one source.', zh_Hans: '验证一个来源的发现、真实查询、字段映射和筛选链路。' },
|
|
101
|
+
argsHint: '数据源名称、一个业务指标、一个筛选维度(可选)',
|
|
102
|
+
instructions: ['This is a bounded read-only source test: do not register definitions, write files or save dashboards. Resolve the requested source with dashboard_list_sources and dashboard_discover. Preview one minimal real query; inspect field types, units, completeness and actual rows. Test one observed non-default filter and the all-values/null case when supported using dashboard_test_filter. Stop at the first unresolved failure. Report exact tool/error, the step that passed and the remaining gap; provider connectivity alone is not full dashboard acceptance.']
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'dashboard-audit-metrics', group: 'checks',
|
|
106
|
+
label: { en_US: 'Inspect · Audit metric definitions', zh_Hans: '通用检查 · 核对指标口径' },
|
|
107
|
+
description: { en_US: 'Check calculations, grain and comparisons without changing the dashboard.', zh_Hans: '检查分子分母、去重、关联粒度、统计期间与单位。' },
|
|
108
|
+
argsHint: '目标看板或数据源、需要核对的指标及已知业务口径',
|
|
109
|
+
instructions: ['Audit only. Inspect the selected/uniquely identified dashboard with dashboard_open, or resolve the supplied source with dashboard_list_sources/dashboard_discover. Do not register definitions, edit files or save changes. Use bounded independent previews to check the declared calculations against source data.', metrics, 'Return each metric with its actual formula, grain, period, unit, evidence and verdict: verified, incorrect or insufficient evidence. Distinguish business-definition uncertainty from a query defect. Recommend concrete corrections without applying them.']
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'dashboard-verify', group: 'checks',
|
|
113
|
+
label: { en_US: 'Inspect · Verify a saved dashboard', zh_Hans: '通用检查 · 验证筛选与刷新' },
|
|
114
|
+
description: { en_US: 'Verify saved identity, real filters and refresh; distinguish viewer checks from tool evidence.', zh_Hans: '核对保存版本、筛选与刷新,区分真实执行证据和待人工验证项。' },
|
|
115
|
+
argsHint: '目标看板、筛选场景、预期业务结果(可选)',
|
|
116
|
+
instructions: ['Acceptance only. Resolve the exact requested Artifact, open its current saved state with dashboard_open and record its identity/revision. Do not modify files, call save tools, publish or change source data. Preview its saved Bindings with actual defaults and observed alternative filter values; use dashboard_test_filter to validate affected queries and the null/all case. For HTML inspect its saved lifecycle and dependency contract as well. If actual viewer controls can be exercised through available authorized tools, check initial load, apply filters, refresh and reopening. Otherwise mark viewer behavior untested and give at most three manual actions with observable expected results. Do not equate a successful query or resource card with a verified browser interaction.']
|
|
117
|
+
}
|
|
118
|
+
].map(command);
|
package/dist/lib/renderer.js
CHANGED
|
@@ -3,6 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.toDataAnalyticsPayload = toDataAnalyticsPayload;
|
|
4
4
|
/** The only Data Analytics v1 translation. Providers never see chart encoding syntax. */
|
|
5
5
|
function toDataAnalyticsPayload(manifest, datasets) {
|
|
6
|
+
// Missing datasets are failures, not empty/zero business results. The caller renders
|
|
7
|
+
// their failure state separately; successful empty datasets still retain their views.
|
|
8
|
+
const available = new Set(datasets.map(dataset => dataset.datasetId));
|
|
9
|
+
manifest = { ...manifest, charts: manifest.charts.filter(chart => available.has(chart.datasetId)),
|
|
10
|
+
tables: manifest.tables.filter(table => available.has(table.datasetId)) };
|
|
6
11
|
const snapshot = Object.fromEntries(datasets.flatMap((dataset) => [
|
|
7
12
|
[dataset.datasetId, dataset.rows],
|
|
8
13
|
...(!manifest.charts.some((chart) => chart.datasetId === dataset.datasetId) ? [] : [[`__plot.${dataset.datasetId}`, dataset.rows.map((row) => Object.fromEntries(dataset.fields.map((field) => [field.id, manifest.charts.some((chart) => chart.datasetId === dataset.datasetId && [chart.x, chart.y].includes(field.id)) ? renderValue(row[field.id], field.logicalType) : row[field.id]])))]])
|
|
@@ -15,7 +15,7 @@ export declare function dashboardSaveEvidence(files: () => WorkspaceFilesApi): {
|
|
|
15
15
|
token: number;
|
|
16
16
|
generations: Map<string, number>;
|
|
17
17
|
supersedesPaths: string[];
|
|
18
|
-
confirmation: "
|
|
18
|
+
confirmation: "draft" | "source";
|
|
19
19
|
};
|
|
20
20
|
accept(save: SaveAttempt, output: unknown): Promise<void>;
|
|
21
21
|
readonly pending: boolean;
|
|
@@ -41,6 +41,7 @@ let DashboardSharingService = class DashboardSharingService {
|
|
|
41
41
|
versionId: link.artifactVersionId, url: link.presentationUrl, expiresAt: link.expiresAt } : null };
|
|
42
42
|
}
|
|
43
43
|
async share(context, input) {
|
|
44
|
+
await this.dashboards.authorizeProject(context, 'manage');
|
|
44
45
|
const state = await this.inspect(context, input.artifactId, input.versionMode === 'version' ? input.versionId : undefined);
|
|
45
46
|
if (state.issues.length || !state.published?.versionId)
|
|
46
47
|
throw new common_1.BadRequestException({ code: 'DASHBOARD_SHARE_BLOCKED', issues: state.issues });
|
|
@@ -52,6 +53,7 @@ let DashboardSharingService = class DashboardSharingService {
|
|
|
52
53
|
return this.inspect(context, input.artifactId, input.versionMode === 'version' ? input.versionId : undefined);
|
|
53
54
|
}
|
|
54
55
|
async revoke(context, artifactId) {
|
|
56
|
+
await this.dashboards.authorizeProject(context, 'manage');
|
|
55
57
|
await this.dashboards.history(context, artifactId);
|
|
56
58
|
await this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability).revokeArtifactShare({ artifactId, shareKey: dashboard_sharing_1.DASHBOARD_SHARE_KEY });
|
|
57
59
|
return this.inspect(context, artifactId);
|
|
@@ -80,6 +80,8 @@ export declare class DashboardViewProvider implements IXpertViewExtensionProvide
|
|
|
80
80
|
};
|
|
81
81
|
dashboards: {
|
|
82
82
|
title: string | null | undefined;
|
|
83
|
+
draftRevision?: number | undefined;
|
|
84
|
+
canDelete: boolean;
|
|
83
85
|
artifactId: string;
|
|
84
86
|
versionId: string | null | undefined;
|
|
85
87
|
versionNumber: number | undefined;
|
|
@@ -116,123 +118,6 @@ export declare class DashboardViewProvider implements IXpertViewExtensionProvide
|
|
|
116
118
|
success: boolean;
|
|
117
119
|
data: {
|
|
118
120
|
artifactId: string;
|
|
119
|
-
versionId: string;
|
|
120
|
-
versionNumber: number;
|
|
121
|
-
title: string | null | undefined;
|
|
122
|
-
};
|
|
123
|
-
refresh: boolean;
|
|
124
|
-
} | {
|
|
125
|
-
success: boolean;
|
|
126
|
-
data: {
|
|
127
|
-
artifactId: string;
|
|
128
|
-
draftId: string;
|
|
129
|
-
draftRevision: number;
|
|
130
|
-
baseVersionId: string | null;
|
|
131
|
-
status: "draft";
|
|
132
|
-
title: string;
|
|
133
|
-
};
|
|
134
|
-
refresh: boolean;
|
|
135
|
-
} | {
|
|
136
|
-
success: boolean;
|
|
137
|
-
data: {
|
|
138
|
-
payload?: {
|
|
139
|
-
ok: boolean;
|
|
140
|
-
widget_type: string;
|
|
141
|
-
surface: string;
|
|
142
|
-
sources: never[];
|
|
143
|
-
manifest: {
|
|
144
|
-
version: number;
|
|
145
|
-
surface: string;
|
|
146
|
-
title: string;
|
|
147
|
-
cards: never[];
|
|
148
|
-
filters: never[];
|
|
149
|
-
sources: never[];
|
|
150
|
-
charts: {
|
|
151
|
-
id: string;
|
|
152
|
-
title: string;
|
|
153
|
-
type: "bar" | "line" | "area" | "pie" | "scatter";
|
|
154
|
-
dataset: string;
|
|
155
|
-
encodings: {
|
|
156
|
-
color?: {
|
|
157
|
-
field: string;
|
|
158
|
-
} | undefined;
|
|
159
|
-
x: {
|
|
160
|
-
field: string;
|
|
161
|
-
};
|
|
162
|
-
y: {
|
|
163
|
-
field: string;
|
|
164
|
-
};
|
|
165
|
-
};
|
|
166
|
-
}[];
|
|
167
|
-
tables: {
|
|
168
|
-
id: string;
|
|
169
|
-
title: string;
|
|
170
|
-
dataset: string;
|
|
171
|
-
columns: {
|
|
172
|
-
field: string;
|
|
173
|
-
label: string;
|
|
174
|
-
}[];
|
|
175
|
-
}[];
|
|
176
|
-
blocks: ({
|
|
177
|
-
id: string;
|
|
178
|
-
type: string;
|
|
179
|
-
chartId: string;
|
|
180
|
-
} | {
|
|
181
|
-
id: string;
|
|
182
|
-
type: string;
|
|
183
|
-
tableId: string;
|
|
184
|
-
})[];
|
|
185
|
-
};
|
|
186
|
-
snapshot: {
|
|
187
|
-
version: number;
|
|
188
|
-
generatedAt: string;
|
|
189
|
-
status: string;
|
|
190
|
-
datasets: any;
|
|
191
|
-
};
|
|
192
|
-
} | undefined;
|
|
193
|
-
datasets: import("@xpert-ai/contracts").AnalyticalDataset[];
|
|
194
|
-
manifest: {
|
|
195
|
-
schema: "xpert.live-dashboard";
|
|
196
|
-
id: string;
|
|
197
|
-
title: string;
|
|
198
|
-
bindings: import("@xpert-ai/contracts").AnalyticsBinding[];
|
|
199
|
-
controls: {
|
|
200
|
-
type: "string" | "number" | "boolean" | "date";
|
|
201
|
-
controlId: string;
|
|
202
|
-
label: string;
|
|
203
|
-
defaultValue?: string | number | boolean | null | undefined;
|
|
204
|
-
allowedValues?: (string | number | boolean | null)[] | undefined;
|
|
205
|
-
}[];
|
|
206
|
-
charts: {
|
|
207
|
-
type: "bar" | "line" | "area" | "pie" | "scatter";
|
|
208
|
-
id: string;
|
|
209
|
-
title: string;
|
|
210
|
-
datasetId: string;
|
|
211
|
-
x: string;
|
|
212
|
-
y: string;
|
|
213
|
-
color?: string | undefined;
|
|
214
|
-
}[];
|
|
215
|
-
tables: {
|
|
216
|
-
id: string;
|
|
217
|
-
title: string;
|
|
218
|
-
datasetId: string;
|
|
219
|
-
fields: string[];
|
|
220
|
-
}[];
|
|
221
|
-
presentation?: {
|
|
222
|
-
type: "html";
|
|
223
|
-
html: string;
|
|
224
|
-
dependencies?: import("@xpert-ai/plugin-sdk/artifact-document").ArtifactDocumentDependencyLock[] | undefined;
|
|
225
|
-
} | undefined;
|
|
226
|
-
};
|
|
227
|
-
draftId?: string | undefined;
|
|
228
|
-
draftRevision?: number | undefined;
|
|
229
|
-
baseVersionId?: string | null | undefined;
|
|
230
|
-
isDraft?: boolean | undefined;
|
|
231
|
-
title: string | null | undefined;
|
|
232
|
-
updatedAt: string | Date | undefined;
|
|
233
|
-
artifactId: string;
|
|
234
|
-
versionId: string | null | undefined;
|
|
235
|
-
versionNumber: number | undefined;
|
|
236
121
|
};
|
|
237
122
|
refresh: boolean;
|
|
238
123
|
} | {
|
|
@@ -32,21 +32,22 @@ let DashboardViewProvider = class DashboardViewProvider {
|
|
|
32
32
|
}
|
|
33
33
|
supports(context) { return context.hostType === 'agent' || context.hostType === 'project'; }
|
|
34
34
|
getViewManifests(context, slot) {
|
|
35
|
-
if (!(context.hostType === 'agent' &&
|
|
35
|
+
if (!(context.hostType === 'agent' && ['agent.workbench', 'agent.workbench.fixed', 'agent.workbench.main'].includes(slot) || context.hostType === 'project' && slot === 'detail.sections'))
|
|
36
36
|
return [];
|
|
37
37
|
return [{ key: exports.VIEW_KEY, title: text('Live dashboards', '实时看板'), hostType: context.hostType, slot, order: 35,
|
|
38
|
-
icon: dashboard_icon_1.dashboardIcon, refreshable: true, activation: { requiredFeatures: [exports.FEATURE] },
|
|
38
|
+
icon: dashboard_icon_1.dashboardIcon, refreshable: true, activation: { projectResource: true, requiredFeatures: [exports.FEATURE] },
|
|
39
39
|
source: { provider: exports.VIEW_PROVIDER, plugin: dashboard_service_1.PLUGIN_NAME },
|
|
40
|
-
workbench: { fixed: true, menu: { enabled: true, label: text('Live dashboards', '实时看板'), icon: dashboard_icon_1.dashboardIcon, order: 35 } },
|
|
40
|
+
workbench: { fixed: true, openMode: 'on-demand', menu: { enabled: true, label: text('Live dashboards', '实时看板'), icon: dashboard_icon_1.dashboardIcon, order: 35 } },
|
|
41
41
|
view: { type: 'remote_component', runtime: 'react', protocolVersion: 1, component: { isolation: 'iframe', entry: exports.VIEW_KEY }, dataSource: { mode: 'platform' } },
|
|
42
42
|
dataSource: { mode: 'platform', querySchema: { supportsSelection: true, supportsParameters: true }, cache: { enabled: false } },
|
|
43
|
-
clientCommands: [{ key: contracts_1.ASSISTANT_CONTEXT_SET_COMMAND, label: text('Select dashboard for Assistant', '设置助理当前看板') },
|
|
43
|
+
clientCommands: [{ key: 'workbench.content.status' }, { key: 'workbench.content.resolve' }, { key: contracts_1.ASSISTANT_CONTEXT_SET_COMMAND, label: text('Select dashboard for Assistant', '设置助理当前看板') },
|
|
44
44
|
{ key: contracts_1.ARTIFACT_PRESENTATION_OPEN_COMMAND, label: text('Open shared page', '打开独立页面') },
|
|
45
45
|
{ key: contracts_1.WORKBENCH_NAVIGATION_OPEN_COMMAND, label: text('Open dashboard', '打开看板') }],
|
|
46
46
|
hostEvents: { subscriptions: [{ key: 'dashboard-created', event: 'assistant.tool.completed',
|
|
47
47
|
filter: { toolNames: dashboard_receipt_1.DASHBOARD_MUTATION_TOOLS }, action: { type: 'forward', debounceMs: 0 } }] },
|
|
48
48
|
actions: [
|
|
49
49
|
...['share-status', 'share', 'revoke-share'].map(key => ({ key, label: text('Team sharing', '团队分享'), actionType: 'invoke', placement: 'toolbar', inputSchema: { type: 'object', properties: {}, additionalProperties: true } })),
|
|
50
|
+
{ key: 'delete', label: text('Delete dashboard', '删除看板'), actionType: 'invoke', placement: 'toolbar', inputSchema: { type: 'object', properties: {}, additionalProperties: true } },
|
|
50
51
|
{ key: 'publish', label: text('Publish draft', '发布草稿'), actionType: 'invoke', placement: 'toolbar', inputSchema: { type: 'object', properties: {}, additionalProperties: true } },
|
|
51
52
|
{ key: 'save', label: text('Save', '保存'), actionType: 'invoke', placement: 'toolbar', inputSchema: { type: 'object', properties: {}, additionalProperties: true } },
|
|
52
53
|
{ key: 'query', label: text('Refresh', '刷新'), actionType: 'invoke', placement: 'toolbar', inputSchema: { type: 'object', properties: {}, additionalProperties: true } },
|
|
@@ -69,6 +70,13 @@ let DashboardViewProvider = class DashboardViewProvider {
|
|
|
69
70
|
const input = v3_1.z.object({ artifactId: v3_1.z.string().uuid(), versionId: v3_1.z.string().uuid().optional() }).strict().parse(request.input);
|
|
70
71
|
return { success: true, data: action === 'share-status' ? await this.sharing.inspect(context, input.artifactId, input.versionId) : await this.sharing.revoke(context, input.artifactId), refresh: false };
|
|
71
72
|
}
|
|
73
|
+
if (action === 'delete') {
|
|
74
|
+
const parsed = v3_1.z.object({ artifactId: v3_1.z.string().uuid(), expectedRevision: v3_1.z.number().int().positive(), confirmed: v3_1.z.literal(true) }).strict().safeParse(request.input);
|
|
75
|
+
if (!parsed.success)
|
|
76
|
+
throw new common_1.BadRequestException('DASHBOARD_DELETION_CONFIRMATION_REQUIRED');
|
|
77
|
+
const input = parsed.data;
|
|
78
|
+
return { success: true, data: await this.service.trash(context, input.artifactId, input.expectedRevision), refresh: false };
|
|
79
|
+
}
|
|
72
80
|
if (action === 'publish') {
|
|
73
81
|
const parsed = v3_1.z.object({ artifactId: v3_1.z.string().uuid(), expectedRevision: v3_1.z.number().int().positive(),
|
|
74
82
|
expectedCurrentVersionId: v3_1.z.string().uuid().nullable(), confirmed: v3_1.z.literal(true) }).strict().safeParse(request.input);
|