@xpert-ai/plugin-live-dashboard 0.1.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/.xpertai-plugin/plugin.json +1 -1
  2. package/README.md +77 -4
  3. package/dist/lib/content-capability.d.ts +22 -0
  4. package/dist/lib/content-capability.js +33 -0
  5. package/dist/lib/content.provider.d.ts +23 -0
  6. package/dist/lib/content.provider.js +163 -0
  7. package/dist/lib/dashboard-navigation.d.ts +24 -0
  8. package/dist/lib/dashboard-navigation.js +1 -1
  9. package/dist/lib/dashboard-queries.d.ts +11 -0
  10. package/dist/lib/dashboard-queries.js +20 -0
  11. package/dist/lib/dashboard-search.d.ts +5 -0
  12. package/dist/lib/dashboard-search.js +12 -0
  13. package/dist/lib/dashboard-view.schema.d.ts +27 -0
  14. package/dist/lib/dashboard-view.schema.js +1 -1
  15. package/dist/lib/dashboard.schema.js +4 -2
  16. package/dist/lib/dashboard.service.d.ts +13 -0
  17. package/dist/lib/dashboard.service.js +69 -16
  18. package/dist/lib/document-host.d.ts +4 -0
  19. package/dist/lib/document-host.js +13 -0
  20. package/dist/lib/document-network.d.ts +3 -0
  21. package/dist/lib/document-network.js +9 -0
  22. package/dist/lib/html-guidance.d.ts +1 -1
  23. package/dist/lib/html-guidance.js +1 -1
  24. package/dist/lib/middleware.js +49 -15
  25. package/dist/lib/plugin.js +3 -1
  26. package/dist/lib/presentation.provider.d.ts +5 -0
  27. package/dist/lib/presentation.provider.js +2 -2
  28. package/dist/lib/preview-retry.d.ts +7 -0
  29. package/dist/lib/preview-retry.js +52 -0
  30. package/dist/lib/prompt-workflows.d.ts +3 -0
  31. package/dist/lib/prompt-workflows.js +118 -0
  32. package/dist/lib/renderer.js +5 -0
  33. package/dist/lib/save-evidence.d.ts +1 -1
  34. package/dist/lib/sharing.service.js +2 -0
  35. package/dist/lib/view.provider.d.ts +2 -117
  36. package/dist/lib/view.provider.js +12 -4
  37. package/dist/remote/app.css +1 -1
  38. package/dist/remote/app.js +36 -36
  39. package/dist/remote/build-manifest.json +38 -28
  40. package/dist/sandbox-actions/action.json +1 -1
  41. package/dist/sandbox-actions/bundle/host.js +2 -2
  42. package/dist/sandbox-actions/bundle/resource-cache.mjs +39 -0
  43. package/dist/sandbox-actions/bundle/validate.mjs +106 -17
  44. package/dist/validation/host-entry.d.ts +3 -1
  45. package/dist/validation/host-entry.js +5 -2
  46. package/docs/drafts.mdx +1 -1
  47. package/docs/frontend-libraries.mdx +2 -2
  48. package/docs/html-mode.md +3 -3
  49. package/docs/team-sharing.mdx +1 -1
  50. package/docs/verification.mdx +3 -1
  51. package/package.json +6 -6
  52. package/skills/live-dashboard/SKILL.md +1 -1
  53. package/skills/live-dashboard/references/data-validation.md +4 -0
  54. package/skills/live-dashboard/references/html-runtime.md +1 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xpert-ai/plugin-live-dashboard",
3
- "version": "0.1.4",
3
+ "version": "0.3.0",
4
4
  "level": "system",
5
5
  "artifactNamespace": "live_dashboard",
6
6
  "sandboxActions": "./dist/sandbox-actions/action.json",
package/README.md CHANGED
@@ -31,7 +31,7 @@ The Workbench supplies its selected Artifact/version through the platform contex
31
31
  update preserves identity and omitted fields, requires fresh preview receipts for replacement bindings, and uses the existing validation/CAS path.
32
32
  Successful update receipts open the saved working draft; only human publication creates a formal version. See [continuous editing](docs/editing.mdx).
33
33
 
34
- Tools are `dashboard_list_sources`, `dashboard_discover`, `dashboard_preview`, `dashboard_preview_binding`, `dashboard_create`, `dashboard_open`, `dashboard_update` and `dashboard_create_html`. File authoring is supplied by the platform SandboxFile middleware. The canonical preview tool accepts complete `bindingJson` returned by an adapter registration tool. Both preview tools use the same discovery and receipt checks. The middleware adds business-semantic guidance while preserving the host system message. It requires discovery and a successful preview of the exact binding/defaults before creation via run-local `previewId` receipts; save revalidates current source access. Evidence is local to the run and actor, and repeated creation of the same manifest rechecks authorization before reusing its Artifact.
34
+ Tools are `dashboard_list_sources`, `dashboard_discover`, `dashboard_preview`, `dashboard_preview_binding`, `dashboard_create`, `dashboard_open`, `dashboard_update` and `dashboard_create_html`. File authoring is supplied by the platform SandboxFile middleware. The canonical preview tool accepts complete `bindingJson` returned by an adapter registration tool. Both preview tools automatically discover the query contract with an empty request when it is absent in this run. Contracts are cached by exact source (including version) and provider; concurrent previews share discovery, and discovery failures propagate without executing the query and can be retried. Explicit discovery remains available for business metadata inspection. Source selection, payload validation and runtime authorization still apply. The middleware adds business-semantic guidance while preserving the host system message. Creation requires a successful preview of the exact binding/defaults via run-local `previewId` receipts; save revalidates current source access. Evidence is local to the run and actor, and repeated creation of the same manifest rechecks authorization before reusing its Artifact.
35
35
 
