@xpert-ai/plugin-live-dashboard 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/.xpertai-plugin/plugin.json +1 -1
  2. package/README.md +8 -2
  3. package/dist/index.js +3 -1
  4. package/dist/lib/agent.schema.d.ts +177 -23
  5. package/dist/lib/agent.schema.js +11 -9
  6. package/dist/lib/archive.service.d.ts +169 -0
  7. package/dist/lib/archive.service.js +147 -0
  8. package/dist/lib/bounded-map.d.ts +2 -0
  9. package/dist/lib/bounded-map.js +25 -0
  10. package/dist/lib/capacity.d.ts +14 -0
  11. package/dist/lib/capacity.js +16 -0
  12. package/dist/lib/content.provider.d.ts +1 -1
  13. package/dist/lib/dashboard-archive.d.ts +70 -0
  14. package/dist/lib/dashboard-archive.js +118 -0
  15. package/dist/lib/dashboard-queries.d.ts +1 -1
  16. package/dist/lib/dashboard-queries.js +11 -6
  17. package/dist/lib/dashboard-receipt.js +1 -1
  18. package/dist/lib/dashboard-sources.d.ts +69 -0
  19. package/dist/lib/dashboard-sources.js +38 -0
  20. package/dist/lib/dashboard-view.schema.d.ts +422 -88
  21. package/dist/lib/dashboard.schema.d.ts +379 -58
  22. package/dist/lib/dashboard.schema.js +20 -2
  23. package/dist/lib/dashboard.service.d.ts +80 -15
  24. package/dist/lib/dashboard.service.js +65 -9
  25. package/dist/lib/definition-tools.d.ts +4 -4
  26. package/dist/lib/document-host.js +1 -1
  27. package/dist/lib/document-package.d.ts +19 -4
  28. package/dist/lib/editing-patch.d.ts +3 -0
  29. package/dist/lib/editing-patch.js +4 -2
  30. package/dist/lib/editing-session.d.ts +4 -4
  31. package/dist/lib/editing.d.ts +204 -20
  32. package/dist/lib/editing.js +10 -3
  33. package/dist/lib/filter-evidence.d.ts +28 -0
  34. package/dist/lib/filter-evidence.js +3 -2
  35. package/dist/lib/html-guidance.d.ts +1 -0
  36. package/dist/lib/html-guidance.js +2 -1
  37. package/dist/lib/html-validation-feedback.d.ts +7 -0
  38. package/dist/lib/html-validation-feedback.js +19 -0
  39. package/dist/lib/html-validation.js +3 -2
  40. package/dist/lib/inspection-host.d.ts +7 -0
  41. package/dist/lib/inspection-host.js +48 -0
  42. package/dist/lib/inspection-runtime.d.ts +2 -0
  43. package/dist/lib/inspection-runtime.js +77 -0
  44. package/dist/lib/json-input.d.ts +4 -0
  45. package/dist/lib/json-input.js +21 -0
  46. package/dist/lib/middleware.d.ts +3 -1
  47. package/dist/lib/middleware.js +92 -13
  48. package/dist/lib/plugin.js +3 -1
  49. package/dist/lib/presentation.provider.d.ts +24 -9
  50. package/dist/lib/presentation.provider.js +2 -1
  51. package/dist/lib/preview-retry.d.ts +4 -3
  52. package/dist/lib/preview-retry.js +13 -7
  53. package/dist/lib/prompt-workflows.js +1 -1
  54. package/dist/lib/query-session.d.ts +9 -0
  55. package/dist/lib/query-session.js +64 -0
  56. package/dist/lib/save-requests.d.ts +118 -0
  57. package/dist/lib/save-requests.js +199 -0
  58. package/dist/lib/source-executions.d.ts +437 -0
  59. package/dist/lib/source-executions.js +49 -0
  60. package/dist/lib/source-validation.d.ts +2 -0
  61. package/dist/lib/source-validation.js +21 -0
  62. package/dist/lib/view.provider.d.ts +213 -8
  63. package/dist/lib/view.provider.js +47 -5
  64. package/dist/remote/app.css +1 -1
  65. package/dist/remote/app.js +185 -63
  66. package/dist/remote/build-manifest.json +52 -33
  67. package/dist/sandbox-actions/action.json +1 -1
  68. package/dist/sandbox-actions/bundle/host.js +63 -14
  69. package/dist/sandbox-actions/bundle/validate.mjs +2 -2
  70. package/dist/validation/host-entry.d.ts +5 -1
  71. package/dist/validation/host-entry.js +21 -4
  72. package/docs/docs.json +5 -1
  73. package/docs/large-dashboards-acceptance.mdx +74 -0
  74. package/docs/large-dashboards.mdx +80 -0
  75. package/docs/sources-inspect-portability-acceptance.mdx +67 -0
  76. package/docs/sources-inspect-portability.mdx +115 -0
  77. package/package.json +8 -7
  78. package/skills/live-dashboard/SKILL.md +5 -4
  79. package/skills/live-dashboard/references/source-inspection.md +17 -0
@@ -19,9 +19,10 @@ const promises_1 = require("node:timers/promises");
19
19
  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
+ const html_validation_feedback_1 = require("./html-validation-feedback");
22
23
  const PLUGIN = '@xpert-ai/plugin-live-dashboard', ACTION = 'live_dashboard.validate-html', POOL = 'sandbox-browser';
23
24
  const reportSchema = v3_1.z.object({ ok: v3_1.z.boolean(), errors: v3_1.z.array(v3_1.z.string().max(300)).max(16),
24
- requestedBindings: v3_1.z.array(v3_1.z.string().max(80)).max(60), renderedTextLength: v3_1.z.number().int().nonnegative() }).strict();
25
+ requestedBindings: v3_1.z.array(v3_1.z.string().max(80)).max(64), renderedTextLength: v3_1.z.number().int().nonnegative() }).strict();
25
26
  const recordSchema = v3_1.z.object({ tenantId: v3_1.z.string(), organizationId: v3_1.z.string(), userId: v3_1.z.string(), manifestId: v3_1.z.string(), deadline: v3_1.z.number(),
26
27
  filePath: v3_1.z.string(), workspacePath: v3_1.z.string(), size: v3_1.z.number(), sha256: v3_1.z.string() }).strict();
