@chengxianglibra/dsh-data-analysis 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -53
- package/cordis.patch.yml +1 -1
- package/lib/bin/environment.js +9 -7
- package/lib/bin/presentation-lint.js +21 -0
- package/lib/bridges.js +2 -2
- package/lib/client/credentials/changes.js +74 -0
- package/lib/client/credentials/create-datasource.js +171 -0
- package/lib/client/credentials/credential-draft.js +42 -0
- package/lib/client/credentials/defaults.js +11 -0
- package/lib/client/credentials/descriptions.js +46 -0
- package/lib/client/credentials/install.js +319 -0
- package/lib/client/credentials/model.js +766 -0
- package/lib/client/credentials/report-publishing.js +96 -0
- package/lib/client/credentials/styles.js +21 -0
- package/lib/client/i18n/context.js +22 -0
- package/lib/client/i18n/copy.js +57 -0
- package/lib/client/i18n/credentials.js +475 -0
- package/lib/client/i18n/host.js +27 -0
- package/lib/client/i18n/navigation.js +41 -0
- package/lib/client/i18n/presentation.js +577 -0
- package/lib/client/i18n/python.js +36 -0
- package/lib/client/i18n/semantic.js +441 -0
- package/lib/client/input-context.js +21 -0
- package/lib/client/python-tool/card.js +35 -0
- package/lib/client/python-tool/execution-error.js +35 -0
- package/lib/client/python-tool/install.js +9 -0
- package/lib/client/python-tool/model.js +101 -0
- package/lib/client/right-tabs/install.js +535 -0
- package/lib/client/right-tabs/live-delivery.js +58 -0
- package/lib/client/right-tabs/navigation.js +47 -0
- package/lib/client/right-tabs/page.js +178 -0
- package/lib/client/right-tabs/styles.js +11 -0
- package/lib/client/rpc.js +5 -0
- package/lib/client/semantic-reference.js +10 -0
- package/lib/client/workspace-header-action.js +24 -0
- package/lib/client/workspace-shortcuts.js +26 -0
- package/lib/client.js +1250 -730
- package/lib/compatibility.js +2 -2
- package/lib/datasource/authoring.js +109 -0
- package/lib/datasource/bridge-programs.js +42 -27
- package/lib/datasource/bridge.js +144 -9
- package/lib/datasource/changes-contract.js +41 -0
- package/lib/datasource/changes.js +49 -0
- package/lib/datasource/configure.js +39 -0
- package/lib/datasource/defaults.js +25 -0
- package/lib/datasource/index.js +4 -2
- package/lib/datasource/python-options.js +21 -0
- package/lib/datasource/python.js +206 -0
- package/lib/datasource/resolver-program.js +112 -0
- package/lib/datasource/rpc.js +150 -0
- package/lib/datasource/service.js +962 -0
- package/lib/datasource/shell-env.js +2 -333
- package/lib/datasource/test.js +25 -51
- package/lib/disclosure/activation.js +19 -7
- package/lib/disclosure/execution-guidance.js +52 -0
- package/lib/disclosure/help.js +4 -1
- package/lib/environment/binding.js +54 -9
- package/lib/environment/runtime.js +120 -108
- package/lib/environment/subprocess.js +6 -1
- package/lib/environment/summary.js +3 -3
- package/lib/environment/workspace.js +23 -1
- package/lib/index.js +1 -1
- package/lib/lifecycle.js +33 -0
- package/lib/plugin-agents.js +154 -0
- package/lib/plugin.js +172 -133
- package/lib/presentation/asset.js +34 -0
- package/lib/presentation/assets/portable.js +606 -0
- package/lib/presentation/assets/static.js +1216 -0
- package/lib/presentation/build/index.js +72 -0
- package/lib/presentation/commit.js +163 -0
- package/lib/presentation/contracts/asset-path.js +9 -0
- package/lib/presentation/contracts/catalog.js +62 -0
- package/lib/presentation/contracts/charts.js +453 -0
- package/lib/presentation/contracts/editing.js +58 -0
- package/lib/presentation/contracts/index.js +663 -0
- package/lib/presentation/contracts/interaction.js +62 -0
- package/lib/presentation/contracts/types.js +15 -0
- package/lib/presentation/contracts/values.js +28 -0
- package/lib/presentation/delivery.js +71 -0
- package/lib/presentation/export-html.js +214 -0
- package/lib/presentation/files.js +82 -0
- package/lib/presentation/index.js +6 -0
- package/lib/presentation/lint.js +46 -0
- package/lib/presentation/projection/files.js +85 -0
- package/lib/presentation/projection/index.js +196 -0
- package/lib/presentation/projection/program.js +137 -0
- package/lib/presentation/projection/sql-code.js +79 -0
- package/lib/presentation/receipt.js +34 -0
- package/lib/presentation/reports.js +219 -0
- package/lib/presentation/rpc.js +186 -0
- package/lib/presentation/tool.js +118 -0
- package/lib/python-execution-contracts.js +1 -0
- package/lib/python-execution.js +232 -0
- package/lib/report-publishing/adapters.js +111 -0
- package/lib/report-publishing/config.js +69 -0
- package/lib/report-publishing/service.js +180 -0
- package/lib/rpc.js +49 -0
- package/lib/semantic-browser/contracts.js +97 -0
- package/lib/semantic-browser/definition.js +64 -0
- package/lib/semantic-browser/program.js +129 -0
- package/lib/semantic-browser/service.js +106 -0
- package/lib/semantic-reference/bridge.js +127 -0
- package/lib/semantic-reference/contracts.js +127 -0
- package/lib/semantic-reference/environment.js +56 -0
- package/lib/semantic-reference/index.js +3 -0
- package/lib/semantic-reference/labels.js +16 -0
- package/lib/semantic-reference/rpc.js +143 -0
- package/lib/semantic-reference/search.js +95 -0
- package/lib/semantic-reference/usage.js +152 -0
- package/lib/tool-lifecycle.js +22 -0
- package/lib/types/bridges.d.ts +2 -2
- package/lib/types/client/credentials/changes.d.ts +9 -0
- package/lib/types/client/credentials/create-datasource.d.ts +7 -0
- package/lib/types/client/credentials/credential-draft.d.ts +14 -0
- package/lib/types/client/credentials/defaults.d.ts +2 -0
- package/lib/types/client/credentials/descriptions.d.ts +1 -0
- package/lib/types/client/credentials/install.d.ts +19 -0
- package/lib/types/client/credentials/model.d.ts +79 -0
- package/lib/types/client/credentials/report-publishing.d.ts +5 -0
- package/lib/types/client/credentials/styles.d.ts +1 -0
- package/lib/types/client/i18n/context.d.ts +17 -0
- package/lib/types/client/i18n/copy.d.ts +806 -0
- package/lib/types/client/i18n/credentials.d.ts +475 -0
- package/lib/types/client/i18n/host.d.ts +16 -0
- package/lib/types/client/i18n/navigation.d.ts +41 -0
- package/lib/types/client/i18n/presentation.d.ts +577 -0
- package/lib/types/client/i18n/python.d.ts +19 -0
- package/lib/types/client/i18n/semantic.d.ts +441 -0
- package/lib/types/client/input-context.d.ts +19 -0
- package/lib/types/client/presentation/ask-dsh.d.ts +5 -0
- package/lib/types/client/presentation/catalog-model.d.ts +24 -0
- package/lib/types/client/presentation/catalog.d.ts +20 -0
- package/lib/types/client/presentation/chart-explorer.d.ts +12 -0
- package/lib/types/client/presentation/chart-geometry.d.ts +30 -0
- package/lib/types/client/presentation/chart-renderer.d.ts +10 -0
- package/lib/types/client/presentation/chart-specials.d.ts +10 -0
- package/lib/types/client/presentation/chart-styles.d.ts +1 -0
- package/lib/types/client/presentation/chart-tooltip.d.ts +13 -0
- package/lib/types/client/presentation/chart-view.d.ts +23 -0
- package/lib/types/client/presentation/context-reference.d.ts +12 -0
- package/lib/types/client/presentation/copy-context.d.ts +5 -0
- package/lib/types/client/presentation/delivery-model.d.ts +75 -0
- package/lib/types/client/presentation/delivery.d.ts +78 -0
- package/lib/types/client/presentation/download.d.ts +2 -0
- package/lib/types/client/presentation/editor-controls.d.ts +12 -0
- package/lib/types/client/presentation/export-menu.d.ts +26 -0
- package/lib/types/client/presentation/export-view.d.ts +63 -0
- package/lib/types/client/presentation/global-filters.d.ts +6 -0
- package/lib/types/client/presentation/host-entry.d.ts +18 -0
- package/lib/types/client/presentation/icons.d.ts +4 -0
- package/lib/types/client/presentation/markdown.d.ts +5 -0
- package/lib/types/client/presentation/model.d.ts +61 -0
- package/lib/types/client/presentation/reader.d.ts +24 -0
- package/lib/types/client/presentation/reference-source.d.ts +9 -0
- package/lib/types/client/presentation/source-code-model.d.ts +17 -0
- package/lib/types/client/presentation/source-code.d.ts +6 -0
- package/lib/types/client/presentation/source-dialog.d.ts +12 -0
- package/lib/types/client/presentation/source-facts.d.ts +20 -0
- package/lib/types/client/presentation/source-format.d.ts +5 -0
- package/lib/types/client/presentation/source-highlight.d.ts +7 -0
- package/lib/types/client/presentation/sources.d.ts +13 -0
- package/lib/types/client/presentation/styles.d.ts +1 -0
- package/lib/types/client/presentation/table.d.ts +16 -0
- package/lib/types/client/presentation/use-element-width.d.ts +5 -0
- package/lib/types/client/python-tool/card.d.ts +2 -0
- package/lib/types/client/python-tool/execution-error.d.ts +3 -0
- package/lib/types/client/python-tool/install.d.ts +2 -0
- package/lib/types/client/python-tool/model.d.ts +20 -0
- package/lib/types/client/right-tabs/install.d.ts +18 -0
- package/lib/types/client/right-tabs/live-delivery.d.ts +8 -0
- package/lib/types/client/right-tabs/navigation.d.ts +17 -0
- package/lib/types/client/right-tabs/page.d.ts +36 -0
- package/lib/types/client/right-tabs/styles.d.ts +2 -0
- package/lib/types/client/rpc.d.ts +3 -0
- package/lib/types/client/semantic-reference.d.ts +4 -0
- package/lib/types/client/workspace-header-action.d.ts +7 -0
- package/lib/types/client/workspace-shortcuts.d.ts +15 -0
- package/lib/types/client.d.ts +9 -124
- package/lib/types/compatibility.d.ts +3 -3
- package/lib/types/datasource/authoring.d.ts +35 -0
- package/lib/types/datasource/bridge.d.ts +43 -4
- package/lib/types/datasource/changes-contract.d.ts +18 -0
- package/lib/types/datasource/changes.d.ts +15 -0
- package/lib/types/datasource/configure.d.ts +7 -0
- package/lib/types/datasource/defaults.d.ts +7 -0
- package/lib/types/datasource/index.d.ts +8 -5
- package/lib/types/datasource/python-options.d.ts +9 -0
- package/lib/types/datasource/python.d.ts +19 -0
- package/lib/types/datasource/resolver-program.d.ts +6 -0
- package/lib/types/datasource/rpc.d.ts +5 -0
- package/lib/types/datasource/service.d.ts +155 -0
- package/lib/types/datasource/shell-env.d.ts +0 -29
- package/lib/types/datasource/test.d.ts +6 -29
- package/lib/types/disclosure/activation.d.ts +2 -1
- package/lib/types/disclosure/execution-guidance.d.ts +6 -0
- package/lib/types/disclosure/help.d.ts +1 -1
- package/lib/types/environment/binding.d.ts +5 -1
- package/lib/types/environment/errors.d.ts +1 -1
- package/lib/types/environment/runtime.d.ts +5 -5
- package/lib/types/environment/types.d.ts +15 -3
- package/lib/types/environment/workspace.d.ts +1 -0
- package/lib/types/index.d.ts +1 -1
- package/lib/types/lifecycle.d.ts +8 -0
- package/lib/types/plugin-agents.d.ts +27 -0
- package/lib/types/plugin.d.ts +14 -24
- package/lib/types/presentation/asset.d.ts +6 -0
- package/lib/types/presentation/build/index.d.ts +16 -0
- package/lib/types/presentation/commit.d.ts +4 -0
- package/lib/types/presentation/contracts/asset-path.d.ts +2 -0
- package/lib/types/presentation/contracts/catalog.d.ts +27 -0
- package/lib/types/presentation/contracts/charts.d.ts +11 -0
- package/lib/types/presentation/contracts/editing.d.ts +8 -0
- package/lib/types/presentation/contracts/index.d.ts +11 -0
- package/lib/types/presentation/contracts/interaction.d.ts +8 -0
- package/lib/types/presentation/contracts/types.d.ts +255 -0
- package/lib/types/presentation/contracts/values.d.ts +9 -0
- package/lib/types/presentation/delivery.d.ts +3 -0
- package/lib/types/presentation/export-html.d.ts +13 -0
- package/lib/types/presentation/files.d.ts +7 -0
- package/lib/types/presentation/index.d.ts +6 -0
- package/lib/types/presentation/lint.d.ts +13 -0
- package/lib/types/presentation/projection/files.d.ts +2 -0
- package/lib/types/presentation/projection/index.d.ts +20 -0
- package/lib/types/presentation/projection/program.d.ts +2 -0
- package/lib/types/presentation/projection/sql-code.d.ts +2 -0
- package/lib/types/presentation/receipt.d.ts +14 -0
- package/lib/types/presentation/reports.d.ts +10 -0
- package/lib/types/presentation/rpc.d.ts +25 -0
- package/lib/types/presentation/tool.d.ts +12 -0
- package/lib/types/python-execution-contracts.d.ts +10 -0
- package/lib/types/python-execution.d.ts +10 -0
- package/lib/types/report-publishing/adapters.d.ts +6 -0
- package/lib/types/report-publishing/config.d.ts +22 -0
- package/lib/types/report-publishing/service.d.ts +68 -0
- package/lib/types/rpc.d.ts +3 -0
- package/lib/types/semantic-browser/contracts.d.ts +47 -0
- package/lib/types/semantic-browser/definition.d.ts +20 -0
- package/lib/types/semantic-browser/program.d.ts +2 -0
- package/lib/types/semantic-browser/service.d.ts +26 -0
- package/lib/types/semantic-reference/bridge.d.ts +12 -0
- package/lib/types/semantic-reference/contracts.d.ts +54 -0
- package/lib/types/semantic-reference/environment.d.ts +22 -0
- package/lib/types/semantic-reference/index.d.ts +3 -0
- package/lib/types/semantic-reference/labels.d.ts +16 -0
- package/lib/types/semantic-reference/rpc.d.ts +15 -0
- package/lib/types/semantic-reference/search.d.ts +10 -0
- package/lib/types/semantic-reference/usage.d.ts +43 -0
- package/lib/types/tool-lifecycle.d.ts +4 -0
- package/lib/types/workspace-identity.d.ts +6 -0
- package/lib/workspace-identity.js +9 -0
- package/package.json +99 -53
- package/python/presentation-kit/dist/dsh_data_analysis_presentation_kit-1.1.0-py3-none-any.whl +0 -0
- package/skills/dsh-data-analysis-files/SKILL.md +32 -0
- package/skills/dsh-data-analysis-files/references/examples.md +93 -0
- package/skills/dsh-data-analysis-presentation/SKILL.md +28 -0
- package/skills/dsh-data-analysis-presentation/references/chart-examples.md +18 -0
- package/skills/dsh-data-analysis-presentation/references/charts.md +55 -0
- package/skills/dsh-data-analysis-presentation/references/examples/artifact.draft.json +36 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-bins.dataset.json +33 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-boxes.dataset.json +45 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-composition.dataset.json +33 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-funnel.dataset.json +27 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-precision.dataset.json +21 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-series.dataset.json +58 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-shares.dataset.json +33 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-waterfall.dataset.json +44 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts.draft.json +393 -0
- package/skills/dsh-data-analysis-presentation/references/examples/comparison-selected.dataset.json +24 -0
- package/skills/dsh-data-analysis-presentation/references/examples/comparison-summary.dataset.json +22 -0
- package/skills/dsh-data-analysis-presentation/references/examples/comparison.dataset.json +18 -0
- package/skills/dsh-data-analysis-presentation/references/examples/comparison.draft.json +80 -0
- package/skills/dsh-data-analysis-presentation/references/examples/computed-sources.draft.json +42 -0
- package/skills/dsh-data-analysis-presentation/references/examples/computed.dataset.json +12 -0
- package/skills/dsh-data-analysis-presentation/references/examples/computed.draft.json +58 -0
- package/skills/dsh-data-analysis-presentation/references/examples/interaction-detail.dataset.json +30 -0
- package/skills/dsh-data-analysis-presentation/references/examples/interaction-summary.dataset.json +22 -0
- package/skills/dsh-data-analysis-presentation/references/examples/interaction.draft.json +279 -0
- package/skills/dsh-data-analysis-presentation/references/examples/source-only.draft.json +27 -0
- package/skills/dsh-data-analysis-presentation/references/examples/write-charts.py +73 -0
- package/skills/dsh-data-analysis-presentation/references/examples/write-comparison.py +42 -0
- package/skills/dsh-data-analysis-presentation/references/examples/write-computed.py +21 -0
- package/skills/dsh-data-analysis-presentation/references/examples/write-interaction.py +33 -0
- package/skills/dsh-data-analysis-presentation/references/examples.md +44 -0
- package/skills/dsh-data-analysis-presentation/references/export-html.md +7 -0
- package/skills/dsh-data-analysis-presentation/references/interaction.md +56 -0
- package/skills/dsh-data-analysis-presentation/references/narrative.md +37 -0
- package/skills/dsh-data-analysis-presentation/references/publishing.md +3 -0
- package/skills/dsh-data-analysis-presentation/references/schema.md +162 -0
- package/lib/datasource/access.js +0 -60
- package/lib/datasource/credentials.js +0 -24
- package/lib/evidence/bridge-program.js +0 -153
- package/lib/evidence/bridge.js +0 -166
- package/lib/evidence/index.js +0 -2
- package/lib/evidence/sources.js +0 -263
- package/lib/types/datasource/access.d.ts +0 -27
- package/lib/types/evidence/bridge.d.ts +0 -40
- package/lib/types/evidence/index.d.ts +0 -4
- package/lib/types/evidence/sources.d.ts +0 -57
- package/python/report-kit/dist/dsh_data_analysis_report_kit-3.0.0-py3-none-any.whl +0 -0
- package/skills/dsh-data-analysis-report/SKILL.md +0 -57
- package/skills/dsh-data-analysis-report/assets/marivo-artifact.js +0 -113
- package/skills/dsh-data-analysis-report/assets/marivo-session-dag.js +0 -1201
- package/skills/dsh-data-analysis-report/assets/report-data.js +0 -449
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { performance } from 'node:perf_hooks';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
4
|
+
import { savePythonExecution } from "../python-execution.js";
|
|
5
|
+
import { registerMarivoTool } from "../tool-lifecycle.js";
|
|
6
|
+
import { resolveMarivoDatasourceBridge } from "./bridge.js";
|
|
7
|
+
import { pythonTimeout, resolvePythonOptions } from "./python-options.js";
|
|
8
|
+
import { PYTHON_LAUNCHER, PYTHON_WORKER } from "./resolver-program.js";
|
|
9
|
+
import { CredentialServiceError, } from "./service.js";
|
|
10
|
+
import { datasourceToolValue } from "./test.js";
|
|
11
|
+
/** The message survives Harness error normalization; it contains only safe adapter facts. */
|
|
12
|
+
export class MarivoPythonExecutionError extends Error {
|
|
13
|
+
execution;
|
|
14
|
+
constructor(message, execution) {
|
|
15
|
+
super(`${message}; execution=${JSON.stringify(execution)}`);
|
|
16
|
+
this.name = 'MarivoPythonExecutionError';
|
|
17
|
+
this.execution = execution;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const CHECK_EFFECTS = 'Inspect existing effects and the intended Session through current Runtime Help before retrying; do not automatically replay. Saved results and remote query cancellation are not confirmed.';
|
|
21
|
+
const PREPARATION_CODES = new Set([
|
|
22
|
+
'agent-required',
|
|
23
|
+
'invalid-datasources',
|
|
24
|
+
'call-ended',
|
|
25
|
+
'context-changed',
|
|
26
|
+
'credentials-changed',
|
|
27
|
+
'execution-ended',
|
|
28
|
+
'disposed',
|
|
29
|
+
]);
|
|
30
|
+
function quote(value) {
|
|
31
|
+
return process.platform === 'win32'
|
|
32
|
+
? `'${value.replaceAll("'", "''")}'`
|
|
33
|
+
: `'${value.replaceAll("'", "'\\''")}'`;
|
|
34
|
+
}
|
|
35
|
+
export function registerMarivoPythonTool(ctx, source, service, options = {}) {
|
|
36
|
+
const limits = resolvePythonOptions(options);
|
|
37
|
+
return registerMarivoTool(ctx, defineTool({
|
|
38
|
+
name: 'marivo_python',
|
|
39
|
+
description: 'Execute foreground Python in the bound Runtime and Workspace, including local pandas and native DuckDB file analysis. This call waits for missing credentials for every listed Marivo datasource before starting Python once; configured credentials do not trigger a connection test. When using Marivo Sessions or readers, create/resume them inside this execution and close Sessions in finally. No background execution or secret environment variables. Nonzero exits are reported without replay. Successful execution saves the exact submitted code and returns codeRef; explicitly associate it with presentation datasets through draft codeRefs. A codeCaptureError does not change the execution outcome and must not trigger replay. The execution summary reports adapter phase, outcome and effective Shell budget, not query progress or saved Artifacts. Credential waiting precedes the Shell budget; outer Code Mode deadlines and cancellation still apply. Timeout does not confirm remote query cancellation or absence of saved results.',
|
|
40
|
+
parameters: {
|
|
41
|
+
code: {
|
|
42
|
+
type: 'string',
|
|
43
|
+
required: true,
|
|
44
|
+
description: 'Python code, never credential values. Close resources within this call. When using Marivo Session APIs, consult current Runtime Help and close Sessions in finally.',
|
|
45
|
+
},
|
|
46
|
+
datasources: {
|
|
47
|
+
type: 'array',
|
|
48
|
+
required: true,
|
|
49
|
+
items: { type: 'string' },
|
|
50
|
+
description: 'All exact Marivo datasource names this execution may access through data or metadata connections, including inspection and datasources without passwords. Pass [] for local pandas or native DuckDB file analysis without Marivo datasource access. Catalog-only definition reads need no datasource connection.',
|
|
51
|
+
},
|
|
52
|
+
timeoutMs: {
|
|
53
|
+
type: 'number',
|
|
54
|
+
description: `Optional foreground Shell timeout in milliseconds, a positive integer at most 2147483647. Defaults to ${limits.pythonTimeoutMs}; capped at ${limits.pythonMaxTimeoutMs} and then by Harness Shell. Outer Code Mode deadlines remain independent.`,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
output: {
|
|
58
|
+
schema: { type: 'json' },
|
|
59
|
+
render: (_args, value) => [{ type: 'text', text: JSON.stringify(value) }],
|
|
60
|
+
},
|
|
61
|
+
async execute(args, exec) {
|
|
62
|
+
const calledAt = performance.now();
|
|
63
|
+
let phase = 'preparing';
|
|
64
|
+
let requestedTimeoutMs = null;
|
|
65
|
+
let effectiveTimeoutMs = null;
|
|
66
|
+
let shellStartedAt = null;
|
|
67
|
+
let executionElapsedMs = null;
|
|
68
|
+
let prepared;
|
|
69
|
+
const summary = (reason, nextAction) => {
|
|
70
|
+
const now = performance.now();
|
|
71
|
+
return {
|
|
72
|
+
phase,
|
|
73
|
+
reason,
|
|
74
|
+
requestedTimeoutMs,
|
|
75
|
+
effectiveTimeoutMs,
|
|
76
|
+
elapsedMs: now - calledAt,
|
|
77
|
+
executionElapsedMs: executionElapsedMs ?? (shellStartedAt === null ? null : now - shellStartedAt),
|
|
78
|
+
nextAction,
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
const rejectInput = (message) => {
|
|
82
|
+
throw new MarivoPythonExecutionError(message, summary('not-started', 'Correct the input or Host configuration before retrying.'));
|
|
83
|
+
};
|
|
84
|
+
try {
|
|
85
|
+
try {
|
|
86
|
+
requestedTimeoutMs = pythonTimeout(args.timeoutMs === undefined ? limits.pythonTimeoutMs : args.timeoutMs, 'timeoutMs');
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
rejectInput('timeoutMs must be a positive integer no greater than 2147483647');
|
|
90
|
+
}
|
|
91
|
+
const code = args.code;
|
|
92
|
+
if (typeof code !== 'string' || !code.trim() || Buffer.byteLength(code) > 131072)
|
|
93
|
+
rejectInput('Invalid Python code size');
|
|
94
|
+
const shell = ctx.get('shell');
|
|
95
|
+
if (!shell)
|
|
96
|
+
rejectInput('DSH Shell execution service is required');
|
|
97
|
+
const shellEnv = ctx.get('shellEnv');
|
|
98
|
+
if (!shellEnv)
|
|
99
|
+
rejectInput('DSH Shell environment service is required');
|
|
100
|
+
const policyService = ctx.get('sandboxPolicy');
|
|
101
|
+
if (shell.sandboxMode !== undefined && !policyService)
|
|
102
|
+
rejectInput('DSH sandbox policy is required');
|
|
103
|
+
const admission = await service.track(service.prepareExecution(exec, () => resolveMarivoDatasourceBridge(source), args.datasources));
|
|
104
|
+
if (!('status' in admission) || admission.status !== 'ready')
|
|
105
|
+
return {
|
|
106
|
+
...datasourceToolValue(admission),
|
|
107
|
+
execution: summary('not-started', 'Resolve the datasource credential requirement or reported connection failure before retrying.'),
|
|
108
|
+
};
|
|
109
|
+
prepared = admission;
|
|
110
|
+
const { binding } = prepared.bridge;
|
|
111
|
+
const policy = policyService?.resolve(exec.agent ? { session: exec.agent.session } : {});
|
|
112
|
+
const spec = shell.resolve({
|
|
113
|
+
command: `${process.platform === 'win32' ? '& ' : ''}${quote(binding.pythonExecutable)} -c ${quote(PYTHON_LAUNCHER)}`,
|
|
114
|
+
workdir: binding.projectRoot,
|
|
115
|
+
timeoutMs: Math.min(requestedTimeoutMs, limits.pythonMaxTimeoutMs),
|
|
116
|
+
signal: prepared.signal,
|
|
117
|
+
stdin: JSON.stringify({
|
|
118
|
+
identity: binding,
|
|
119
|
+
project_root: binding.projectRoot,
|
|
120
|
+
grants: prepared.grants,
|
|
121
|
+
values: prepared.values,
|
|
122
|
+
code,
|
|
123
|
+
worker: PYTHON_WORKER,
|
|
124
|
+
}),
|
|
125
|
+
env: { MARIVO_PERSIST_CREDENTIALS: '0' },
|
|
126
|
+
dshEnv: shellEnv.collect(exec),
|
|
127
|
+
...(policy ? { sandboxPolicy: policy } : {}),
|
|
128
|
+
});
|
|
129
|
+
effectiveTimeoutMs = spec.timeoutMs;
|
|
130
|
+
// Keep this guard adjacent to the only launch, after synchronous Host hooks.
|
|
131
|
+
prepared.assertCurrent();
|
|
132
|
+
const startedAt = new Date().toISOString();
|
|
133
|
+
phase = 'executing';
|
|
134
|
+
shellStartedAt = performance.now();
|
|
135
|
+
const result = await service.track(shell.run(spec));
|
|
136
|
+
executionElapsedMs = performance.now() - shellStartedAt;
|
|
137
|
+
const finishedAt = new Date().toISOString();
|
|
138
|
+
const credentialValues = prepared.values;
|
|
139
|
+
// Defense at the result seam as well as before Harness collection/spill.
|
|
140
|
+
const redact = (text) => Object.values(credentialValues)
|
|
141
|
+
.filter(Boolean)
|
|
142
|
+
.sort((a, b) => b.length - a.length)
|
|
143
|
+
.reduce((out, value) => out.split(value).join('[REDACTED]'), text);
|
|
144
|
+
let codeRef;
|
|
145
|
+
let codeCaptureError;
|
|
146
|
+
if (result.exitCode === 0 && !result.timedOut && !result.aborted) {
|
|
147
|
+
phase = 'capturing-code';
|
|
148
|
+
try {
|
|
149
|
+
prepared.assertCurrent();
|
|
150
|
+
if (Object.values(prepared.values).some((value) => value && code.includes(value)))
|
|
151
|
+
throw new Error('Host credential in submitted code');
|
|
152
|
+
codeRef = await service.track(savePythonExecution(binding.projectRoot, { text: code, startedAt, finishedAt }, prepared.signal));
|
|
153
|
+
prepared.assertCurrent();
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
codeRef = undefined;
|
|
157
|
+
codeCaptureError =
|
|
158
|
+
'Python completed successfully, but its source code could not be saved. Do not rerun the code to recover this snapshot; inspect its existing effects.';
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const reason = result.timedOut
|
|
162
|
+
? 'timed-out'
|
|
163
|
+
: result.aborted
|
|
164
|
+
? 'cancelled'
|
|
165
|
+
: result.exitCode === 0
|
|
166
|
+
? 'succeeded'
|
|
167
|
+
: result.exitCode === null
|
|
168
|
+
? 'unknown'
|
|
169
|
+
: 'nonzero-exit';
|
|
170
|
+
return JSON.parse(JSON.stringify({
|
|
171
|
+
exitCode: result.exitCode,
|
|
172
|
+
timedOut: result.timedOut,
|
|
173
|
+
aborted: result.aborted,
|
|
174
|
+
stdout: redact(result.stdout.text),
|
|
175
|
+
stderr: redact(result.stderr.text),
|
|
176
|
+
truncated: result.stdout.truncated || result.stderr.truncated,
|
|
177
|
+
sandbox: result.sandbox ?? null,
|
|
178
|
+
...(codeRef ? { codeRef } : {}),
|
|
179
|
+
...(codeCaptureError ? { codeCaptureError } : {}),
|
|
180
|
+
execution: summary(reason, codeCaptureError ?? (reason === 'succeeded' ? null : CHECK_EFFECTS)),
|
|
181
|
+
}));
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
if (error instanceof MarivoPythonExecutionError)
|
|
185
|
+
throw error;
|
|
186
|
+
const cancelled = (prepared?.signal ?? exec.signal).aborted;
|
|
187
|
+
const reason = cancelled
|
|
188
|
+
? 'cancelled'
|
|
189
|
+
: shellStartedAt === null
|
|
190
|
+
? 'not-started'
|
|
191
|
+
: 'unknown';
|
|
192
|
+
const code = error instanceof CredentialServiceError && PREPARATION_CODES.has(error.code)
|
|
193
|
+
? ` (${error.code})`
|
|
194
|
+
: '';
|
|
195
|
+
throw new MarivoPythonExecutionError(shellStartedAt === null
|
|
196
|
+
? `Marivo Python preparation failed or was cancelled; Python was not started${code}`
|
|
197
|
+
: 'Marivo Python execution outcome is unknown after an execution service failure or cancellation', summary(reason, shellStartedAt === null
|
|
198
|
+
? 'Check credentials, Workspace binding and Host execution policy before retrying.'
|
|
199
|
+
: CHECK_EFFECTS));
|
|
200
|
+
}
|
|
201
|
+
finally {
|
|
202
|
+
prepared?.release();
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
}));
|
|
206
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/** Shared Python adapter. All inputs below arrive on a Host-owned pipe. */
|
|
2
|
+
export const RESOLVER_PROGRAM = String.raw `
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import marivo
|
|
9
|
+
import marivo.datasource as md
|
|
10
|
+
|
|
11
|
+
if not all(hasattr(md, name) for name in ("credential_scope", "CredentialRequest", "SecretValue", "DatasourceCredentialError")):
|
|
12
|
+
raise RuntimeError("Marivo credential_scope capability is required; upgrade the bound Runtime")
|
|
13
|
+
|
|
14
|
+
def definition(description):
|
|
15
|
+
return hashlib.sha256(json.dumps({
|
|
16
|
+
"name": description.name, "backend": description.backend_type,
|
|
17
|
+
"fields": description.literal_fields, "refs": description.env_refs,
|
|
18
|
+
}, sort_keys=True, default=str).encode()).hexdigest()
|
|
19
|
+
|
|
20
|
+
class SnapshotResolver:
|
|
21
|
+
def __init__(self, payload):
|
|
22
|
+
self.root = Path(payload["project_root"]).resolve()
|
|
23
|
+
self.grants = payload["grants"]
|
|
24
|
+
self.values = payload["values"]
|
|
25
|
+
|
|
26
|
+
def resolve(self, request):
|
|
27
|
+
grant = self.grants.get(request.datasource)
|
|
28
|
+
if request.cancelled:
|
|
29
|
+
reason = "timeout"
|
|
30
|
+
elif Path(request.project_root).resolve() != self.root or grant is None:
|
|
31
|
+
reason = "denied"
|
|
32
|
+
elif any(grant["fields"].get(field) != request.reference for field in request.fields):
|
|
33
|
+
reason = "denied"
|
|
34
|
+
elif not request.fields or request.reference not in grant["fields"].values():
|
|
35
|
+
reason = "denied"
|
|
36
|
+
elif definition(md.describe(request.datasource)) != grant["definition"]:
|
|
37
|
+
reason = "denied"
|
|
38
|
+
elif not self.values.get(request.reference):
|
|
39
|
+
reason = "missing"
|
|
40
|
+
else:
|
|
41
|
+
return md.SecretValue(self.values[request.reference])
|
|
42
|
+
raise md.DatasourceCredentialError(reason=reason, reference=request.reference,
|
|
43
|
+
datasource=request.datasource, fields=request.fields)
|
|
44
|
+
`.trim();
|
|
45
|
+
/** The worker checks the exact binding and enters scope before any user code. */
|
|
46
|
+
export const PYTHON_WORKER = `${RESOLVER_PROGRAM}\n${String.raw `
|
|
47
|
+
payload = json.load(sys.stdin)
|
|
48
|
+
expected = payload["identity"]
|
|
49
|
+
if (os.path.abspath(sys.executable) != expected["pythonExecutable"] or
|
|
50
|
+
marivo.__version__ != expected["marivoVersion"] or
|
|
51
|
+
os.path.abspath(marivo.__file__) != expected["packagePath"] or
|
|
52
|
+
str(Path.cwd().resolve()) != payload["project_root"]):
|
|
53
|
+
raise RuntimeError("Marivo execution identity changed; rebind required")
|
|
54
|
+
resolver = SnapshotResolver(payload)
|
|
55
|
+
code = payload.pop("code")
|
|
56
|
+
sys.stdin = open(os.devnull)
|
|
57
|
+
try:
|
|
58
|
+
with md.credential_scope(resolver=resolver):
|
|
59
|
+
for name, grant in resolver.grants.items():
|
|
60
|
+
description = md.describe(name)
|
|
61
|
+
if grant["name"] != name or description.name != name:
|
|
62
|
+
raise RuntimeError("Marivo datasource identity changed; execution cancelled")
|
|
63
|
+
if definition(description) != grant["definition"]:
|
|
64
|
+
raise RuntimeError("Marivo datasource definition changed; execution cancelled")
|
|
65
|
+
exec(compile(code, "<marivo_python>", "exec"), {"__name__": "__main__"})
|
|
66
|
+
finally:
|
|
67
|
+
resolver.values.clear()
|
|
68
|
+
payload.clear()
|
|
69
|
+
`}`;
|
|
70
|
+
/** Capture raw fd output before Harness collection/spill, including driver writes. */
|
|
71
|
+
export const PYTHON_LAUNCHER = String.raw `
|
|
72
|
+
import json, subprocess, sys, threading
|
|
73
|
+
payload = json.load(sys.stdin)
|
|
74
|
+
worker = payload.pop("worker")
|
|
75
|
+
values = sorted(set(payload["values"].values()), key=len, reverse=True)
|
|
76
|
+
child = subprocess.Popen([sys.executable, "-c", worker], stdin=subprocess.PIPE,
|
|
77
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
78
|
+
streams = [bytearray(), bytearray()]
|
|
79
|
+
overflow = threading.Event()
|
|
80
|
+
def collect(pipe, target):
|
|
81
|
+
while True:
|
|
82
|
+
chunk = pipe.read(8192)
|
|
83
|
+
if not chunk:
|
|
84
|
+
break
|
|
85
|
+
if len(target) + len(chunk) > 1048576:
|
|
86
|
+
overflow.set()
|
|
87
|
+
child.kill()
|
|
88
|
+
elif not overflow.is_set():
|
|
89
|
+
target.extend(chunk)
|
|
90
|
+
threads = [threading.Thread(target=collect, args=(pipe, buf), daemon=True)
|
|
91
|
+
for pipe, buf in zip((child.stdout, child.stderr), streams)]
|
|
92
|
+
for thread in threads:
|
|
93
|
+
thread.start()
|
|
94
|
+
try:
|
|
95
|
+
child.stdin.write(json.dumps(payload).encode())
|
|
96
|
+
child.stdin.close()
|
|
97
|
+
except BrokenPipeError:
|
|
98
|
+
pass
|
|
99
|
+
child.wait()
|
|
100
|
+
for thread in threads:
|
|
101
|
+
thread.join()
|
|
102
|
+
if overflow.is_set():
|
|
103
|
+
print("Marivo Python output limit exceeded", file=sys.stderr)
|
|
104
|
+
raise SystemExit(74)
|
|
105
|
+
for stream, raw in zip((sys.stdout, sys.stderr), streams):
|
|
106
|
+
text = raw.decode("utf-8", errors="replace")
|
|
107
|
+
for value in values:
|
|
108
|
+
if value:
|
|
109
|
+
text = text.replace(value, "[REDACTED]")
|
|
110
|
+
stream.write(text)
|
|
111
|
+
raise SystemExit(child.returncode if child.returncode >= 0 else 1)
|
|
112
|
+
`.trim();
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { registerPluginRpc } from "../rpc.js";
|
|
3
|
+
import { withDatasourceDefaults } from "./defaults.js";
|
|
4
|
+
import { CREDENTIAL_CHANNEL, credentialError } from "./service.js";
|
|
5
|
+
const text = z.string().min(1).max(256);
|
|
6
|
+
const handle = { generation: z.string().uuid(), id: z.string().uuid(), scope: z.string().uuid() };
|
|
7
|
+
const mutation = z
|
|
8
|
+
.object({
|
|
9
|
+
...handle,
|
|
10
|
+
action: z.enum(['save', 'update', 'delete', 'delete-datasource', 'test', 'submit', 'diagnose']),
|
|
11
|
+
deleteCredentials: z.boolean().optional(),
|
|
12
|
+
version: z.string().max(65536),
|
|
13
|
+
requestId: z.string().uuid().optional(),
|
|
14
|
+
reference: text.optional(),
|
|
15
|
+
changes: z.record(text, z.string().min(1).max(65536)).optional(),
|
|
16
|
+
})
|
|
17
|
+
.strict();
|
|
18
|
+
export function registerCredentialRpc(connection, service, workspace, datasourceDefaults) {
|
|
19
|
+
const unregister = registerPluginRpc(connection, CREDENTIAL_CHANNEL, [
|
|
20
|
+
'overview',
|
|
21
|
+
'authoring',
|
|
22
|
+
'create-datasource',
|
|
23
|
+
'configuration',
|
|
24
|
+
'update-datasource',
|
|
25
|
+
'select-configuration',
|
|
26
|
+
'watch',
|
|
27
|
+
'start',
|
|
28
|
+
'operation',
|
|
29
|
+
'cancel-operation',
|
|
30
|
+
'cancel-request',
|
|
31
|
+
], async (endpoint, payload, signal) => {
|
|
32
|
+
try {
|
|
33
|
+
if (Buffer.byteLength(JSON.stringify(payload) ?? '') > 1_048_576)
|
|
34
|
+
throw new Error('invalid-request');
|
|
35
|
+
let value;
|
|
36
|
+
if (endpoint === 'overview') {
|
|
37
|
+
const input = z.object({ workspaceId: text }).strict().parse(payload);
|
|
38
|
+
value = {
|
|
39
|
+
generation: service.generation,
|
|
40
|
+
datasources: await service.overview(input.workspaceId, () => workspace(input.workspaceId), signal),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
else if (endpoint === 'authoring') {
|
|
44
|
+
const input = z
|
|
45
|
+
.object({ workspaceId: text, mode: z.enum(['create', 'edit']).default('create') })
|
|
46
|
+
.strict()
|
|
47
|
+
.parse(payload);
|
|
48
|
+
const bridge = await workspace(input.workspaceId);
|
|
49
|
+
if (!bridge.authoring)
|
|
50
|
+
throw new Error('authoring-unavailable');
|
|
51
|
+
const schema = await bridge.authoring(signal);
|
|
52
|
+
value = {
|
|
53
|
+
generation: service.generation,
|
|
54
|
+
...(input.mode === 'edit'
|
|
55
|
+
? schema
|
|
56
|
+
: withDatasourceDefaults(schema, datasourceDefaults)),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
else if (endpoint === 'configuration') {
|
|
60
|
+
const input = z.object({ workspaceId: text, name: text }).strict().parse(payload);
|
|
61
|
+
const bridge = await workspace(input.workspaceId);
|
|
62
|
+
if (!bridge.configuration)
|
|
63
|
+
throw new Error('authoring-unavailable');
|
|
64
|
+
value = await bridge.configuration(input.name, signal);
|
|
65
|
+
}
|
|
66
|
+
else if (endpoint === 'select-configuration') {
|
|
67
|
+
const input = z
|
|
68
|
+
.object({ workspaceId: text, requestId: z.string().uuid(), name: text })
|
|
69
|
+
.strict()
|
|
70
|
+
.parse(payload);
|
|
71
|
+
value = await service.selectConfiguration(input.requestId, input.workspaceId, input.name, () => workspace(input.workspaceId), signal);
|
|
72
|
+
}
|
|
73
|
+
else if (endpoint === 'create-datasource' || endpoint === 'update-datasource') {
|
|
74
|
+
const input = z
|
|
75
|
+
.object({
|
|
76
|
+
workspaceId: text,
|
|
77
|
+
generation: z.string().uuid(),
|
|
78
|
+
fingerprint: z.string().min(1).max(256),
|
|
79
|
+
backend: text,
|
|
80
|
+
name: text.optional(),
|
|
81
|
+
version: text.optional(),
|
|
82
|
+
requestId: z.string().uuid().optional(),
|
|
83
|
+
fields: z.record(text, z.unknown()),
|
|
84
|
+
})
|
|
85
|
+
.strict()
|
|
86
|
+
.parse(payload);
|
|
87
|
+
const request = input.requestId
|
|
88
|
+
? await service.configurationRequest(input.requestId, input.workspaceId, () => workspace(input.workspaceId), signal)
|
|
89
|
+
: undefined;
|
|
90
|
+
const operationSignal = request ? AbortSignal.any([signal, request.signal]) : signal;
|
|
91
|
+
const resolve = async () => {
|
|
92
|
+
if (input.requestId)
|
|
93
|
+
await service.configurationRequest(input.requestId, input.workspaceId, () => workspace(input.workspaceId), operationSignal);
|
|
94
|
+
return workspace(input.workspaceId);
|
|
95
|
+
};
|
|
96
|
+
if (endpoint === 'update-datasource') {
|
|
97
|
+
const { name, version } = z.object({ name: text, version: text }).parse(input);
|
|
98
|
+
if (request?.view.configuration.mode === 'edit' &&
|
|
99
|
+
request.view.configuration.name !== name)
|
|
100
|
+
throw new Error('invalid-request');
|
|
101
|
+
value = await service.updateDatasource(input.generation, input.fingerprint, { backend: input.backend, fields: input.fields, name, version }, resolve, operationSignal);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
if (input.name || input.version || request?.view.configuration.mode === 'edit')
|
|
105
|
+
throw new Error('invalid-request');
|
|
106
|
+
value = await service.createDatasource(input.generation, input.fingerprint, { backend: input.backend, fields: input.fields }, resolve, operationSignal);
|
|
107
|
+
}
|
|
108
|
+
if (input.requestId) {
|
|
109
|
+
const configured = await service.selectConfiguration(input.requestId, input.workspaceId, value.name, () => workspace(input.workspaceId), operationSignal);
|
|
110
|
+
value = { ...value, request: configured };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else if (endpoint === 'watch') {
|
|
114
|
+
const input = z
|
|
115
|
+
.object({ sessionId: text, cursor: z.string().max(256).optional() })
|
|
116
|
+
.strict()
|
|
117
|
+
.parse(payload);
|
|
118
|
+
value = await service.waitWatch(input.sessionId, input.cursor, signal);
|
|
119
|
+
}
|
|
120
|
+
else if (endpoint === 'start') {
|
|
121
|
+
value = service.start(mutation.parse(payload));
|
|
122
|
+
}
|
|
123
|
+
else if (endpoint === 'operation') {
|
|
124
|
+
const input = z.object(handle).strict().parse(payload);
|
|
125
|
+
value = service.operation(input.generation, input.id, input.scope) ?? null;
|
|
126
|
+
}
|
|
127
|
+
else if (endpoint === 'cancel-operation') {
|
|
128
|
+
const input = z.object(handle).strict().parse(payload);
|
|
129
|
+
service.cancelOperation(input.generation, input.id, input.scope);
|
|
130
|
+
value = { cancelled: true };
|
|
131
|
+
}
|
|
132
|
+
else if (endpoint === 'cancel-request') {
|
|
133
|
+
const input = z.object({ requestId: z.string().uuid() }).strict().parse(payload);
|
|
134
|
+
service.cancelRequest(input.requestId);
|
|
135
|
+
value = { cancelled: true };
|
|
136
|
+
}
|
|
137
|
+
else
|
|
138
|
+
throw new Error('unknown-endpoint');
|
|
139
|
+
return { ok: true, value };
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
return {
|
|
143
|
+
ok: false,
|
|
144
|
+
error: { code: 'internal', message: credentialError(error), details: {} },
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
// The profile owns this shared service; withdrawing its Web entry must not close it.
|
|
149
|
+
return unregister;
|
|
150
|
+
}
|