36
36
  Specialized Assistants may configure `allowedFinalProviders` on the middleware, for example `["workspace_script.query"]`. Upstream discovery and preview still work with other providers, but Agent save rejects final bindings outside this list. Omit it for the existing unrestricted provider workflow. This is an Agent workflow policy, not a substitute for the server's source/Artifact authorization.
37
37
 
@@ -39,7 +39,7 @@ For a dedicated Dashboard Assistant, mark its connected `LiveDashboard` middlewa
39
39
 
40
40
  Only the LLM boundary uses `requestJson` / `payloadJson` strings, because recursive JSON unions are unreliable in model function schemas. The plugin parses these into the unchanged canonical JSON protocol before calling the platform. Protocol version and operation come from the discovered provider contract; parameter name also supplies the Agent-created control ID. Saved manifests retain the full generic Binding protocol. Preview uses Binding parameter defaults, avoiding a second control mapping for the model to repeat. Provider syntax is learned from discovery, not embedded in platform prompts.
41
41
 
42
- The model gets at most 20 preview rows/32 KB, with original completeness and explicit omitted-row counts. It has 24 model tool rounds; after that it must report saved receipts or a blocker. Authored definitions, units, date range and aggregation grain still require business verification: structural validation alone does not prove metric correctness.
42
+ The model gets at most 20 preview rows/32 KB, with original completeness and explicit omitted-row counts. Failed preview/filter-test inputs execute at most twice per run; repeated identical failures are blocked before querying. Six consecutive preview failures stop tool use and request a truthful blocker summary; a model that still emits tool calls receives a terminal diagnostic instead. Schema failures count too. Successful previews reset the consecutive-failure count, and corrected inputs can be tried before the limit. The model also has 24 model tool rounds; after that it must report saved receipts or a blocker. Authored definitions, units, date range and aggregation grain still require business verification: structural validation alone does not prove metric correctness.
43
43
 
44
44
  Categorical charts must encode every Dataset grain dimension in `x`/`color`. The renderer does not aggregate: totals need a separate previewed binding. Unsupported business metrics require clarification, not a proxy dashboard without agreement.
45
45
 
@@ -57,13 +57,78 @@ Preview/save also probe a declared non-default filter value and, when applicable
57
57
 
58
58
  Artifacts are owner-only within their agent/project host for this MVP. MIME: `application/json`; resource type: `live-dashboard`; namespace: `live_dashboard`. The Workbench supports selection, JSON import/edit, typed filters, refresh, version/freshness and partial-result notices.
59
59
 