27
28
  const digest = (buffer) => (0, node_crypto_1.createHash)('sha256').update(buffer).digest('hex');
@@ -70,7 +71,7 @@ let HtmlDashboardValidation = class HtmlDashboardValidation {
70
71
  const report = reportSchema.parse(JSON.parse(await redis.get(`${key}:report`) ?? 'null'));
71
72
  finished = true;
72
73
  if (!report.ok)
73
- throw new common_1.BadRequestException(`DASHBOARD_HTML_PREFLIGHT_FAILED: ${JSON.stringify(report.errors)}. Repair the Workspace HTML or include every required previewId, then retry. No dashboard was saved.`);
74
+ throw (0, html_validation_feedback_1.htmlPreflightFailure)(manifest, report);
74
75
  return { jobId, requestedBindings: report.requestedBindings, status: 'passed' };
75
76
  }
76
77
  await (0, promises_1.setTimeout)(250);
@@ -0,0 +1,7 @@
1
+ import { type DashboardComponent } from './dashboard-sources';
2
+ /** One frame generation, one nonce; inspection is UI metadata and grants no query authority. */
3
+ export declare function createDashboardInspector(frame: HTMLIFrameElement, components: DashboardComponent[], onSelect: (id: string | null) => void, onExit: () => void, standard?: boolean): {
4
+ prefix: string;
5
+ setEnabled(value: boolean): void;
6
+ dispose(): void;
7
+ };
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createDashboardInspector = createDashboardInspector;
4
+ const v3_1 = require("zod/v3");
5
+ const inspection_runtime_1 = require("./inspection-runtime");
6
+ const dashboard_sources_1 = require("./dashboard-sources");
7
+ const message = v3_1.z.discriminatedUnion('type', [
8
+ v3_1.z.object({ channel: v3_1.z.literal('live-dashboard.inspection'), key: v3_1.z.string(), type: v3_1.z.literal('ready') }).strict(),
9
+ v3_1.z.object({ channel: v3_1.z.literal('live-dashboard.inspection'), key: v3_1.z.string(), type: v3_1.z.literal('exit') }).strict(),
10
+ v3_1.z.object({ channel: v3_1.z.literal('live-dashboard.inspection'), key: v3_1.z.string(), type: v3_1.z.literal('selected'), componentId: dashboard_sources_1.componentId.nullable() }).strict()
11
+ ]);
12
+ /** One frame generation, one nonce; inspection is UI metadata and grants no query authority. */
13
+ function createDashboardInspector(frame, components, onSelect, onExit, standard = false) {
14
+ const key = Array.from(crypto.getRandomValues(new Uint8Array(16)), byte => byte.toString(16).padStart(2, '0')).join('');
15
+ const host = frame.ownerDocument.defaultView;
16
+ let connected = false, enabled = false, disposed = false;
17
+ const send = () => {
18
+ if (connected && !disposed)
19
+ frame.contentWindow?.postMessage({ channel: 'live-dashboard.inspection', key, type: 'mode', enabled,
20
+ standard, components: components.map(({ id, elementId }) => ({ id, elementId })) }, '*');
21
+ };
22
+ const receive = (event) => {
23
+ if (disposed || event.source !== frame.contentWindow)
24
+ return;
25
+ const parsed = message.safeParse(event.data);
26
+ if (!parsed.success || parsed.data.key !== key)
27
+ return;
28
+ if (parsed.data.type === 'ready') {
29
+ connected = true;
30
+ send();
31
+ }
32
+ else if (parsed.data.type === 'exit') {
33
+ enabled = false;
34
+ onExit();
35
+ }
36
+ else {
37
+ const selected = parsed.data.componentId;
38
+ if (enabled && (selected === null || components.some(item => item.id === selected)))
39
+ onSelect(selected);
40
+ }
41
+ };
42
+ host.addEventListener('message', receive);
43
+ return {
44
+ prefix: `<script>${inspection_runtime_1.DASHBOARD_INSPECTION_RUNTIME.replace('__INSPECTION_SESSION__', key)}</script>`,
45
+ setEnabled(value) { enabled = value; send(); },
46
+ dispose() { enabled = false; send(); disposed = true; host.removeEventListener('message', receive); }
47
+ };
48
+ }
@@ -0,0 +1,2 @@
1
+ /** Injected into the isolated dashboard only; it never reads or executes query definitions. */
2
+ export declare const DASHBOARD_INSPECTION_RUNTIME: string;
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DASHBOARD_INSPECTION_RUNTIME = void 0;
4
+ /** Injected into the isolated dashboard only; it never reads or executes query definitions. */
5
+ exports.DASHBOARD_INSPECTION_RUNTIME = String.raw `
6
+ (() => {
7
+ const key = '__INSPECTION_SESSION__', channel = 'live-dashboard.inspection';
8
+ let enabled = false, standard = false, components = [], selected = null, overlay, previousFocus;
9
+ const tabs = new Map();
10
+ const post = (type, data = {}) => parent.postMessage({ channel, key, type, ...data }, '*');
11
+ function outline(element) {
12
+ selected = element;
13
+ if (!element || !enabled) { if (overlay) overlay.hidden = true; return; }
14
+ if (!overlay) {
15
+ overlay = document.createElement('div');
16
+ overlay.setAttribute('aria-hidden', 'true');
17
+ overlay.style.cssText = 'position:fixed;pointer-events:none;z-index:2147483647;border:2px solid #2563eb;background:rgba(37,99,235,.06);box-sizing:border-box';
18
+ document.documentElement.append(overlay);
19
+ }
20
+ const r = element.getBoundingClientRect();
21
+ Object.assign(overlay.style, { left:r.left+'px', top:r.top+'px', width:r.width+'px', height:r.height+'px' });
22
+ overlay.hidden = false;
23
+ }
24
+ function target(node) {
25
+ for (let element = node instanceof Element ? node : null; element; element = element.parentElement) {
26
+ if (components.some(c => c.elementId === (standard ? element.getAttribute('data-layout-block-id') : element.id))) return element;
27
+ }
28
+ return null;
29
+ }
30
+ function registerTargets() {
31
+ if (!enabled) return;
32
+ for (const component of components) {
33
+ const element = standard ? [...document.querySelectorAll('[data-layout-block-id]')].find(e => e.getAttribute('data-layout-block-id') === component.elementId) : document.getElementById(component.elementId);
34
+ if (element && !tabs.has(element)) { tabs.set(element, element.getAttribute('tabindex')); element.setAttribute('tabindex', '0'); }
35
+ }
36
+ }
37
+ new MutationObserver(registerTargets).observe(document, { childList:true, subtree:true });
38
+ function activate(value) {
39
+ if (value === enabled) return;
40
+ enabled = value;
41
+ if (enabled) {
42
+ previousFocus = document.activeElement;
43
+ registerTargets();
44
+ } else {
45
+ outline(null);
46
+ for (const [element, value] of tabs) value === null ? element.removeAttribute('tabindex') : element.setAttribute('tabindex', value);
47
+ tabs.clear();
48
+ if (previousFocus instanceof HTMLElement && previousFocus.isConnected) previousFocus.focus({ preventScroll:true });
49
+ }
50
+ }
51
+ function choose(event) {
52
+ if (!enabled) return;
53
+ event.preventDefault(); event.stopImmediatePropagation();
54
+ const element = target(event.target), component = components.find(c => c.elementId === (standard ? element?.getAttribute('data-layout-block-id') : element?.id));
55
+ outline(element);
56
+ post('selected', { componentId:component?.id ?? null });
57
+ }
58
+ window.addEventListener('message', event => {
59
+ const m = event.data;
60
+ if (event.source !== parent || !m || m.channel !== channel || m.key !== key) return;
61
+ if (m.type === 'mode') { components = m.components; standard = m.standard; activate(m.enabled === true); }
62
+ });
63
+ window.addEventListener('pointermove', e => { if (enabled) outline(target(e.target)); }, true);
64
+ window.addEventListener('focusin', e => { if (enabled) outline(target(e.target)); }, true);
65
+ for (const name of ['pointerdown', 'pointerup', 'mousedown', 'mouseup', 'dblclick']) window.addEventListener(name, e => {
66
+ if (enabled) { e.preventDefault(); e.stopImmediatePropagation(); }
67
+ }, true);
68
+ window.addEventListener('click', choose, true);
69
+ window.addEventListener('keydown', e => {
70
+ if (!enabled) return;
71
+ if (e.key === 'Escape') { e.preventDefault(); e.stopImmediatePropagation(); activate(false); post('exit'); }
72
+ else if (e.key === 'Enter' || e.key === ' ') choose(e);
73
+ }, true);
74
+ window.addEventListener('scroll', () => { if (enabled) outline(selected); }, true);
75
+ window.addEventListener('resize', () => { if (enabled) outline(selected); });
76
+ post('ready');
77
+ })();`;
@@ -0,0 +1,4 @@
1
+ type JsonInputField = 'requestJson' | 'binding.payloadJson' | 'bindingJson';
2
+ /** Report syntax locations without echoing query text or native errors containing input fragments. */
3
+ export declare function parseJsonInput(text: string, field: JsonInputField): unknown;
4
+ export {};
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseJsonInput = parseJsonInput;
4
+ /** Report syntax locations without echoing query text or native errors containing input fragments. */
5
+ function parseJsonInput(text, field) {
6
+ try {
7
+ return JSON.parse(text);
8
+ }
9
+ catch (error) {
10
+ if (!(error instanceof SyntaxError))
11
+ throw error;
12
+ const match = /\bat position (\d+)(?: \(line \d+ column \d+\))?$/.exec(error.message);
13
+ const offset = match ? Number(match[1]) : error.message === 'Unexpected end of JSON input' ? text.length : undefined;
14
+ if (offset === undefined || offset > text.length) {
15
+ throw new Error(`DASHBOARD_INVALID_JSON: ${field} contains invalid JSON; the parser did not provide a character position. Supply one complete JSON object without Markdown fences.`);
16
+ }
17
+ const lines = text.slice(0, offset).split(/\r\n|\r|\n/);
18
+ const character = offset === text.length ? 'end of input' : JSON.stringify(String.fromCodePoint(text.codePointAt(offset)));
19
+ throw new Error(`DASHBOARD_INVALID_JSON: ${field} has invalid JSON at offset ${offset} (0-based UTF-16), line ${lines.length}, column ${lines.at(-1).length + 1} (1-based); found ${character}. Check this position and the preceding JSON delimiter or value.`);
20
+ }
21
+ }
@@ -1,10 +1,12 @@
1
+ import { DashboardSaveRequests } from './save-requests';
1
2
  import type { TAgentMiddlewareMeta } from '@xpert-ai/contracts';
