@xpert-ai/plugin-analytics-files 0.0.0-stage → 0.1.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.
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.discoverySchema = exports.payloadSchema = exports.aggregateSchema = exports.filterSchema = exports.optionsSchema = exports.datasetSchema = exports.columnSchema = exports.readerSchema = exports.fileSchema = exports.scalar = exports.selector = exports.key = exports.forbidden = exports.MAX_ROWS = exports.MAX_BYTES = exports.PROVIDER = void 0;
4
+ exports.jsonSchema = jsonSchema;
5
+ exports.readPath = readPath;
6
+ const v3_1 = require("zod/v3");
7
+ const zod_to_json_schema_1 = require("zod-to-json-schema");
8
+ exports.PROVIDER = 'file.table';
9
+ exports.MAX_BYTES = 8_000_000;
10
+ exports.MAX_ROWS = 50_000;
11
+ exports.forbidden = new Set(['__proto__', 'prototype', 'constructor']);
12
+ exports.key = v3_1.z.string().regex(/^[A-Za-z][A-Za-z0-9_]{0,63}$/).refine(value => !exports.forbidden.has(value));
13
+ const segment = v3_1.z.string().min(1).max(256).refine(value => !exports.forbidden.has(value));
14
+ exports.selector = v3_1.z.array(segment).max(12);
15
+ exports.scalar = v3_1.z.union([v3_1.z.string().max(4096), v3_1.z.number().finite(), v3_1.z.boolean(), v3_1.z.null()]);
16
+ const filePath = v3_1.z.string().min(1).max(1024).refine(value => !value.startsWith('/') && !value.includes('\\') &&
17
+ !value.includes('\0') && value.split('/').every(part => !!part && part !== '.' && part !== '..'));
18
+ // Source configuration is administrator-owned; neither queries nor controls contain a file locator.
19
+ exports.fileSchema = v3_1.z.discriminatedUnion('catalog', [
20
+ v3_1.z.object({ catalog: v3_1.z.literal('xperts'), xpertId: v3_1.z.string().uuid(), filePath }).strict(),
21
+ v3_1.z.object({ catalog: v3_1.z.literal('user-xperts'), xpertId: v3_1.z.string().uuid(), userId: v3_1.z.string().uuid(), filePath }).strict(),
22
+ v3_1.z.object({ catalog: v3_1.z.literal('users'), userId: v3_1.z.string().uuid(), filePath }).strict(),
23
+ v3_1.z.object({ catalog: v3_1.z.literal('projects'), projectId: v3_1.z.string().uuid(), filePath }).strict()
24
+ ]);
25
+ exports.readerSchema = v3_1.z.discriminatedUnion('format', [
26
+ v3_1.z.object({ format: v3_1.z.literal('csv'), delimiter: v3_1.z.enum([',', ';', '\t']).default(','), headerRow: v3_1.z.number().int().min(1).max(100).default(1) }).strict(),
27
+ v3_1.z.object({ format: v3_1.z.literal('json'), rowsPath: exports.selector }).strict(),
28
+ v3_1.z.object({ format: v3_1.z.literal('xlsx'), sheet: v3_1.z.string().min(1).max(31), headerRow: v3_1.z.number().int().min(1).max(100).default(1), formulaPolicy: v3_1.z.enum(['reject', 'cached']).default('reject') }).strict()
29
+ ]);
30
+ exports.columnSchema = v3_1.z.object({
31
+ key: exports.key, path: exports.selector.min(1), label: v3_1.z.string().min(1).max(160), description: v3_1.z.string().max(2000),
32
+ logicalType: v3_1.z.enum(['string', 'boolean', 'int64', 'decimal', 'float64', 'date', 'timestamp']),
33
+ nullable: v3_1.z.boolean(), role: v3_1.z.enum(['dimension', 'measure', 'time', 'identifier']), unit: v3_1.z.string().max(80).optional(),
34
+ // CSV empty cells are not silently converted to null or zero.
35
+ nullValues: v3_1.z.array(v3_1.z.string().max(64)).max(10).default([])
36
+ }).strict();
37
+ exports.datasetSchema = v3_1.z.object({
38
+ id: exports.key, revision: exports.key, name: v3_1.z.string().min(1).max(160), description: v3_1.z.string().min(1).max(4000),
39
+ file: exports.fileSchema, reader: exports.readerSchema, columns: v3_1.z.array(exports.columnSchema).min(1).max(80)
40
+ }).strict().superRefine((dataset, ctx) => {
41
+ if (new Set(dataset.columns.map(c => c.key)).size !== dataset.columns.length)
42
+ ctx.addIssue({ code: 'custom', message: 'Duplicate column key' });
43
+ if (dataset.reader.format !== 'json' && dataset.columns.some(c => c.path.length !== 1))
44
+ ctx.addIssue({ code: 'custom', message: 'CSV/XLSX column paths must contain one exact header' });
45
+ if (Buffer.byteLength(JSON.stringify(dataset)) > 48000)
46
+ ctx.addIssue({ code: 'custom', message: 'Dataset exceeds discovery metadata budget' });
47
+ });
48
+ exports.optionsSchema = v3_1.z.object({ datasets: v3_1.z.array(exports.datasetSchema).min(1).max(100) }).strict()
49
+ .refine(value => new Set(value.datasets.map(d => d.id)).size === value.datasets.length, 'Duplicate dataset ID');
50
+ const operand = v3_1.z.discriminatedUnion('type', [
51
+ v3_1.z.object({ type: v3_1.z.literal('literal'), value: exports.scalar }).strict(),
52
+ v3_1.z.object({ type: v3_1.z.literal('parameter'), name: exports.key }).strict()
53
+ ]);
54
+ exports.filterSchema = v3_1.z.object({ column: exports.key, operator: v3_1.z.enum(['eq', 'ne', 'gt', 'gte', 'lt', 'lte', 'contains']),
55
+ value: operand, skipWhenNull: v3_1.z.boolean().default(false) }).strict();
56
+ exports.aggregateSchema = v3_1.z.object({ key: exports.key, operation: v3_1.z.enum(['sum', 'count', 'min', 'max']), column: exports.key.optional() }).strict();
57
+ exports.payloadSchema = v3_1.z.object({
58
+ datasetId: exports.key, revision: exports.key, contractHash: v3_1.z.string().regex(/^[a-f0-9]{64}$/),
59
+ select: v3_1.z.array(exports.key).min(1).max(80), filters: v3_1.z.array(exports.filterSchema).max(20).default([]),
60
+ groupBy: v3_1.z.array(exports.key).max(12).default([]), aggregates: v3_1.z.array(exports.aggregateSchema).max(20).default([]),
61
+ orderBy: v3_1.z.array(v3_1.z.object({ column: exports.key, direction: v3_1.z.enum(['asc', 'desc']) }).strict()).max(12).default([])
62
+ }).strict();
63
+ exports.discoverySchema = v3_1.z.object({ datasetId: exports.key.optional(), page: v3_1.z.number().int().min(1).max(5).default(1) }).strict();
64
+ function jsonSchema(schema) {
65
+ return JSON.parse(JSON.stringify((0, zod_to_json_schema_1.zodToJsonSchema)(schema, { $refStrategy: 'root' })));
66
+ }
67
+ function readPath(value, path) {
68
+ let current = value;
69
+ for (const part of path) {
70
+ if (exports.forbidden.has(part) || !current || typeof current !== 'object' || !Object.hasOwn(current, part))
71
+ throw new Error('FILE_DATASET_FIELD_MISSING');
72
+ current = Object.getOwnPropertyDescriptor(current, part)?.value;
73
+ }
74
+ return current;
75
+ }
@@ -0,0 +1,15 @@
1
+ import type { AnalyticsValue } from '@xpert-ai/contracts';
2
+ import { type FileDataset, type FileQuery } from './schema';
3
+ export declare function queryWorker(bytes: Buffer, definition: FileDataset, query: FileQuery, parameters: Record<string, AnalyticsValue>, maxRows: number, signal: AbortSignal, deadlineAt: number): Promise<{
4
+ columns: {
5
+ key: string;
6
+ }[];
7
+ rows: Record<string, string | number | boolean | null>[];
8
+ completeness: {
9
+ totalRows: number;
10
+ returnedRows: number;
11
+ totalAccuracy: "exact";
12
+ scope: "page" | "full_query_result";
13
+ truncated: boolean;
14
+ };
15
+ }>;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.queryWorker = queryWorker;
4
+ const node_worker_threads_1 = require("node:worker_threads");
5
+ const node_path_1 = require("node:path");
6
+ const v3_1 = require("zod/v3");
7
+ const schema_1 = require("./schema");
8
+ const responseSchema = v3_1.z.union([
9
+ v3_1.z.object({ error: v3_1.z.string().max(160) }).strict(),
10
+ v3_1.z.object({ result: v3_1.z.object({ columns: v3_1.z.array(v3_1.z.object({ key: v3_1.z.string() }).strict()).max(80),
11
+ rows: v3_1.z.array(v3_1.z.record(schema_1.scalar)).max(1000), completeness: v3_1.z.object({ totalRows: v3_1.z.number().int(), returnedRows: v3_1.z.number().int(),
12
+ totalAccuracy: v3_1.z.literal('exact'), scope: v3_1.z.enum(['page', 'full_query_result']), truncated: v3_1.z.boolean() }).strict() }).strict() }).strict()
13
+ ]);
14
+ async function queryWorker(bytes, definition, query, parameters, maxRows, signal, deadlineAt) {
15
+ signal.throwIfAborted();
16
+ const timeout = deadlineAt - Date.now();
17
+ if (timeout <= 0)
18
+ throw new Error('FILE_DATASET_DEADLINE_EXCEEDED');
19
+ const worker = new node_worker_threads_1.Worker((0, node_path_1.join)(__dirname, 'worker.js'), { workerData: { bytes, definition, query, parameters, maxRows },
20
+ // Do not inherit the host's ts-node preloads or debugger into the shipped JavaScript worker.
21
+ execArgv: [], resourceLimits: { maxOldGenerationSizeMb: 192, maxYoungGenerationSizeMb: 32, stackSizeMb: 4 } });
22
+ try {
23
+ return await new Promise((resolve, reject) => {
24
+ const fail = () => reject(new Error('FILE_DATASET_CANCELLED'));
25
+ const timer = setTimeout(() => reject(new Error('FILE_DATASET_DEADLINE_EXCEEDED')), timeout);
26
+ signal.addEventListener('abort', fail, { once: true });
27
+ const clean = () => { clearTimeout(timer); signal.removeEventListener('abort', fail); };
28
+ worker.once('message', (data) => { clean(); try {
29
+ resolve(parseResult(data));
30
+ }
31
+ catch (e) {
32
+ reject(e);
33
+ } });
34
+ worker.once('error', () => { clean(); reject(new Error('FILE_DATASET_WORKER_FAILED')); });
35
+ worker.once('exit', () => { clean(); reject(new Error('FILE_DATASET_WORKER_EXITED')); });
36
+ if (signal.aborted)
37
+ fail();
38
+ });
39
+ }
40
+ finally {
41
+ await worker.terminate();
42
+ }
43
+ }
44
+ function parseResult(data) {
45
+ const message = responseSchema.parse(data);
46
+ if ('error' in message)
47
+ throw new Error(message.error);
48
+ return message.result;
49
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ // Parsing and aggregation run off the API event loop and can be terminated at the host deadline.
4
+ const node_worker_threads_1 = require("node:worker_threads");
5
+ const v3_1 = require("zod/v3");
6
+ const schema_1 = require("./schema");
7
+ const readers_1 = require("./readers");
8
+ const query_1 = require("./query");
9
+ const inputSchema = v3_1.z.object({ definition: schema_1.datasetSchema, query: schema_1.payloadSchema,
10
+ parameters: v3_1.z.record(schema_1.scalar), maxRows: v3_1.z.number().int().min(1).max(1000),
11
+ bytes: v3_1.z.instanceof(Uint8Array) }).strict();
12
+ async function run() {
13
+ const input = inputSchema.parse(node_worker_threads_1.workerData);
14
+ const rows = await (0, readers_1.readRows)(Buffer.from(input.bytes), input.definition);
15
+ return (0, query_1.executeQuery)(rows, input.definition, input.query, input.parameters, input.maxRows);
16
+ }
17
+ run().then(result => node_worker_threads_1.parentPort?.postMessage({ result }), (error) => {
18
+ const message = error instanceof Error && /^FILE_DATASET_[A-Z_]+(?:: [A-Za-z][A-Za-z0-9_]*)?$/.test(error.message) ? error.message : 'FILE_DATASET_PARSE_FAILED';
19
+ node_worker_threads_1.parentPort?.postMessage({ error: message });
20
+ });
@@ -0,0 +1 @@
1
+ export declare function validateXlsxArchive(bytes: Buffer): void;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateXlsxArchive = validateXlsxArchive;
4
+ const fflate_1 = require("fflate");
5
+ const MAX_EXPANDED_BYTES = 40_000_000;
6
+ // ZIP sizes are untrusted. Count actual output in small chunks before ExcelJS allocates workbook contents.
7
+ function validateXlsxArchive(bytes) {
8
+ const entries = new Map();
9
+ let declaredBytes = 0;
10
+ (0, fflate_1.unzipSync)(bytes, { filter(entry) {
11
+ declaredBytes += entry.originalSize;
12
+ if (entries.size >= 2000 || declaredBytes > MAX_EXPANDED_BYTES || entries.has(entry.name))
13
+ throw new Error('FILE_DATASET_XLSX_EXPANSION_LIMIT');
14
+ if (/vbaProject|externalLinks/i.test(entry.name))
15
+ throw new Error('FILE_DATASET_XLSX_EXTERNAL_CONTENT_DENIED');
16
+ entries.set(entry.name, entry);
17
+ return false;
18
+ } });
19
+ if (!entries.has('xl/workbook.xml') || !entries.has('[Content_Types].xml'))
20
+ throw new Error('FILE_DATASET_XLSX_INVALID');
21
+ const seen = new Set(), completed = new Set();
22
+ let expandedBytes = 0, failure;
23
+ const archive = new fflate_1.Unzip(file => {
24
+ if (failure)
25
+ return;
26
+ const declared = entries.get(file.name);
27
+ if (!declared || seen.has(file.name) || file.compression !== declared.compression ||
28
+ (file.size !== undefined && file.size !== declared.size) ||
29
+ (file.originalSize !== undefined && file.originalSize !== declared.originalSize)) {
30
+ failure = new Error('FILE_DATASET_XLSX_INVALID');
31
+ return;
32
+ }
33
+ seen.add(file.name);
34
+ let fileBytes = 0;
35
+ file.ondata = (error, chunk, final) => {
36
+ if (failure)
37
+ return;
38
+ if (error) {
39
+ failure = new Error('FILE_DATASET_XLSX_INVALID');
40
+ return;
41
+ }
42
+ fileBytes += chunk.length;
43
+ expandedBytes += chunk.length;
44
+ if (fileBytes > declared.originalSize || expandedBytes > MAX_EXPANDED_BYTES) {
45
+ failure = new Error('FILE_DATASET_XLSX_EXPANSION_LIMIT');
46
+ }
47
+ else if (final) {
48
+ if (fileBytes !== declared.originalSize)
49
+ failure = new Error('FILE_DATASET_XLSX_INVALID');
50
+ completed.add(file.name);
51
+ }
52
+ };
53
+ file.start();
54
+ });
55
+ archive.register(fflate_1.UnzipInflate);
56
+ for (let offset = 0; offset < bytes.length && !failure; offset += 1024)
57
+ archive.push(bytes.subarray(offset, offset + 1024), offset + 1024 >= bytes.length);
58
+ if (failure)
59
+ throw failure;
60
+ if (completed.size !== entries.size)
61
+ throw new Error('FILE_DATASET_XLSX_INVALID');
62
+ }
@@ -0,0 +1,7 @@
1
+ # Mintlify automatically ignores these files and directories:
2
+ # .git, .github, .claude, .agents, .idea, node_modules,
3
+ # README.md, LICENSE.md, CHANGELOG.md, CONTRIBUTING.md
4
+
5
+ # Draft content
6
+ drafts/
7
+ *.draft.mdx
package/docs/AGENTS.md ADDED
@@ -0,0 +1,3 @@
1
+ # File Analytics documentation
2
+
3
+ Maintain bilingual MDX pages and docs.json navigation. Keep file parsing, query semantics and format limits in this plugin; platform file authorization and Dataset/Binding remain generic. Document cached Excel formula limitations, live source reads, explicit catalog configuration and partial result semantics. Do not include credentials, private acceptance IDs or host paths. Distinguish contract tests, local Agent/UI acceptance and formal release verification.
package/docs/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mintlify
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/docs/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # File Analytics documentation
2
+
3
+ English and Chinese feature documentation for `@xpert-ai/plugin-analytics-files`.
4
+
5
+ Run `mint validate` in this directory after editing. Run `mint dev` to preview the navigation in `docs.json`. Keep configuration examples aligned with `src/lib/schema.ts`, and keep local credentials and acceptance resource IDs out of these pages.
@@ -0,0 +1,42 @@
1
+ ---
2
+ title: 'Configuration and queries'
3
+ description: 'Approved files, explicit column semantics and declarative queries.'
4
+ ---
5
+
6
+ Create a platform data source of type `workspace-files`. Configure `datasets` using the packaged `examples/sales.json` catalog. Replace the example Assistant ID with a published Assistant you can access. Upload the file through the platform Files UI/API. Paths are Volume-relative, never local host paths.
7
+
8
+ Each dataset has `id`, `revision`, `name`, `description`, `file`, `reader` and `columns`.
9
+
10
+ | File catalog | Required scope | Authorization |
11
+ | --- | --- | --- |
12
+ | `xperts` | `xpertId` | Current viewer's exact published Assistant grant; shared volume |
13
+ | `user-xperts` | `xpertId`, `userId` | Exact Assistant grant and matching current user |
14
+ | `users` | `userId` | Matching current user |
15
+ | `projects` | `projectId` | Current human Project membership; archived Projects denied |
16
+
17
+ Tenant and organization come from the platform execution, never Binding parameters. Discovery and every query reauthorize the file. Changing source options invalidates the inspected contract even if the administrator forgets to increase `revision`.
18
+
19
+ Reader formats are explicit:
20
+
21
+ - CSV: `format: "csv"`, delimiter `,`, `;` or tab, and a one-based `headerRow`. UTF-8/BOM, quoted fields and embedded newlines are supported. Duplicate or missing headers fail.
22
+ - JSON: `format: "json"` and `rowsPath`, an array of own-property segments selecting an object array. Each column's `path` selects a scalar within a row. No JSONPath evaluation or external references.
23
+ - XLSX: `format: "xlsx"`, exact visible `sheet`, one-based `headerRow`, and `formulaPolicy: "reject" | "cached"`. CSV/XLSX column paths contain one exact header. The selected sheet's data rows are read; cell-range selection is not implemented.
24
+
25
+ Columns declare stable `key`, `path`, `label`, `description`, `logicalType`, `nullable`, `role`, optional `unit` and explicit `nullValues`. Missing fields fail even when nullable. JSON decimal values must be strings; CSV decimal strings are parsed exactly. Excel numeric cells retain Excel's stored numeric precision: use text cells for high-precision identifiers/decimals. Empty strings are not silently zero/null. Date strings use ISO dates; timestamp strings require a timezone. Excel date cells use the workbook's date system through ExcelJS.
26
+
27
+ Discovery supplies a complete `metadata.binding` to copy, plus `bindingPayload` containing dataset identity, revision, contract hash and selected fields. `bindingPayload` alone is not a Binding. Raw rows declare no uniqueness (`grain: []`); after grouping, use the grouping output field IDs as grain. Queries support AND filters (`eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `contains`), `groupBy`, aggregates (`sum`, `count`, `min`, `max`) and `orderBy`. No SQL, JavaScript, URL or file reference is accepted in the payload.
28
+
29
+ ```json
30
+ {
31
+ "column": "region",
32
+ "operator": "eq",
33
+ "value": { "type": "parameter", "name": "region" },
34
+ "skipWhenNull": true
35
+ }
36
+ ```
37
+
38
+ Declare the matching Binding parameter as optional with `defaultValue: null` to represent all regions. Empty string remains an actual filter value. Controls cannot add fields or change files.
39
+
40
+ Aggregates need new aliases distinct from source keys, for example `{ "key": "total", "operation": "sum", "column": "net_sales" }`. `select` then contains only grouping and aggregate keys. `sum` on int64 returns decimal; `sum/min/max` are nullable, while `count` is a non-null int64 without a unit. Empty global sums return null, counts zero. Copy output types, nullability and units exactly; preview before saving.
41
+
42
+ The Agent uses the existing `dashboard_sources`, `dashboard_discover`, and `dashboard_preview_binding` tools. Follow the inspected provider instructions and the Live Dashboard skill for creating/saving the page. Use Workspace scripts for joins, custom transformations or computations outside this contract.
package/docs/docs.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "$schema": "https://mintlify.com/docs.json",
3
+ "theme": "mint",
4
+ "name": "File Analytics",
5
+ "colors": {
6
+ "primary": "#2563EB"
7
+ },
8
+ "navigation": {
9
+ "languages": [
10
+ {
11
+ "language": "en",
12
+ "groups": [
13
+ {
14
+ "group": "File Analytics",
15
+ "pages": [
16
+ "index",
17
+ "configuration",
18
+ "verification"
19
+ ]
20
+ }
21
+ ]
22
+ },
23
+ {
24
+ "language": "cn",
25
+ "groups": [
26
+ {
27
+ "group": "文件分析",
28
+ "pages": [
29
+ "zh-Hans/index",
30
+ "zh-Hans/configuration",
31
+ "zh-Hans/verification"
32
+ ]
33
+ }
34
+ ]
35
+ }
36
+ ]
37
+ }
38
+ }
package/docs/index.mdx ADDED
@@ -0,0 +1,14 @@
1
+ ---
2
+ title: 'Declarative file analytics'
3
+ description: 'CSV, JSON and XLSX datasets backed by platform WorkspaceFiles.'
4
+ ---
5
+
6
+ `file.table` lets Agents discover approved files and use declarative selection, filtering, grouping, aggregation and sorting. Administrators declare the file catalog, field types and business semantics. The platform owns file access and Dataset/Binding; this plugin owns parsers and query semantics. Existing chart and HTML/JS dashboard renderers need no source-specific changes.
7
+
8
+ Refresh rereads the current file and records its content digest. Artifact versions preserve the query, not a historical copy of business data. Compatible file replacement is immediately visible; missing fields, incompatible types and changed source contracts fail closed.
9
+
10
+ Inputs are limited to 8 MB and 50,000 rows. Output follows platform row limits (currently 1,000) and 2 MB. Oversized input fails rather than producing misleading totals. Output truncation remains explicit. Parsing and aggregation use a cancellable Worker under the host deadline.
11
+
12
+ XLSX is limited to 40 MB expanded ZIP contents, 2,000 entries and 200 columns. Before workbook parsing, the plugin checks declared ZIP sizes and counts actual decompressed bytes in bounded chunks; inconsistent sizes fail closed. Macros, external-workbook content, hidden sheets and merged data cells are rejected. Formula cells are rejected by default. Explicit cached mode reads only existing results: it never recalculates formulas or guarantees cache freshness.
13
+
14
+ [Configuration](/configuration) · [Verification](/verification)
@@ -0,0 +1,24 @@
1
+ ---
2
+ title: 'Verification'
3
+ description: 'Contract checks and local application acceptance.'
4
+ ---
5
+
6
+ The package test suite uses actual CSV, JSON and XLSX bytes and the shipped Worker entrypoint. It checks exact sums, filtering, source replacement, saved Binding reuse, discovery redaction, output contracts, revocation before/after processing, Project/private ownership, invalid paths, cancellation, schema drift, formulas, ZIP expansion, forged ZIP sizes and streaming/stored ZIP compatibility.
7
+
8
+ Run the plugin lifecycle harness after building. A successful mock lifecycle test establishes loadability, not live file authorization or Agent behavior.
9
+
10
+ For local application acceptance, configure equivalent CSV/JSON/XLSX datasets in a published test Assistant's Workspace. Generate a dashboard through a real Chat request, compare totals with known fixture values, change a filter, update the source through the Files API and refresh. Close/reopen the resource card and reload the host page. Verify the same Artifact and saved Binding, with newly fetched source data.
11
+
12
+ Revoke the viewer's source/file access and verify discovery and saved queries fail; restore it and retry. Test unknown Bindings, undeclared controls, payload/file overrides, direct browser fetch and parent DOM access. Keep environment-specific receipts outside the repository. Local acceptance does not constitute a formal release verification.
13
+
14
+ ## 2026-10-18 local acceptance
15
+
16
+ A real configured model generated separate HTML/ECharts dashboards from CSV, JSON and XLSX through one Chat request per source in xpert-pro. No prewritten HTML or scripts were supplied. All three saved drafts passed UI checks for KPI, chart series, detail rows, region filtering, toolbar refresh, page refresh, resource-card reopening and browser-reload restoration.
17
+
18
+ The independent fixture has seven input rows and six month/region groups: total 5,700 CNY, East 3,500 and West 2,200. Changing the first source amount by 100/200/300 CNY for CSV/JSON/XLSX produced filtered East totals of 3,600/3,700/3,800 after refresh. Selection and the mounted document were preserved; source files were restored afterwards.
19
+
20
+ With the same viewer JWT, revoking Assistant file access or DATA_SOURCE_VIEW denied both discovery and query on all three formats; restoring access allowed the next request. The viewer had no inherited workspace membership during this test. Temporary test grants were removed. Saved dashboard queries rejected undeclared Bindings and file, reader, provider, payload or control overrides; the document could not fetch the backend directly or read its parent DOM.
21
+
22
+ These results cover the local xpert-pro environment, not a new data-xpert SSO run or formal release verification. The 20 source-adapter tests passed separately. Real model generation can require tool-error recovery; a saved receipt is necessary, and page review must still validate business semantics.
23
+
24
+ A new three-source dashboard also passed shared-filter and source-update UI checks. A separate dashboard retained its Artifact, draft, Bindings, controls and library locks across two edit requests (observed revisions r1 → r3 → r5); the final draft passed the same refresh/reopen checks. The Live Dashboard plugin records both failed initial trials and the successful retries in `docs/file-datasets-acceptance.mdx`.
@@ -0,0 +1,38 @@
1
+ ---
2
+ title: '配置与查询'
3
+ description: '受控文件目录、字段口径和声明式查询。'
4
+ ---
5
+
6
+ 安装插件后创建类型为 `workspace-files` 的平台数据源,按包内 `examples/sales.json` 配置 `datasets`。示例 Assistant ID 需要替换为有权访问的已发布 Assistant;通过平台 Files 上传源文件。路径为 Volume 内相对路径,不能使用本机绝对路径。
7
+
8
+ 每个数据集包含 `id/revision/name/description/file/reader/columns`。`file.catalog` 支持共享 Assistant `xperts`、用户隔离 Assistant `user-xperts`、用户文件 `users` 和项目 `projects`。用户目录必须匹配当前访问者;项目需要有效成员权限,Assistant 文件检查准确的已发布 Assistant 授权。租户和组织由平台提供。
9
+
10
+ `reader.format` 显式选择 `csv/json/xlsx`,不按文件名猜测。CSV 支持 UTF-8/BOM、逗号/分号/Tab 分隔、引号和换行;用 `headerRow` 指定表头。JSON 用 `rowsPath` 选择对象数组。XLSX 用 `sheet/headerRow/formulaPolicy` 选择工作表和公式策略,首期读取工作表的数据行,不支持任意单元格范围。
11
+
12
+ 字段声明 `key/path/label/description/logicalType/nullable/role`,可选 `unit/nullValues`。CSV/XLSX 的 `path` 只有一个精确表头名称;JSON 使用逐层属性路径。字段缺失、重复表头、类型变化会失败。JSON 的 decimal 使用字符串;Excel 高精度数值建议保存为文本,普通数值单元格保留 Excel 已存精度。空字符串不自动变成零或 null。
13
+
14
+ 发现返回完整的 `metadata.binding`,可直接复制后调整查询;`bindingPayload` 仅为其中的 payload,不能作为完整 Binding 传入工具。原始行不声明唯一粒度(`grain: []`),分组后可用分组输出字段 ID 声明粒度。
15
+
16
+ 发现同时提供受限的真实预览。查询仅允许:
17
+
18
+ - `select`:输出字段。
19
+ - `filters`:AND 条件,操作符为 eq/ne/gt/gte/lt/lte/contains。
20
+ - `groupBy/aggregates`:分组与 sum/count/min/max,聚合别名不能与来源字段重名。
21
+ - `orderBy`:按选中的输出字段排序。
22
+
23
+ 区域筛选例子:
24
+
25
+ ```json
26
+ {
27
+ "column": "region",
28
+ "operator": "eq",
29
+ "value": { "type": "parameter", "name": "region" },
30
+ "skipWhenNull": true
31
+ }
32
+ ```
33
+
34
+ 对应 Binding 参数设为可选,默认 null 表示全部;空字符串仍是实际筛选值。int64 求和返回 decimal,sum/min/max 可空,count 为非空 int64 且不继承单位。全局空集求和为 null,计数为零。输出类型、可空性和单位必须与契约一致。
35
+
36
+ Agent 使用现有看板来源发现、数据集发现和 Binding 预览工具,按照返回的原生契约操作,再用 Live Dashboard 技能保存页面。复杂清洗、联表和其他运算继续使用 Workspace 脚本。查询和控件均不能替换文件位置、读取范围、解析器或用户身份。
37
+
38
+ 修改数据源定义后,即使未修改 revision,契约摘要也会失效;重新发现并预览后才能保存新 Binding。
@@ -0,0 +1,16 @@
1
+ ---
2
+ title: '声明式文件数据源'
3
+ description: '通过平台 WorkspaceFiles 将 CSV、JSON 和 XLSX 接入实时看板。'
4
+ ---
5
+
6
+ `file.table` 让 Agent 直接发现已配置的文件数据集,通过字段选择、筛选、聚合和排序生成看板。管理员明确文件、字段类型及业务口径;Agent 使用真实预览结果构建 Binding。无需新增 Agent 中间件。
7
+
8
+ 平台负责文件访问、当前访问者权限、Dataset/Binding、查询限制及 Artifact 生命周期;本插件负责格式解析、查询语义、类型转换与精确聚合。看板插件继续使用原有标准图表和 HTML/JS 渲染。
9
+
10
+ 每次筛选和刷新重新读取文件,查询结果携带文件摘要。保存 Artifact 固定查询定义,不冻结业务数据。替换文件但保留字段契约后可直接刷新;删除字段、改变类型或修改数据源定义会要求修正文件或重新发现、预览。
11
+
12
+ 首期限制:输入最多 8 MB、50,000 行,返回最多服从平台行数上限(当前 1,000 行)、2 MB。超出输入限制直接失败,不对部分输入计算“完整总额”。返回行数被截断时保留完整性标记。
13
+
14
+ 解析和聚合在可终止 Worker 中执行,服从平台截止时间。XLSX 限制解压总量 40 MB、2,000 个 ZIP 条目及 200 列。解析工作簿前先检查 ZIP 声明大小,再分块计数实际解压字节;大小不一致直接拒绝。拒绝宏、外部工作簿内容、隐藏工作表和合并的数据单元格。默认拒绝公式;显式选择缓存模式仅使用文件内已有结果,不计算公式,也不保证缓存最新。
15
+
16
+ [配置与查询](/zh-Hans/configuration) · [测试与验收](/zh-Hans/verification)
@@ -0,0 +1,24 @@
1
+ ---
2
+ title: '测试与验收'
3
+ description: '协议测试与 xpert-pro 本地闭环。'
4
+ ---
5
+
6
+ 单元测试使用真实 CSV、JSON、XLSX 字节及实际 Worker 入口,覆盖精确求和、筛选、源文件替换、保存后的 Binding 重用、输出类型、权限撤销、目录隔离、非法路径、取消、结构变化、公式、压缩包大小限制、伪造 ZIP 大小及流式/未压缩 ZIP 的兼容性。构建后运行仓库插件生命周期测试,确认产物可以加载。
7
+
8
+ 本地验收需要在已发布测试 Assistant 的文件工作区配置三种等价来源,通过真实 Chat 一句话生成看板,核对已知总额与明细,切换筛选,通过 Files 更新源文件后刷新,关闭/重开资源卡片并刷新宿主页面。确认 Artifact 和 Binding 保留,取数使用更新后的文件。
9
+
10
+ 撤销当前访问者的来源或文件权限后,发现与已保存查询都应失败;恢复后重新取数。另验证未知 Binding、额外控件、文件或 payload 覆盖、浏览器直接请求后端和访问父 DOM 均不能绕过边界。
11
+
12
+ 机器路径、账号和验收资源 ID 只保存在本地回执中。协议测试、加载成功和本地应用验收是不同层次,本批不进行正式发布验证。
13
+
14
+ ## 2026-10-18 本地补验
15
+
16
+ 在 xpert-pro 中,使用现有真实模型,分别对 CSV、JSON、XLSX 发出一条 Chat 业务请求,生成独立 HTML/ECharts 看板;没有提供预制 HTML 或脚本。三份草稿均通过 KPI、图表系列、明细、区域筛选、工具栏刷新、页面内刷新、资源卡片重开及刷新浏览器后恢复的 UI 验收。
17
+
18
+ 独立测试数据包含 7 条输入、6 个月份/区域分组:总额 5,700 元,华东 3,500 元,华西 2,200 元。分别将 CSV/JSON/XLSX 首条源金额增加 100/200/300 元,刷新后的华东总额为 3,600/3,700/3,800 元;当前筛选及已挂载页面保持不变。测试后已恢复源文件。
19
+
20
+ 保持同一个访问者 JWT,撤销 Assistant 文件访问或 DATA_SOURCE_VIEW 后,三种格式的发现和查询均拒绝;恢复权限后下一次请求成功。测试访问者没有继承工作区成员权限,临时授权已清理。已保存看板拒绝未知 Binding,以及文件、读取配置、Provider、payload 和未知控件覆盖;页面直接请求后端和读取父 DOM 均被拒绝。
21
+
22
+ 以上仅覆盖本地 xpert-pro,不代表新一轮 data-xpert SSO 或正式发布验证。文件适配器的 20 项测试单独通过。真实模型可能需要根据工具错误自行修复;必须取得保存回执,页面验收仍需核对业务语义。
23
+
24
+ 新会话生成的三来源看板也通过共同筛选与源文件更新的页面验收。另一看板经两轮修改,保持同一 Artifact、草稿、Bindings、控件和依赖锁(检查时点修订 r1 → r3 → r5);最终稿通过相同的刷新与重开验收。初轮失败及后续补验分别保留在看板插件 `docs/file-datasets-acceptance.mdx` 中。
@@ -0,0 +1,8 @@
1
+ month,region,net_sales
2
+ 2026-07,华东,600.00
3
+ 2026-07,华东,400.00
4
+ 2026-07,华西,800.00
5
+ 2026-08,华东,1500.00
6
+ 2026-08,华西,900.00
7
+ 2026-09,华东,1000.00
8
+ 2026-09,华西,500.00
@@ -0,0 +1,56 @@
1
+ {
2
+ "datasets": [
3
+ {
4
+ "id": "q3_sales",
5
+ "revision": "v1",
6
+ "name": "Q3 文件销售订单",
7
+ "description": "2026 年第三季度已支付订单净销售额=实付金额-退款金额,人民币元。每行是一笔订单,按 month+region 聚合后唯一。区域筛选 null 表示全部。",
8
+ "file": {
9
+ "catalog": "xperts",
10
+ "xpertId": "00000000-0000-4000-8000-000000000001",
11
+ "filePath": "sales.csv"
12
+ },
13
+ "reader": {
14
+ "format": "csv",
15
+ "delimiter": ",",
16
+ "headerRow": 1
17
+ },
18
+ "columns": [
19
+ {
20
+ "key": "month",
21
+ "path": [
22
+ "month"
23
+ ],
24
+ "label": "月份",
25
+ "description": "2026 年第三季度月份,YYYY-MM",
26
+ "logicalType": "string",
27
+ "nullable": false,
28
+ "role": "time"
29
+ },
30
+ {
31
+ "key": "region",
32
+ "path": [
33
+ "region"
34
+ ],
35
+ "label": "区域",
36
+ "description": "销售区域:华东、华西",
37
+ "logicalType": "string",
38
+ "nullable": false,
39
+ "role": "dimension"
40
+ },
41
+ {
42
+ "key": "net_sales",
43
+ "path": [
44
+ "net_sales"
45
+ ],
46
+ "label": "净销售额",
47
+ "description": "已支付订单实付金额减退款金额,人民币元;原始订单行可按月和区域求和",
48
+ "logicalType": "decimal",
49
+ "nullable": false,
50
+ "role": "measure",
51
+ "unit": "CNY"
52
+ }
53
+ ]
54
+ }
55
+ ]
56
+ }
package/index.cjs ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('./dist/index.js').default
package/package.json CHANGED
@@ -1,6 +1,67 @@
1
1
  {
2
2
  "name": "@xpert-ai/plugin-analytics-files",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Declarative CSV, JSON and XLSX analytics over authorized Workspace files",
5
+ "main": "./index.cjs",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "require": "./index.cjs",
11
+ "default": "./index.cjs"
12
+ },
13
+ "./package.json": "./package.json"
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "index.cjs",
18
+ "README.md",
19
+ "docs",
20
+ "examples"
21
+ ],
22
+ "license": "AGPL-3.0",
23
+ "private": false,
24
+ "xpert": {
25
+ "plugin": {
26
+ "level": "system",
27
+ "artifactNamespace": "analytics_files"
28
+ }
29
+ },
30
+ "peerDependencies": {
31
+ "@xpert-ai/plugin-sdk": "^3.25.0",
32
+ "@xpert-ai/contracts": "^3.25.0",
33
+ "@nestjs/common": "^11.1.6",
34
+ "@nestjs/core": "^11.1.6",
35
+ "zod": "3.25.67"
36
+ },
37
+ "devDependencies": {
38
+ "@xpert-ai/plugin-sdk": "3.25.0",
39
+ "@xpert-ai/contracts": "3.25.0",
40
+ "@nestjs/common": "^11.1.6",
41
+ "@nestjs/core": "^11.1.6",
42
+ "zod": "3.25.67",
43
+ "typescript": "^5.9.2",
44
+ "@types/node": "^20.19.9",
45
+ "reflect-metadata": "^0.2.2"
46
+ },
47
+ "dependencies": {
48
+ "zod-to-json-schema": "3.25.2",
49
+ "csv-parse": "6.1.0",
50
+ "exceljs": "4.4.0",
51
+ "fflate": "0.8.3",
52
+ "decimal.js": "10.6.0"
53
+ },
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "https://github.com/xpert-ai/xpert-plugins.git",
57
+ "directory": "xpertai/tools/analytics-files"
58
+ },
59
+ "publishConfig": {
60
+ "access": "public"
61
+ },
62
+ "scripts": {
63
+ "build": "tsc -p tsconfig.json",
64
+ "test": "corepack pnpm run build && node --test tests/*.test.cjs",
65
+ "typecheck": "tsc -p tsconfig.json --noEmit"
66
+ }
6
67
  }