60
+ ## Built-in prompt workflows / 内置提示词模板
61
+
62
+ When the Assistant has the `LiveDashboard` middleware, type `/dashboard-` in its
63
+ chat composer and select a workflow. Selection inserts an invocation so the user
64
+ can add requirements before sending; it does not submit or publish automatically.
65
+ Commands are contributed by the installed middleware, so existing Assistants can
66
+ use them after the updated plugin is loaded. No Assistant-template import or
67
+ database seeding is needed. The host must support middleware slash commands.
68
+
69
+ | Mode / 分类 | Command | 用途 |
70
+ | --- | --- | --- |
71
+ | Standard | `/dashboard-standard-auto` | 根据真实数据质量和业务价值自动选题,新建独立标准看板 |
72
+ | Standard | `/dashboard-standard-new` | 围绕指定主题新建标准看板 |
73
+ | Standard | `/dashboard-standard-improve` | 调整指定看板的图表、指标、筛选,保留 Artifact 身份 |
74
+ | Standard | `/dashboard-standard-recover` | 定位失败步骤,用本轮证据恢复标准草稿保存 |
75
+ | HTML | `/dashboard-html-auto` | 自动选题,新建布局精致、可实际使用的 HTML 看板 |
76
+ | HTML | `/dashboard-html-new` | 围绕指定业务问题、受众和视觉要求新建 HTML 看板 |
77
+ | HTML | `/dashboard-html-style` | 参考图片或风格描述美化已有 HTML 看板,保留查询与交互 |
78
+ | HTML | `/dashboard-html-recover` | 保留页面候选,按真实校验错误修复并恢复保存 |
79
+ | Inspect | `/dashboard-explore` | 只读探索数据源、字段、日期覆盖和候选业务主题 |
80
+ | Inspect | `/dashboard-test-source` | 最小真实查询、字段映射和筛选验证 |
81
+ | Inspect | `/dashboard-audit-metrics` | 只读核对计算、单位、期间和关联粒度 |
82
+ | Inspect | `/dashboard-verify` | 核对保存版本、筛选、刷新,并标明未执行的页面验收 |
83
+
84
+ Examples (replace the descriptive inputs with your own requirements):
85
+
86
+ ```text
87
+ /dashboard-standard-auto 使用当前组织的 BI 数据源,为采购主管挑选一个数据完整、能帮助识别交付问题的主题。新建独立看板。
88
+ /dashboard-standard-new 使用库存数据,面向仓库主管创建库存积压看板。核实库龄和库存金额口径,提供仓库与日期筛选。
89
+ /dashboard-html-auto 从已授权数据中选择一个能支持日常运营决策的主题。面向部门负责人,浅色企业风格,摘要指标、主趋势、关注清单和明细。
90
+ /dashboard-html-new 创建供应商交付分析看板。使用已授权采购数据,核实交付及时率口径;参考附图的留白、层次和配色,不复制示例数字。
91
+ /dashboard-html-style 美化当前选中的 HTML 看板,参考附图布局;保留现有指标、绑定、筛选和刷新,只调整视觉。
92
+ /dashboard-html-recover 继续保存刚才失败的 HTML 候选。读取实际文件与错误,重新获取本轮所需预览;如果运行时仍不可用,报告准确阻塞。
93
+ /dashboard-test-source 检查当前采购数据源的一个有实际数据的查询,验证一个供应商筛选及全部供应商。
94
+ /dashboard-verify 检查当前看板的默认日期、一个真实筛选值和全部值;区分查询验证与待人工页面验证。
95
+ ```
96
+
97
+ Provide a source name, business question, audience and date range where relevant;
98
+ HTML requests can include a reference image or style. IDs, provider contracts and
99
+ paths are discovered from tools, never hard-coded into the templates. Automatic
100
+ selection ranks business usefulness and data readiness; specified-topic workflows
101
+ do not silently substitute another topic. New HTML workflows choose an unused
102
+ file path and never overwrite the selected dashboard.
103
+
104
+ The templates guide real queries, metric checks, run-local previews/filter tests
105
+ and successful draft-save receipts. They do not grant source access or install a
106
+ missing Browser Runtime. HTML saves still require runtime validation. Native
107
+ dashboards keep their own renderer and supported chart/table schema. Formal
108
+ publication remains a human action. Inspection commands do not register
109
+ definitions, edit business data or save dashboards; a tool-level pass is not a
110
+ claim of visual acceptance.
111
+
112
+ ### Manual acceptance after loading the plugin
113
+
114
+ 1. In an Assistant with LiveDashboard, open the `/` picker and search `dashboard`.
115
+ Verify all twelve localized entries; selecting one should leave an editable
116
+ invocation in the composer.
117
+ 2. Run one standard-new and one HTML-new request against an authorized test source.
118
+ Verify independent Artifact IDs, fresh previews and successful draft receipts;
119
+ HTML also requires its normal Browser Runtime check.
120
+ 3. Reopen each saved draft, change a real filter and refresh. Confirm real data
121
+ updates. Run an inspection command and verify it does not save a new revision.
122
+
60
123
  ## HTML mode
61
124
 
62
125
  See [HTML mode and acceptance](docs/html-mode.md). Agent tools write a complete single-file document to the authorized file workspace, then read and copy its bytes into the immutable dashboard JSON version alongside Bindings and Controls. Before creating an Artifact, a plugin-owned Sandbox Action runs the page with real preview datasets in the platform Browser Runtime, through the sandbox-browser queue. Unavailable runtime or failed initial rendering blocks HTML save and returns diagnostics to the Agent. No live workspace path is dereferenced on reopening. A changed file requires a new saved version. Existing JSON-only dashboards remain compatible.
63
126
 
64
127
  The platform browser entry `@xpert-ai/plugin-sdk/artifact-document` owns the sandbox, CSP, MessageChannel, request validation and latest-response policy. The plugin owns lifecycle and supplies a narrow callback to `query-binding`. Its server loads the exact saved Artifact version, finds the declared Binding, validates controls and rechecks source access. The endpoint rejects full Binding, Provider, SQL/payload and alternate source fields.
65
128
 
66
- Pages call `window.xpertLiveArtifact.ready()`, `subscribe(bindingId, render)`, `query(bindingId, controls)` and `refresh(bindingId)`. Register subscriptions before the first query so the toolbar can refresh without resetting the document. Use inline DOM/SVG/Canvas; network/CDN imports and eval are unavailable. Script processing remains in the separate Workspace Script provider.
129
+ HTML preflight checks each native select's alternative/restored values and each button path in a fresh document mount, with at most two pages active. A button path also clicks controls it inserts, reveals or enables, retaining their prerequisite state and the same query quota; traversal is bounded to the first 12 buttons and 12 clicks per path. Every scenario checks initial bindings and host refresh using the same preview datasets. Successful static resources can be reused within this validation (bounded to 32 MB, up to 8 MB per resource); every replay still passes the resource policy, and failed, redirected or non-cacheable responses are not replayed. A 55-second validation deadline fails closed and cancels remaining pages before the host's 60-second Action limit. This prevents repeated resource downloads and the synthetic interaction sweep from exhausting the Action budget or one document's rate window without raising the SDK's query/concurrency limits. A request storm within a scenario still blocks saving; preflight is not a full combinatorial or sustained-load test.
130
+
131
+ Pages call `window.xpertLiveArtifact.ready()`, `subscribe(bindingId, render)`, `query(bindingId, controls)` and `refresh(bindingId)`. Register subscriptions before the first query so the toolbar can refresh without resetting the document. Use inline DOM/SVG/Canvas or online scripts/styles/fonts from the HTTPS origins returned by `dashboard_libraries.online`. Tailwind CDN, jsDelivr, cdnjs, unpkg and Google Fonts are supported according to their resource-type allowlist. Preflight and the displayed page use the same policy; failed loads prevent saving. Pin online versions where possible. Unlike packaged libraries, online bytes are not part of the immutable Artifact snapshot. Direct data requests, external images, eval, workers and frames remain unavailable. Script processing remains in the separate Workspace Script provider.
67
132
 