2
3
  import { type AgentMiddleware, type IAgentMiddlewareStrategy, type IAgentMiddlewareContext } from '@xpert-ai/plugin-sdk';
3
4
  import { DashboardService } from './dashboard.service';
4
5
  export declare const 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.";
5
6
  export declare class DashboardMiddleware implements IAgentMiddlewareStrategy {
6
7
  private readonly service;
8
+ private readonly saveRequests?;
7
9
  readonly meta: TAgentMiddlewareMeta;
8
- constructor(service: DashboardService);
10
+ constructor(service: DashboardService, saveRequests?: DashboardSaveRequests | undefined);
9
11
  createMiddleware(_options: unknown, context: IAgentMiddlewareContext): AgentMiddleware;
10
12
  }
@@ -8,8 +8,14 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
8
8
  var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
10
  };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
11
14
  Object.defineProperty(exports, "__esModule", { value: true });
12
15
  exports.DashboardMiddleware = exports.DASHBOARD_GUIDANCE = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const save_requests_1 = require("./save-requests");
18
+ const bounded_map_1 = require("./bounded-map");
13
19
  const runtime_context_1 = require("./runtime-context");
14
20
  const definition_tools_1 = require("./definition-tools");
15
21
  const analytics_source_1 = require("./analytics-source");
@@ -37,6 +43,7 @@ const resource_card_1 = require("./resource-card");
37
43
  const dashboard_icon_1 = require("./dashboard-icon");
38
44
  const preview_retry_1 = require("./preview-retry");
39
45
  const prompt_workflows_1 = require("./prompt-workflows");
46
+ const json_input_1 = require("./json-input");
40
47
  const middlewareOptionsSchema = v3_1.z.object({ allowedFinalProviders: v3_1.z.array(v3_1.z.string().min(1)).min(1).max(20).optional(),
41
48
  maxModelCalls: v3_1.z.number().int().min(1).max(64).default(24) }).strict().default({});