68
133
  HTML is capped at 300 KB; the whole saved definition remains capped at 600 KB. HTML pages own their chart layout, so their charts/tables arrays are empty. The standard renderer remains an independent option. Sharing, data permissions and source limits are unchanged.
69
134
 
@@ -75,7 +140,7 @@ The renderer runs in a script-only sandbox with no-network CSP. A small in-memor
75
140
 
76
141
  ## Build and validation
77
142
 
78
- Use the xpertai workspace's declared Corepack/pnpm. Development dependencies pin published SDK/contracts 3.26.1 and the Data Analytics renderer 0.1.35; no local platform or community links are required.
143
+ Use the xpertai workspace's declared Corepack/pnpm. Development dependencies pin published SDK/contracts 3.27.0 and the Data Analytics renderer 0.1.35; no local platform or community links are required. SDK 3.27.0 includes the host-owned online resource policy, so no SDK patch is required. Rebuild both the viewer and HTML preflight assets after changing the SDK; `verify:dist` checks their source fingerprints.
79
144
 
80
145
  ```sh
81
146
  corepack pnpm install --frozen-lockfile
@@ -107,3 +172,11 @@ The Share action publishes a link to an already published dashboard: follow the
107
172
  See [team sharing and standalone pages](docs/team-sharing.mdx) and [local acceptance](docs/team-sharing-acceptance.mdx). This feature needs a host exposing the Artifact Presentation registry, API and page; it cannot be installed on an older SDK alone.
108
173
 
109
174
  Chat messages use the platform resource-card protocol after a successful create, update or open. The card opens the exact dashboard in Workbench; no dashboard renderer or queries are embedded in messages. See [chat resource cards](docs/chat-resource-cards.mdx).
175
+
176
+ ### Delete a saved dashboard
177
+
178
+ Open the dashboard picker and use the trash icon beside an entry. The confirmation shows its title and Artifact ID, so same-name dashboards can be distinguished. Deletion recycles the whole dashboard (draft and published versions), removes it from the active list and revokes shared links; it does not delete source data or Assistant workspace files. Deleting the selected dashboard clears its preview and Assistant context; deleting another dashboard preserves the current view.
179
+
180
+ The owner can delete private Assistant dashboards; Project dashboards require the current user's management permission. Read-only shared pages expose no delete control. The platform checks the confirmed draft revision atomically, rejecting concurrent edits. Entries without a saved working draft (legacy version-only or incomplete first saves) have no recycle revision and do not expose this action. No Agent deletion tool is registered.
181
+
182
+ Manual acceptance after installing the updated plugin: cancel a deletion and confirm the entry remains; delete an unused saved dashboard and confirm it stays absent after reload; delete the selected dashboard and confirm the preview clears and its old shared link is unavailable.
@@ -0,0 +1,22 @@
1
+ import { ModelFeature, type TXpertTeamDraft } from '@xpert-ai/contracts';
2
+ import { type IAssistantCapabilityProvider, type AssistantCapabilityApplyContext } from '@xpert-ai/plugin-sdk';
3
+ /** Optional tool composition; installing a View never silently enables these tools. */
4
+ export declare class DashboardCapability implements IAssistantCapabilityProvider {
5
+ readonly key = "live-dashboard";
6
+ readonly availableForBlankAssistant = true;
7
+ readonly supportsQuickCreation = true;
8
+ readonly requiredModelFeatures: ModelFeature[];
9
+ readonly label: {
10
+ en_US: string;
11
+ zh_Hans: string;
12
+ };
13
+ readonly description: {
14
+ en_US: string;
15
+ zh_Hans: string;
16
+ };
17
+ isEnabled(draft: TXpertTeamDraft): boolean;
18
+ check(): Promise<{
19
+ available: boolean;
20
+ }>;
21
+ apply({ draft }: AssistantCapabilityApplyContext): Promise<void>;
22
+ }
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.DashboardCapability = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ const contracts_1 = require("@xpert-ai/contracts");
12
+ const plugin_sdk_1 = require("@xpert-ai/plugin-sdk");
13
+ /** Optional tool composition; installing a View never silently enables these tools. */
14
+ let DashboardCapability = class DashboardCapability {
15
+ constructor() {
16
+ this.key = 'live-dashboard';
17
+ this.availableForBlankAssistant = true;
18
+ this.supportsQuickCreation = true;
19
+ this.requiredModelFeatures = [contracts_1.ModelFeature.TOOL_CALL];
20
+ this.label = { en_US: 'Live dashboards', zh_Hans: '实时看板' };
21
+ this.description = { en_US: 'Create and maintain live dashboards with this Assistant in a Project.', zh_Hans: '为 Assistant 增加实时看板的创建与维护工具,沿用项目权限。' };
22
+ }
23
+ isEnabled(draft) { return (0, plugin_sdk_1.hasAssistantMiddleware)(draft, 'LiveDashboard'); }
24
+ async check() { return { available: true }; }
25
+ async apply({ draft }) {
26
+ (0, plugin_sdk_1.applyAssistantMiddleware)(draft, { provider: 'LiveDashboard', key: 'Capability_DashboardCapability', title: 'Live dashboards', instructions: 'Maintain live dashboard definitions in the current Project using Live Dashboard tools and registered Analytics providers. Discover and validate current data bindings before saving. Read the current draft revision and handle conflicts without overwriting another editor. Query with the current actor permissions; never copy credentials or query rows into the shared definition. Publishing a definition is separate from saving and refreshing data.' });
27
+ }
28
+ };
29
+ exports.DashboardCapability = DashboardCapability;
30
+ exports.DashboardCapability = DashboardCapability = __decorate([
31
+ (0, common_1.Injectable)(),
32
+ (0, plugin_sdk_1.AssistantCapabilityProvider)('live-dashboard')
33
+ ], DashboardCapability);
@@ -0,0 +1,23 @@
1
+ import type { ResourceContentEditorRegistration } from '@xpert-ai/contracts';
2
+ import { type RuntimeCapabilityRegistry, type ContentCatalogProvider, type ContentResourceRequest, type IContentResourceProvider } from '@xpert-ai/plugin-sdk';
3
+ import { DashboardService } from './dashboard.service';
4
+ export declare const DASHBOARD_CONTENT_PROVIDER = "live-dashboard.content";
5
+ export declare class DashboardContentEditor {
6
+ readonly descriptor: ResourceContentEditorRegistration;
7
+ }
8
+ export declare class DashboardContentProvider implements IContentResourceProvider {
9
+ private readonly service;
10
+ private readonly runtime;
11
+ readonly title = "Live Dashboard";
12
+ constructor(service: DashboardService, runtime: RuntimeCapabilityRegistry);
13
+ readonly catalog: ContentCatalogProvider;
14
+ authorize(input: ContentResourceRequest): Promise<{
15
+ providerKey: string;
16
+ resourceId: string;
17
+ title: string;
18
+ role: "owner" | "manager" | "editor" | "member";
19
+ canWrite: boolean;
20
+ canManage: boolean;
21
+ }>;
22
+ private describe;
23
+ }
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.DashboardContentProvider = exports.DashboardContentEditor = exports.DASHBOARD_CONTENT_PROVIDER = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const plugin_sdk_1 = require("@xpert-ai/plugin-sdk");
18
+ const dashboard_service_1 = require("./dashboard.service");
19
+ const dashboard_search_1 = require("./dashboard-search");
20
+ exports.DASHBOARD_CONTENT_PROVIDER = 'live-dashboard.content';
21
+ const MODEL = { typeId: 'xpert.live-dashboard', modelId: 'live-dashboard.manifest', schemaVersion: '1', editProtocol: { id: 'artifact-draft.cas', version: '1' } };
22
+ const EDITOR = 'live-dashboard.editor';
23
+ const context = (scope) => ({ ...scope.actor, hostType: 'project', hostId: scope.projectId, slots: [] });
24
+ let DashboardContentEditor = class DashboardContentEditor {
25
+ constructor() {
26
+ this.descriptor = { kind: 'resource', protocolVersion: 2, key: EDITOR, title: 'Live Dashboard',
27
+ view: { providerKey: 'live-dashboard', viewKey: 'live-dashboard', entry: 'live-dashboard' }, supports: [{ ...MODEL, access: ['view', 'edit'] }] };
28
+ }
29
+ };
30
+ exports.DashboardContentEditor = DashboardContentEditor;
31
+ exports.DashboardContentEditor = DashboardContentEditor = __decorate([
32
+ (0, common_1.Injectable)(),
33
+ (0, plugin_sdk_1.ContentEditorProvider)(EDITOR)
34
+ ], DashboardContentEditor);
35
+ let DashboardContentProvider = class DashboardContentProvider {
36
+ constructor(service, runtime) {
37
+ this.service = service;
38
+ this.runtime = runtime;
39
+ this.title = 'Live Dashboard';
40
+ this.catalog = {
41
+ types: [{ ...MODEL, title: { en_US: 'Live dashboards', zh_Hans: '实时看板' }, implementationKey: 'live-dashboard',
42
+ implementationTitle: { en_US: 'Live Dashboard', zh_Hans: '实时看板' }, defaultEditorKey: EDITOR }],
43
+ discover: async (scope) => {
44
+ await this.service.authorizeProject(context(scope));
45
+ const page = Number(scope.cursor ?? 1);
46
+ if (!Number.isSafeInteger(page) || page < 1)
47
+ throw new common_1.BadRequestException('DASHBOARD_INVALID_CURSOR');
48
+ const result = await this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability).listArtifacts({ pluginName: dashboard_service_1.PLUGIN_NAME,
49
+ resourceType: 'live-dashboard', resourceIdPrefix: `project/${scope.projectId}/`, page, pageSize: 100, status: 'all' });
50
+ return { resourceIds: result.items.filter(item => !item.userId && item.projectId === scope.projectId && item.status !== 'deleted').map(item => item.id),
51
+ ...(result.items.length === 100 ? { nextCursor: String(page + 1) } : {}) };
52
+ },
53
+ describe: input => this.describe(input),
54
+ extract: async (input) => {
55
+ const saved = await this.service.open(context(input), input.sourceResourceId);
56
+ return (0, dashboard_search_1.dashboardSearchProjection)(saved.manifest, String(saved.draftRevision ?? 0));
57
+ },
58
+ create: async (input) => {
59
+ if (input.templateId)
60
+ throw new common_1.BadRequestException('DASHBOARD_TEMPLATE_UNAVAILABLE');
61
+ const ctx = context(input);
62
+ await this.service.authorizeProject(ctx, 'write');
63
+ const source = { pluginName: dashboard_service_1.PLUGIN_NAME, resourceType: 'live-dashboard', resourceId: `project/${input.projectId}/${input.requestId}` };
64
+ const existing = await this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability).findArtifactBySource(source);
65
+ if (existing && await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).get(existing.id))
66
+ return { providerKey: exports.DASHBOARD_CONTENT_PROVIDER, resourceId: existing.id };
67
+ try {
68
+ const created = await this.service.save(ctx, { schema: 'xpert.live-dashboard', id: input.requestId, title: input.title,
69
+ bindings: [], controls: [], charts: [], tables: [] }, null, 0);
70
+ return { providerKey: exports.DASHBOARD_CONTENT_PROVIDER, resourceId: created.artifactId };
71
+ }
72
+ catch (error) {
73
+ // Another request with this create-only identity may have saved the first
74
+ // draft. Reuse it; never retry a create by overwriting the current draft.
75
+ if (!(error instanceof common_1.ConflictException))
76
+ throw error;
77
+ const winner = await this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability).findArtifactBySource(source);
78
+ if (!winner || !await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).get(winner.id))
79
+ throw error;
80
+ await this.service.open(ctx, winner.id);
81
+ return { providerKey: exports.DASHBOARD_CONTENT_PROVIDER, resourceId: winner.id };
82
+ }
83
+ },
84
+ mutate: async (input) => {
85
+ const ctx = context(input), drafts = this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability);
86
+ if (input.change.action === 'trash' || input.change.action === 'restore') {
87
+ await this.service.authorizeProject(ctx, 'manage');
88
+ await drafts.setRecycled({ artifactId: input.sourceResourceId, expectedRevision: Number(input.expectedRevision), recycled: input.change.action === 'trash' });
89
+ }
90
+ else {
91
+ const saved = await this.service.open(ctx, input.sourceResourceId);
92
+ if (String(saved.draftRevision ?? 0) !== input.expectedRevision)
93
+ throw new common_1.ConflictException('DASHBOARD_DRAFT_CONFLICT');
94
+ if (input.change.action === 'duplicate') {
95
+ await this.service.authorizeProject(ctx, 'write');
96
+ const artifacts = this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability);
97
+ const source = { pluginName: dashboard_service_1.PLUGIN_NAME, resourceType: 'live-dashboard', resourceId: `project/${input.projectId}/${input.change.requestId}` };
98
+ const existing = await artifacts.findArtifactBySource(source);
99
+ if (existing && await drafts.get(existing.id))
100
+ return { providerKey: exports.DASHBOARD_CONTENT_PROVIDER, resourceId: existing.id };
101
+ try {
102
+ // Only the definition is copied. Data is queried again as the acting user;
103
+ // published versions, share tokens and query results are never cloned.
104
+ const copy = await this.service.save(ctx, { ...saved.manifest, id: input.change.requestId, title: input.change.title }, null, 0);
105
+ return { providerKey: exports.DASHBOARD_CONTENT_PROVIDER, resourceId: copy.artifactId };
106
+ }
107
+ catch (error) {
108
+ if (!(error instanceof common_1.ConflictException))
109
+ throw error;
110
+ const winner = await artifacts.findArtifactBySource(source);
111
+ if (!winner || !await drafts.get(winner.id))
112
+ throw error;
113
+ return { providerKey: exports.DASHBOARD_CONTENT_PROVIDER, resourceId: winner.id };
114
+ }
115
+ }
116
+ if (input.change.action !== 'rename')
117
+ throw new common_1.BadRequestException('DASHBOARD_ACTION_UNAVAILABLE');
118
+ await this.service.update(ctx, input.sourceResourceId, Number(input.expectedRevision), { title: input.change.title }, saved.baseVersionId ?? saved.versionId ?? null);
119
+ }
120
+ return { providerKey: exports.DASHBOARD_CONTENT_PROVIDER, resourceId: input.sourceResourceId };
121
+ }
122
+ };
123
+ }
124
+ async authorize(input) {
125
+ const artifact = await this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability).getArtifact(input.resourceId);
126
+ if (!artifact.projectId || artifact.userId || artifact.pluginName !== dashboard_service_1.PLUGIN_NAME || artifact.resourceType !== 'live-dashboard')
127
+ throw new common_1.ForbiddenException('DASHBOARD_ACCESS_DENIED');
128
+ const ctx = context({ actor: input.actor, projectId: artifact.projectId });
129
+ await this.service.authorizeProject(ctx, input.operation === 'read' ? 'read' : input.operation === 'manage' ? 'manage' : 'write');
130
+ const access = (await this.runtime.require(plugin_sdk_1.ProjectAccessRuntimeCapability).listReadable({ actor: input.actor, projectIds: [artifact.projectId] }))[0];
131
+ if (!access)
132
+ throw new common_1.ForbiddenException('DASHBOARD_ACCESS_DENIED');
133
+ return { providerKey: exports.DASHBOARD_CONTENT_PROVIDER, resourceId: input.resourceId, title: artifact.title ?? '', role: access.role,
134
+ canWrite: !access.archived && ['owner', 'manager', 'editor'].includes(access.role), canManage: access.canManage };
135
+ }
136
+ async describe(input) {
137
+ await this.service.authorizeProject(context(input));
138
+ const access = (await this.runtime.require(plugin_sdk_1.ProjectAccessRuntimeCapability).listReadable({ actor: input.actor, projectIds: [input.projectId] }))[0];
139
+ if (!access)
140
+ throw new common_1.ForbiddenException('DASHBOARD_ACCESS_DENIED');
141
+ const artifact = await this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability).getArtifact(input.sourceResourceId);
142
+ if (artifact.projectId !== input.projectId || artifact.status === 'deleted')
143
+ throw new common_1.ForbiddenException('DASHBOARD_ACCESS_DENIED');
144
+ const draft = await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).get(input.sourceResourceId, { includeArchived: true });
145
+ if (!draft)
146
+ throw new common_1.BadRequestException('DASHBOARD_VERSION_UNAVAILABLE');
147
+ const deleted = artifact.status === 'archived';
148
+ const saved = !deleted ? await this.service.open(context(input), input.sourceResourceId) : null;
149
+ return { ...MODEL, offlineAccess: 'project', projectId: input.projectId, title: draft.title ?? artifact.title ?? '', revision: String(draft.revision),
150
+ updatedAt: new Date(draft.updatedAt ?? artifact.updatedAt ?? 0).toISOString(), deleted, defaultEditorKey: EDITOR,
151
+ allowedActions: [...(!deleted ? ['rename', 'duplicate'] : []), ...(access.canManage ? [deleted ? 'restore' : 'trash'] : [])],
152
+ ...(saved ? { preview: { kind: 'outline', revision: String(draft.revision), lines: [saved.manifest.title, ...saved.manifest.charts.map(chart => chart.title), ...saved.manifest.tables.map(table => table.title)].slice(0, 6).map(line => line.slice(0, 160)) } } : {}),
153
+ sourceSync: { status: 'synced', revision: String(draft.revision) },
154
+ source: draft.workspaceFileRef?.workspacePath ? { kind: 'file', path: draft.workspaceFileRef.workspacePath } : { kind: 'managed' } };
155
+ }
156
+ };
157
+ exports.DashboardContentProvider = DashboardContentProvider;
158
+ exports.DashboardContentProvider = DashboardContentProvider = __decorate([
159
+ (0, common_1.Injectable)(),
160
+ (0, plugin_sdk_1.ContentResourceProvider)(exports.DASHBOARD_CONTENT_PROVIDER),
161
+ __param(1, (0, common_1.Inject)(plugin_sdk_1.XPERT_RUNTIME_CAPABILITIES_TOKEN)),
162
+ __metadata("design:paramtypes", [dashboard_service_1.DashboardService, Object])
163
+ ], DashboardContentProvider);
@@ -29,26 +29,46 @@ export declare function dashboardNavigationTarget(context: XpertResolvedViewHost
29
29
  export declare const dashboardNavigationSchema: z.ZodEffects<z.ZodObject<{
30
30
  selectionId: z.ZodOptional<z.ZodString>;
31
31
  parameters: z.ZodOptional<z.ZodObject<{
32
+ contentProvider: z.ZodOptional<z.ZodString>;
33
+ contentId: z.ZodOptional<z.ZodString>;
34
+ contentTitle: z.ZodOptional<z.ZodString>;
35
+ contentEditor: z.ZodOptional<z.ZodString>;
32
36
  artifactId: z.ZodOptional<z.ZodString>;
33
37
  versionId: z.ZodOptional<z.ZodString>;
34
38
  }, "strict", z.ZodTypeAny, {
35
39
  artifactId?: string | undefined;
36
40
  versionId?: string | undefined;
41
+ contentProvider?: string | undefined;
42
+ contentId?: string | undefined;
43
+ contentTitle?: string | undefined;
44
+ contentEditor?: string | undefined;
37
45
  }, {
38
46
  artifactId?: string | undefined;
39
47
  versionId?: string | undefined;
48
+ contentProvider?: string | undefined;
49
+ contentId?: string | undefined;
50
+ contentTitle?: string | undefined;
51
+ contentEditor?: string | undefined;
40
52
  }>>;
41
53
  }, "strip", z.ZodTypeAny, {
42
54
  selectionId?: string | undefined;
43
55
  parameters?: {
44
56
  artifactId?: string | undefined;
45
57
  versionId?: string | undefined;
58
+ contentProvider?: string | undefined;
59
+ contentId?: string | undefined;
60
+ contentTitle?: string | undefined;
61
+ contentEditor?: string | undefined;
46
62
  } | undefined;
47
63
  }, {
48
64
  selectionId?: string | undefined;
49
65
  parameters?: {
50
66
  artifactId?: string | undefined;
51
67
  versionId?: string | undefined;
68
+ contentProvider?: string | undefined;
69
+ contentId?: string | undefined;
70
+ contentTitle?: string | undefined;
71
+ contentEditor?: string | undefined;
52
72
  } | undefined;
53
73
  }>, {
54
74
  artifactId: string | undefined;
@@ -58,6 +78,10 @@ export declare const dashboardNavigationSchema: z.ZodEffects<z.ZodObject<{
58
78
  parameters?: {
59
79
  artifactId?: string | undefined;
60
80
  versionId?: string | undefined;
81
+ contentProvider?: string | undefined;
82
+ contentId?: string | undefined;
83
+ contentTitle?: string | undefined;
84
+ contentEditor?: string | undefined;
61
85
  } | undefined;
62
86
  }>;
63
87
  export type DashboardNavigation = z.infer<typeof dashboardNavigationSchema>;
@@ -20,7 +20,7 @@ function dashboardNavigationTarget(context) {
20
20
  }
21
21
  exports.dashboardNavigationSchema = v3_1.z.object({
22
22
  selectionId: v3_1.z.string().uuid().optional(),
23
- parameters: v3_1.z.object({ artifactId: v3_1.z.string().uuid().optional(), versionId: v3_1.z.string().uuid().optional() }).strict().optional()
23
+ parameters: v3_1.z.object({ contentProvider: v3_1.z.string().optional(), contentId: v3_1.z.string().uuid().optional(), contentTitle: v3_1.z.string().optional(), contentEditor: v3_1.z.string().optional(), artifactId: v3_1.z.string().uuid().optional(), versionId: v3_1.z.string().uuid().optional() }).strict().optional()
24
24
  }).transform((query, ctx) => {
25
25
  const artifactId = query.selectionId ?? query.parameters?.artifactId;
26
26
  const versionId = query.parameters?.versionId;
@@ -6,3 +6,14 @@ export declare function validateControls(manifest: DashboardManifest, input: Rec
6
6
  };
7
7
  export declare function queryDashboardBinding(runtime: RuntimeCapabilityRegistry, actor: DataSourceActor, manifest: DashboardManifest, bindingId: string, input: Record<string, AnalyticsValue>): Promise<AnalyticalDataset>;
8
8
  export declare function queryDashboard(runtime: RuntimeCapabilityRegistry, actor: DataSourceActor, manifest: DashboardManifest, input: Record<string, AnalyticsValue>): Promise<AnalyticalDataset[]>;
9
+ /** Read-only rendering can show independent successful bindings. Saving/publishing still
10
+ * uses queryDashboard and requires every binding to validate. Never return native error
11
+ * details, which may contain private source configuration or query text. */
12
+ export declare function queryDashboardPartial(runtime: RuntimeCapabilityRegistry, actor: DataSourceActor, manifest: DashboardManifest, input: Record<string, AnalyticsValue>): Promise<{
13
+ datasets: AnalyticalDataset[];
14
+ failures: {
15
+ bindingId: string;
16
+ datasetId: string;
17
+ reason: "unavailable";
18
+ }[];
19
+ }>;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validateControls = validateControls;
4
4
  exports.queryDashboardBinding = queryDashboardBinding;
5
5
  exports.queryDashboard = queryDashboard;
6
+ exports.queryDashboardPartial = queryDashboardPartial;
6
7
  const common_1 = require("@nestjs/common");
7
8
  const plugin_sdk_1 = require("@xpert-ai/plugin-sdk");
8
9
  function validateControls(manifest, input) {
@@ -37,3 +38,22 @@ async function queryDashboard(runtime, actor, manifest, input) {
37
38
  throw new common_1.BadRequestException('DASHBOARD_RESULT_TOO_LARGE');
38
39
  return datasets;
39
40
  }
41
+ /** Read-only rendering can show independent successful bindings. Saving/publishing still
42
+ * uses queryDashboard and requires every binding to validate. Never return native error
43
+ * details, which may contain private source configuration or query text. */
44
+ async function queryDashboardPartial(runtime, actor, manifest, input) {
45
+ validateControls(manifest, input);
46
+ const datasets = [];
47
+ const failures = [];
48
+ for (const binding of manifest.bindings) {
49
+ try {
50
+ datasets.push(await queryDashboardBinding(runtime, actor, manifest, binding.id, input));
51
+ }
52
+ catch {
53
+ failures.push({ bindingId: binding.id, datasetId: binding.output.datasetId, reason: 'unavailable' });
54
+ }
55
+ }
56
+ if (Buffer.byteLength(JSON.stringify(datasets)) > 3_000_000)
57
+ throw new common_1.BadRequestException('DASHBOARD_RESULT_TOO_LARGE');
58
+ return { datasets, failures };
59
+ }
@@ -0,0 +1,5 @@
1
+ import type { ContentSearchProjection } from '@xpert-ai/contracts';
2
+ import type { DashboardManifest } from './dashboard.schema';
3
+ /** Explicit allowlist: no Binding source, parameter values, HTML/script, credentials
4
+ * or queried rows cross the shared knowledge boundary. Data access stays viewer-specific. */
5
+ export declare function dashboardSearchProjection(manifest: DashboardManifest, revision: string): ContentSearchProjection;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dashboardSearchProjection = dashboardSearchProjection;
4
+ /** Explicit allowlist: no Binding source, parameter values, HTML/script, credentials
5
+ * or queried rows cross the shared knowledge boundary. Data access stays viewer-specific. */
6
+ function dashboardSearchProjection(manifest, revision) {
7
+ return { revision, sections: [
8
+ { anchor: { kind: 'document', id: manifest.id }, text: manifest.title },
9
+ ...[...manifest.charts, ...manifest.tables].map(view => ({ anchor: { kind: 'widget', id: view.id }, text: view.title })),
10
+ ...manifest.controls.map(control => ({ anchor: { kind: 'control', id: control.controlId }, text: control.label }))
11
+ ] };
12
+ }