42
49
  const providerOptionSchema = {
@@ -45,8 +52,9 @@ const providerOptionSchema = {
45
52
  };
46
53
  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.`;
47
54
  let DashboardMiddleware = class DashboardMiddleware {
48
- constructor(service) {
55
+ constructor(service, saveRequests) {
49
56
  this.service = service;
57
+ this.saveRequests = saveRequests;
50
58
  this.meta = {
51
59
  name: 'LiveDashboard', label: { en_US: 'Live dashboards', zh_Hans: '实时看板' },
52
60
  description: { en_US: 'Discover, preview, create and update governed live dashboards.', zh_Hans: '发现业务数据、验证查询并持续修改实时看板。' },
@@ -62,6 +70,7 @@ let DashboardMiddleware = class DashboardMiddleware {
62
70
  };
63
71
  }
64
72
  createMiddleware(_options, context) {
73
+ const runId = (0, node_crypto_1.randomUUID)();
65
74
  const options = middlewareOptionsSchema.parse(_options);
66
75
  if (!context.xpertId)
67
76
  throw new Error('DASHBOARD_ASSISTANT_REQUIRED');
@@ -104,7 +113,7 @@ let DashboardMiddleware = class DashboardMiddleware {
104
113
  const controls = Object.fromEntries(binding.parameters.map((parameter) => [parameter.controlId, parameter.defaultValue ?? null]));
105
114
  const dataset = await this.service.preview(scope, { binding, controls });
106
115
  const previewId = previewKey(binding, controls);
107
- if (previews.size >= 24 && !previews.has(previewId))
116
+ if (previews.size >= 256 && !previews.has(previewId))
108
117
  throw new Error('DASHBOARD_PREVIEW_LIMIT');
109
118
  previews.set(previewId, binding);
110
119
  const rows = dataset.rows.slice(0, 20);
@@ -133,7 +142,7 @@ let DashboardMiddleware = class DashboardMiddleware {
133
142
  }
134
143
  }
135
144
  };
136
- const save = async (input, config, html, dependencies = []) => {
145
+ const save = async (input, config, html, dependencies = [], sourceFile) => {
137
146
  const { previewIds, supersedesPaths, creationIntent, ...definition } = input;
138
147
  const bindings = resolvePreviews(previewIds);
139
148
  const manifest = dashboard_schema_1.dashboardSchema.parse({ ...definition, bindings, schema: 'xpert.live-dashboard', id: (0, node_crypto_1.randomUUID)(),
@@ -153,6 +162,8 @@ let DashboardMiddleware = class DashboardMiddleware {
153
162
  const sameTitle = [...created.values()].find(item => item.manifest.title === manifest.title);
154
163
  if (sameTitle && !creationIntent)
155
164
  throw new Error(`DASHBOARD_ALREADY_CREATED: this run already saved this dashboard. Use dashboard_open with artifactId ${sameTitle.receipt.artifactId}, then dashboard_update with fresh previewIds for data changes; preserve the Artifact instead of creating another one.`);
165
+ if (this.saveRequests && bindings.length > 8)
166
+ return JSON.stringify(await this.saveRequests.submit(scope, manifest, null, 0, `${runId}/${key}`, sourceFile));
156
167
  const result = await this.service.save(scope, manifest, null);
157
168
  created.set(key, { manifest, receipt: result });
158
169
  const resourceCard = await (0, resource_card_1.emitDashboardCard)(scope, { ...result, state: 'draft', revision: result.draftRevision }, config);
@@ -164,6 +175,20 @@ let DashboardMiddleware = class DashboardMiddleware {
164
175
  throw new Error('DASHBOARD_WORKSPACE_REQUIRED');
165
176
  return files;
166
177
  });
178
+ const pendingRequests = new Map();
179
+ const activeRequests = new Set(), delivered = new Set();
180
+ const observeRequest = async (result, config) => {
181
+ if (!result.terminal)
182
+ activeRequests.add(result.requestId);
183
+ else
184
+ activeRequests.delete(result.requestId);
185
+ if (!result.dashboard || delivered.has(result.requestId))
186
+ return JSON.stringify(result);
187
+ delivered.add(result.requestId);
188
+ const resourceCard = await (0, resource_card_1.emitDashboardCard)(scope, { ...result.dashboard, state: 'draft', revision: result.dashboard.draftRevision }, config);
189
+ return JSON.stringify({ ...result, resourceCard });
190
+ };
191
+ let observing = false, waits = 0;
167
192
  const previewRetries = (0, preview_retry_1.createPreviewRetryGuard)();
168
193
  let modelCalls = 0;
169
194
  return { name: 'LiveDashboard', contextSchema: editing_1.dashboardContextSchema, stateSchema: editing_session_1.dashboardStateSchema,
@@ -172,12 +197,28 @@ let DashboardMiddleware = class DashboardMiddleware {
172
197
  wrapToolCall: async (request, handler) => {
173
198
  const name = request.toolCall.name;
174
199
  const args = request.toolCall.args;
200
+ observing = name === 'dashboard_wait_request';
201
+ if (observing && ++waits > 40)
202
+ throw new Error('DASHBOARD_WAIT_BUDGET: resume with dashboard_get_request using the existing requestId; do not resubmit.');
175
203
  const path = (0, plugin_sdk_1.workspaceFileMutationPath)(request.tool && 'metadata' in request.tool ? request.tool.metadata : undefined, args);
176
204
  const creation = name === 'dashboard_create_html' ? agent_schema_1.createHtmlToolSchema.safeParse(args)
177
205
  : name === 'dashboard_create' ? agent_schema_1.createToolSchema.safeParse(args) : undefined;
178
- const save = dashboard_receipt_1.DASHBOARD_MUTATION_TOOLS.includes(name) ? evidence.beginSave(creation?.success ? creation.data.supersedesPaths : undefined, name === 'dashboard_create' ? 'draft' : 'source') : undefined;
206
+ const priorAttempt = (name === 'dashboard_wait_request' || name === 'dashboard_get_request') && typeof args.requestId === 'string' ? pendingRequests.get(args.requestId) : undefined;
207
+ const save = priorAttempt ?? (dashboard_receipt_1.DASHBOARD_MUTATION_TOOLS.includes(name) ? evidence.beginSave(creation?.success ? creation.data.supersedesPaths : undefined, name === 'dashboard_create' ? 'draft' : 'source') : undefined);
179
208
  const result = path && (evidence.has(path) || previews.size > 0 && agent_schema_1.htmlPathSchema.safeParse(path).success)
180
209
  ? await evidence.trackWrite(path, async () => handler(request)) : await previewRetries.run(request, handler);
210
+ if (save && 'content' in result && typeof result.content === 'string') {
211
+ try {
212
+ const pending = save_requests_1.saveRequestSchema.extend({ terminal: v3_1.z.boolean() }).passthrough().parse(JSON.parse(result.content));
213
+ if (pending.terminal)
214
+ activeRequests.delete(pending.requestId);
215
+ else {
216
+ pendingRequests.set(pending.requestId, save);
217
+ activeRequests.add(pending.requestId);
218
+ }
219
+ }
220
+ catch { /* normal synchronous result */ }
221
+ }
181
222
  if (save && 'content' in result && (!('status' in result) || result.status !== 'error'))
182
223
  await evidence.accept(save, result.content);
183
224
  if ('content' in result && (!('status' in result) || result.status !== 'error')) {
@@ -201,18 +242,54 @@ let DashboardMiddleware = class DashboardMiddleware {
201
242
  // A noncompliant model must not restart the failed loop even after tools were removed.
202
243
  return result?.tool_calls?.length ? new messages_1.AIMessage(summary) : result;
203
244
  }
204
- const exhausted = ++modelCalls > options.maxModelCalls;
245
+ const exhausted = (observing ? modelCalls : ++modelCalls) > options.maxModelCalls;
205
246
  if (exhausted && evidence.pending)
206
247
  throw new Error('DASHBOARD_SAVE_NOT_CONFIRMED: no successful draft save receipt; Workspace changes have not been saved to the dashboard.');
207
- const result = await handler({ ...request, ...(evidence.pending ? { toolChoice: 'required' } : {}), ...(exhausted ? { tools: [] } : {}), systemMessage: new messages_1.SystemMessage({ content: [
248
+ const result = await handler({ ...request, ...(evidence.pending || activeRequests.size ? { toolChoice: 'required' } : {}), ...(exhausted ? { tools: [] } : {}), systemMessage: new messages_1.SystemMessage({ content: [
208
249
  ...(typeof request.systemMessage?.content === 'string' ? [{ type: 'text', text: request.systemMessage.content }] : request.systemMessage?.content ?? []),
209
- { type: 'text', text: exports.DASHBOARD_GUIDANCE + (editing.current() ? `\nCurrent editing target: ${JSON.stringify(editing.current())}. Continue this Artifact with dashboard_open/update unless the user explicitly requests a new independent dashboard.` : '') + '\n' + definition_tools_1.DEFINITION_GUIDANCE + '\n' + html_guidance_1.HTML_DASHBOARD_GUIDANCE + '\n' + editing_1.EDITING_GUIDANCE + (evidence.pending ? `\nDashboard changes are pending without a matching draft save receipt. Unsaved files: ${JSON.stringify(evidence.pendingPaths)}. Continue reading/repairing and call dashboard_create_html or dashboard_update with the exact file path; do not finish or claim it was saved. When abandoning earlier unsaved HTML creation candidates, explicitly pass their exact paths as supersedesPaths to dashboard_create or dashboard_create_html, matching the requested presentation mode. If a standard dashboard is already saved, repeat dashboard_create with the same definition/previewIds and supersedesPaths; do not create an unwanted HTML dashboard. This only abandons those candidates after a successful save; it never discards another Artifact editing copy.` : '') + (options.allowedFinalProviders ? `\nThis Assistant requires every saved dashboard binding to use one of these providers: ${options.allowedFinalProviders.join(', ')}. Other providers may be previewed only as upstream inputs. Do not save a direct-source substitute.` : '') + (exhausted ? '\nThe dashboard tool budget is exhausted. Stop calls; report only saved receipts or explain the concrete blocker truthfully.' : '') }
250
+ { type: 'text', text: `Current dashboard authoring limit: ${this.service.maxBindings} Bindings (storage maximum 64). Shared charts may reuse one Binding; independent filter-option queries count too. Preview in batches, preserving each receipt and filter check.\n` + exports.DASHBOARD_GUIDANCE + (editing.current() ? `\nCurrent editing target: ${JSON.stringify(editing.current())}. Continue this Artifact with dashboard_open/update unless the user explicitly requests a new independent dashboard.` : '') + '\n' + definition_tools_1.DEFINITION_GUIDANCE + '\n' + html_guidance_1.HTML_DASHBOARD_GUIDANCE + '\n' + html_guidance_1.HTML_QUERY_ROUNDS + '\n' + editing_1.EDITING_GUIDANCE + (activeRequests.size ? `\nDurable save requests pending: ${JSON.stringify([...activeRequests])}. Keep calling dashboard_wait_request with the returned requestId and cursor until terminal. Do not resubmit or claim saved before a dashboard receipt. On a later conversation use dashboard_get_request to recover.` : '') + (evidence.pending ? `\nDashboard changes are pending without a matching draft save receipt. Unsaved files: ${JSON.stringify(evidence.pendingPaths)}. Continue reading/repairing and call dashboard_create_html or dashboard_update with the exact file path; do not finish or claim it was saved. When abandoning earlier unsaved HTML creation candidates, explicitly pass their exact paths as supersedesPaths to dashboard_create or dashboard_create_html, matching the requested presentation mode. If a standard dashboard is already saved, repeat dashboard_create with the same definition/previewIds and supersedesPaths; do not create an unwanted HTML dashboard. This only abandons those candidates after a successful save; it never discards another Artifact editing copy.` : '') + (options.allowedFinalProviders ? `\nThis Assistant requires every saved dashboard binding to use one of these providers: ${options.allowedFinalProviders.join(', ')}. Other providers may be previewed only as upstream inputs. Do not save a direct-source substitute.` : '') + (exhausted ? '\nThe dashboard tool budget is exhausted. Stop calls; report only saved receipts or explain the concrete blocker truthfully.' : '') }
210
251
  ] }) });
211
- if (evidence.pending && result && !result.tool_calls?.length)
252
+ if ((evidence.pending || activeRequests.size) && result && !result.tool_calls?.length)
212
253
  throw new Error('DASHBOARD_SAVE_NOT_CONFIRMED: the model stopped without a successful draft save receipt.');
213
254
  return result;
214
255
  }, tools: [
256
+ ...(this.saveRequests ? [
257
+ (0, tools_1.tool)(async ({ requestId, cursor }, config) => observeRequest(await this.saveRequests.wait(scope, requestId, cursor, config.signal), config), {
258
+ name: 'dashboard_wait_request', description: 'Wait up to 20 seconds for a durable save to change. Repeat with its returned cursor while nextAction=wait. A pending request is not a saved dashboard.', schema: save_requests_1.waitSaveRequestSchema
259
+ }),
260
+ (0, tools_1.tool)(async ({ requestId }, config) => observeRequest(await this.saveRequests.get(scope, requestId), config), {
261
+ name: 'dashboard_get_request', description: 'Recover a prior durable save by its requestId. Do not submit duplicate work after reconnecting.', schema: save_requests_1.saveRequestSchema
262
+ })
263
+ ] : []),
215
264
  filterEvidence.tool,
265
+ (0, tools_1.tool)(async ({ items }) => JSON.stringify({ results: await (0, bounded_map_1.boundedMap)(items, async (item, index) => {
266
+ try {
267
+ return { index, status: 'succeeded', result: await previewRetries.attempt('dashboard_test_filter', item, () => filterEvidence.tool.invoke(item)) };
268
+ }
269
+ catch (error) {
270
+ return { index, status: 'failed', message: error instanceof Error ? error.message : 'DASHBOARD_FILTER_TEST_FAILED' };
271
+ }
272
+ }) }), { name: 'dashboard_test_filter_batch', description: 'Run up to eight required filter checks with four workers. Preserves the exact per-preview filter evidence and retry limits.',
273
+ schema: v3_1.z.object({ items: v3_1.z.array(filter_evidence_1.filterTestSchema).min(1).max(8) }).strict(), verboseParsingErrors: true }),
274
+ (0, tools_1.tool)(async ({ items }) => {
275
+ const results = await (0, bounded_map_1.boundedMap)(items, async (item, index) => {
276
+ try {
277
+ const value = await previewRetries.attempt('dashboard_preview_binding', item, () => preview(plugin_sdk_1.analyticsBindingSchema.parse((0, json_input_1.parseJsonInput)(item.bindingJson, 'bindingJson'))));
278
+ const result = JSON.parse(value), rows = result.rows.slice(0, 5);
279
+ while (Buffer.byteLength(JSON.stringify(rows)) > 8000)
280
+ rows.pop();
281
+ return { index, status: 'succeeded', preview: { previewId: result.previewId, datasetId: result.datasetId, rows,
282
+ completeness: result.completeness, freshness: result.freshness, validation: result.validation,
283
+ preview: { displayedRows: rows.length, omittedRows: result.completeness.returnedRows - rows.length, note: 'Bounded display sample. Binding definition is unchanged from this batch item.' } } };
284
+ }
285
+ catch (error) {
286
+ return { index, status: 'failed', message: error instanceof Error ? error.message : 'DASHBOARD_PREVIEW_FAILED' };
287
+ }
288
+ });
289
+ return JSON.stringify({ results, next: 'Keep every successful previewId. Complete each preview.validation.checks using dashboard_test_filter or dashboard_test_filter_batch. Repair only failed items; never invent a receipt.' });
290
+ }, { name: 'dashboard_preview_batch', description: 'Preview up to eight complete canonical Bindings with at most four concurrent queries. Returns independent receipts and diagnostics; preserves retry and filter-evidence requirements.',
291
+ schema: v3_1.z.object({ items: v3_1.z.array(v3_1.z.object({ bindingJson: v3_1.z.string().min(2).max(200000) }).strict()).min(1).max(8) }).strict(),
292
+ verboseParsingErrors: true, metadata: { toolName: { en_US: 'Verify a batch of sources', zh_Hans: '批量验证看板来源' } } }),
216
293
  ...(0, definition_tools_1.createDefinitionTools)(context, () => { listedSources = undefined; }),
217
294
  (0, tools_1.tool)(async () => JSON.stringify({ libraries: (await (0, document_libraries_1.bundledLibraries)()).map(({ source, ...lock }) => lock),
218
295
  online: { enabled: true, ...document_network_1.DASHBOARD_NETWORK_POLICY,
@@ -222,6 +299,7 @@ let DashboardMiddleware = class DashboardMiddleware {
222
299
  bindingIds: [...new Set([...previews.values()].map(binding => binding.id))],
223
300
  startup: 'const live = window.xpertLiveArtifact; live.subscribe(bindingId, renderDataset); await live.ready(); await live.query(bindingId, typedControls); Use an async function, with the exact binding IDs from saved previews.',
224
301
  refresh: 'live.refresh(bindingId) requires a Binding ID. live.refresh() without an ID is invalid. For a page refresh button or timer, call Promise.all(bindingIds.map(id => live.refresh(id))). Each subscription redraws its KPI, chart and details.',
302
+ batch: 'await live.queryMany(bindingIds, typedControls) returns {datasets, failures}; it commits successful subscriptions together after the group settles. Use for shared filters and initial multi-source rounds. Display unavailable sources from failures, never retain old values as current.',
225
303
  filters: 'Mark native data selects with data-dashboard-control="<exact controlId>". Mark purely visual selects with data-dashboard-presentation instead. Changing a data select must call live.query for every affected Binding, not only filter cached rows in JS. bindSelect updates one Binding only. Read references/html-lifecycle.md before writing the page.'
226
304
  },
227
305
  usage: { 'trend-workspace': 'window.TrendWorkspace: pale-blue KPI rail and aligned trends. Read themes/trend-workspace/README.md.',
@@ -232,7 +310,7 @@ let DashboardMiddleware = class DashboardMiddleware {
232
310
  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.' },
233
311
  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 must load successfully during preflight and viewing; they are not frozen with the Artifact.' }), { name: 'dashboard_libraries', description: 'Discover packaged libraries, themes, exact versions and the current unrestricted network policy. Tailwind CDN and other online dependencies are supported. Returns metadata, never library source.',
234
312
  schema: v3_1.z.object({}).strict(), verboseParsingErrors: true, metadata: { toolName: { en_US: 'Discover dashboard libraries', zh_Hans: '查看看板前端图库' } } }),
235
- ...(0, editing_1.createEditingTools)(this.service, scope, context, resolvePreviews, validateFinal, (path, artifactId) => evidence.opened(path, artifactId), editing.resolve),
313
+ ...(0, editing_1.createEditingTools)(this.service, scope, context, resolvePreviews, validateFinal, (path, artifactId) => evidence.opened(path, artifactId), editing.resolve, this.saveRequests),
236
314
  (0, tools_1.tool)(async () => {
237
315
  listedSources = await this.service.sources(scope);
238
316
  return JSON.stringify(listedSources);
@@ -251,8 +329,8 @@ let DashboardMiddleware = class DashboardMiddleware {
251
329
  return preview(binding);
252
330
  }, { 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.',
253
331
  schema: agent_schema_1.previewToolSchema, verboseParsingErrors: true, metadata: { toolName: { en_US: 'Verify dashboard data', zh_Hans: '预览并验证看板数据' } } }),
254
- (0, tools_1.tool)(async ({ bindingJson }) => preview(plugin_sdk_1.analyticsBindingSchema.parse(JSON.parse(bindingJson))), {
255
- 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.',
332
+ (0, tools_1.tool)(async ({ bindingJson }) => preview(plugin_sdk_1.analyticsBindingSchema.parse((0, json_input_1.parseJsonInput)(bindingJson, 'bindingJson'))), {
333
+ name: 'dashboard_preview_binding', description: 'Preview a complete canonical AnalyticsBinding returned by another tool. When editing an existing dashboard, copy dashboard_open.bindings[], preserve binding.id and existing parameters[].controlId, and change only the requested query/parameters. Binding ID is independent of output.datasetId. Serialize the complete Binding (also supported: 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, which derives binding.id from output.datasetId. Automatically discovers the source/provider query contract when absent in this run. Returns a current-run previewId for dashboard_create or dashboard_update; include every intended changed/new Binding receipt and matching controls in the same save.',
256
334
  schema: v3_1.z.object({ bindingJson: v3_1.z.string().min(2).max(200000) }).strict(), verboseParsingErrors: true,
257
335
  metadata: { toolName: { en_US: 'Verify registered dataset', zh_Hans: '验证已注册数据集' } }
258
336
  }),
@@ -270,7 +348,7 @@ let DashboardMiddleware = class DashboardMiddleware {
270
348
  throw new Error('DASHBOARD_HTML_TOO_LARGE');
271
349
  const html = artifact_document_1.artifactDocumentHtml.parse(new TextDecoder('utf-8', { fatal: true }).decode(file.buffer));
272
350
  const { path, libraries, supersedesPaths, ...definition } = input;
273
- const result = JSON.parse(await save({ ...definition, charts: [], tables: [] }, config, html, await (0, document_libraries_1.libraryLocks)(libraries ?? [])));
351
+ const result = JSON.parse(await save({ ...definition, charts: [], tables: [] }, config, html, await (0, document_libraries_1.libraryLocks)(libraries ?? []), { path, sha256: (0, node_crypto_1.createHash)('sha256').update(file.buffer).digest('hex') }));
274
352
  return JSON.stringify({ ...result, sourceFile: { path, sha256: (0, node_crypto_1.createHash)('sha256').update(file.buffer).digest('hex') } });
275
353
  }, { name: 'dashboard_create_html', description: 'Save custom HTML from a Workspace path together with run-local preview receipts and controls in one persistent draft (human publication is separate). Requires real data preview; does not accept query payloads or cached rows.',
276
354
  schema: agent_schema_1.createHtmlToolSchema, verboseParsingErrors: true,
@@ -281,7 +359,8 @@ let DashboardMiddleware = class DashboardMiddleware {
281
359
  exports.DashboardMiddleware = DashboardMiddleware;
282
360
  exports.DashboardMiddleware = DashboardMiddleware = __decorate([
283
361
  (0, plugin_sdk_1.AgentMiddlewareStrategy)('LiveDashboard'),
284
- __metadata("design:paramtypes", [dashboard_service_1.DashboardService])
362
+ __param(1, (0, common_1.Optional)()),
363
+ __metadata("design:paramtypes", [dashboard_service_1.DashboardService, save_requests_1.DashboardSaveRequests])
285
364
  ], DashboardMiddleware);
286
365
  function previewKey(binding, controls) {
287
366
  return hash({ binding, controls: Object.fromEntries(binding.parameters.map((parameter) => [parameter.controlId, Object.hasOwn(controls, parameter.controlId) ? controls[parameter.controlId] : parameter.defaultValue ?? null])) });
@@ -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 save_requests_1 = require("./save-requests");
11
+ const archive_service_1 = require("./archive.service");
10
12
  const projects_service_1 = require("./projects.service");
11
13
  const content_capability_1 = require("./content-capability");
12
14
  const monitor_1 = require("./monitor");
@@ -24,5 +26,5 @@ let LiveDashboardPlugin = class LiveDashboardPlugin {
24
26
  };
25
27
  exports.LiveDashboardPlugin = LiveDashboardPlugin;
26
28
  exports.LiveDashboardPlugin = LiveDashboardPlugin = __decorate([
27
- (0, plugin_sdk_1.XpertServerPlugin)({ controllers: [provider_options_controller_1.DashboardProviderOptionsController], providers: [projects_service_1.DashboardProjectsService, monitor_1.DashboardMonitor, monitor_1.DashboardMonitorMiddleware, artifact_type_provider_1.DashboardArtifactType, 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] })
29
+ (0, plugin_sdk_1.XpertServerPlugin)({ controllers: [provider_options_controller_1.DashboardProviderOptionsController], providers: [save_requests_1.DashboardSaveRequests, archive_service_1.DashboardArchiveService, projects_service_1.DashboardProjectsService, monitor_1.DashboardMonitor, monitor_1.DashboardMonitorMiddleware, artifact_type_provider_1.DashboardArtifactType, 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] })
28
30
  ], LiveDashboardPlugin);
@@ -11,15 +11,10 @@ export declare class DashboardPresentationProvider implements IArtifactPresentat
11
11
  read(context: ArtifactPresentationContext): Promise<{
12
12
  meta: {
13
13
  saved: {
14
- artifactId: string;
15
- versionId: string;
16
- versionNumber: number;
17
- title: string;
18
- isDraft: boolean;
19
14
  manifest: {
20
- schema: "xpert.live-dashboard";
21
- id: string;
22
15
  title: string;
16
+ id: string;
17
+ schema: "xpert.live-dashboard";
23
18
  bindings: import("@xpert-ai/contracts").AnalyticsBinding[];
24
19
  controls: {
25
20
  type: "string" | "number" | "boolean" | "date";
@@ -30,25 +25,45 @@ export declare class DashboardPresentationProvider implements IArtifactPresentat
30
25
  }[];
31
26
  charts: {
32
27
  type: "bar" | "line" | "area" | "pie" | "scatter";
33
- id: string;
34
28
  title: string;
29
+ id: string;
35
30
  datasetId: string;
36
31
  x: string;
37
32
  y: string;
38
33
  color?: string | undefined;
39
34
  }[];
40
35
  tables: {
41
- id: string;
42
36
  title: string;
37
+ id: string;
43
38
  datasetId: string;
44
39
  fields: string[];
45
40
  }[];
41
+ sourceMetadata?: {
42
+ bindingId: string;
43
+ title: string;
44
+ description?: string | undefined;
45
+ }[] | undefined;
46
+ components?: {
47
+ title: string;
48
+ id: string;
49
+ elementId: string;
50
+ kind: "filter" | "metric" | "chart" | "table" | "text";
51
+ sources: {
52
+ bindingId: string;
53
+ fieldIds?: string[] | undefined;
54
+ }[];
55
+ }[] | undefined;
46
56
  presentation?: {
47
57
  type: "html";
48
58
  html: string;
49
59
  dependencies?: import("@xpert-ai/plugin-sdk/artifact-document").ArtifactDocumentDependencyLock[] | undefined;
50
60
  } | undefined;
51
61
  };
62
+ artifactId: string;
63
+ versionId: string;
64
+ versionNumber: number;
65
+ title: string;
66
+ isDraft: boolean;
52
67
  dependencies: import("@xpert-ai/plugin-sdk/artifact-document").ArtifactDocumentDependencyAsset[];
53
68
  };
54
69
  dashboards: {
@@ -22,6 +22,7 @@ const remote_entry_1 = require("./remote-entry");
22
22
  const dashboard_schema_1 = require("./dashboard.schema");
23
23
  const dashboard_queries_1 = require("./dashboard-queries");
24
24
  const renderer_1 = require("./renderer");
25
+ const dashboard_sources_1 = require("./dashboard-sources");
25
26
  let DashboardPresentationProvider = class DashboardPresentationProvider {
26
27
  constructor(runtime) {
27
28
  this.runtime = runtime;
@@ -30,7 +31,7 @@ let DashboardPresentationProvider = class DashboardPresentationProvider {
30
31
  getEntry() { return (0, remote_entry_1.dashboardRemoteEntry)(true); }
31
32
  async read(context) {
32
33
  const saved = this.open(context);
33
- return { meta: { saved, dashboards: [{ artifactId: saved.artifactId, versionId: saved.versionId, title: saved.title }], sources: [], versions: [] } };
34
+ return { meta: { saved: { ...saved, manifest: (0, dashboard_sources_1.publicDashboardManifest)(saved.manifest) }, dashboards: [{ artifactId: saved.artifactId, versionId: saved.versionId, title: saved.title }], sources: [], versions: [] } };
34
35
  }
35
36
  async query(context, request) {
36
37
  const saved = this.open(context);
@@ -1,7 +1,8 @@
1
1
  import type { AgentBuiltInState, ToolCallHandler, ToolCallRequest } from '@xpert-ai/plugin-sdk';
2
2
  /** Run-local bounds include tool-schema failures, before any discovery or query executes. */
3
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>>;
4
+ exhausted: boolean;
5
+ summary: string;
6
+ run(request: ToolCallRequest<AgentBuiltInState>, handler: ToolCallHandler): import("@xpert-ai/plugin-sdk").PromiseOrValue<import("@langchain/core/messages").ToolMessage | import("@langchain/langgraph").Command<unknown, Record<string, unknown>, string>>;
7
+ attempt<T>(name: string, args: unknown, handler: () => Promise<T>): Promise<T>;
7
8
  };
@@ -12,13 +12,11 @@ function createPreviewRetryGuard() {
12
12
  consecutiveFailures++;
13
13
  lastError = message.slice(0, 2000);
14
14
  };
15
- return {
15
+ const guard = {
16
16
  get exhausted() { return consecutiveFailures >= 6; },
17
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');
18
+ async attempt(name, args, handler) {
19
+ const key = (0, node_crypto_1.createHash)('sha256').update(JSON.stringify({ name, 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
20
  if (consecutiveFailures >= 6)
23
21
  throw new Error(this.summary);
24
22
  if ((failures.get(key)?.count ?? 0) >= 2) {
@@ -30,8 +28,8 @@ function createPreviewRetryGuard() {
30
28
  throw new Error('DASHBOARD_PREVIEW_IN_PROGRESS: wait for the identical preview already in progress; do not submit duplicates.');
31
29
  pending.add(key);
32
30
  try {
33
- const result = await handler(request);
34
- if ('status' in result && result.status === 'error' && 'content' in result) {
31
+ const result = await handler();
32
+ if (result && typeof result === 'object' && 'status' in result && result.status === 'error' && 'content' in result) {
35
33
  recordFailure(key, typeof result.content === 'string' ? result.content : 'Preview tool returned an error');
36
34
  }
37
35
  else {
@@ -49,4 +47,12 @@ function createPreviewRetryGuard() {
49
47
  }
50
48
  }
51
49
  };
50
+ return { ...guard,
51
+ get exhausted() { return guard.exhausted; },
52
+ get summary() { return guard.summary; },
53
+ run(request, handler) {
54
+ return PREVIEW_TOOLS.has(request.toolCall.name)
55
+ ? guard.attempt(request.toolCall.name, request.toolCall.args, async () => handler(request)) : handler(request);
56
+ }
57
+ };
52
58
  }