@chengxianglibra/dsh-data-analysis 0.1.2 → 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 +136 -99
- package/cordis.patch.yml +1 -1
- package/lib/bin/environment.js +8 -6
- package/lib/client/credentials/changes.js +74 -0
- package/lib/client/credentials/create-datasource.js +126 -22
- 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 +209 -81
- package/lib/client/credentials/model.js +323 -51
- package/lib/client/credentials/report-publishing.js +96 -0
- package/lib/client/credentials/styles.js +13 -9
- 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 +14 -1
- package/lib/client/workspace-shortcuts.js +26 -0
- package/lib/client.js +365 -242
- package/lib/compatibility.js +1 -1
- package/lib/datasource/authoring.js +43 -4
- package/lib/datasource/bridge-programs.js +23 -20
- package/lib/datasource/bridge.js +46 -3
- 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 +2 -1
- package/lib/datasource/python-options.js +21 -0
- package/lib/datasource/python.js +116 -25
- package/lib/datasource/rpc.js +75 -13
- package/lib/datasource/service.js +286 -11
- package/lib/datasource/test.js +2 -1
- 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/runtime.js +35 -34
- package/lib/environment/workspace.js +18 -1
- package/lib/lifecycle.js +33 -0
- package/lib/plugin-agents.js +154 -0
- package/lib/plugin.js +94 -172
- package/lib/presentation/asset.js +34 -0
- package/lib/presentation/assets/portable.js +14 -14
- package/lib/presentation/assets/static.js +344 -73
- package/lib/presentation/build/index.js +3 -2
- package/lib/presentation/commit.js +19 -12
- package/lib/presentation/contracts/asset-path.js +9 -0
- package/lib/presentation/contracts/catalog.js +62 -0
- package/lib/presentation/contracts/charts.js +0 -23
- package/lib/presentation/contracts/index.js +17 -5
- package/lib/presentation/contracts/types.js +1 -1
- package/lib/presentation/delivery.js +45 -30
- package/lib/presentation/export-html.js +214 -0
- package/lib/presentation/files.js +4 -5
- package/lib/presentation/projection/index.js +2 -1
- package/lib/presentation/projection/program.js +2 -9
- package/lib/presentation/projection/sql-code.js +13 -13
- package/lib/presentation/reports.js +114 -17
- package/lib/presentation/rpc.js +96 -58
- package/lib/presentation/tool.js +6 -10
- 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 +17 -0
- package/lib/semantic-browser/program.js +10 -0
- package/lib/semantic-browser/service.js +40 -6
- package/lib/semantic-reference/bridge.js +10 -1
- package/lib/semantic-reference/contracts.js +20 -6
- package/lib/semantic-reference/environment.js +56 -0
- package/lib/semantic-reference/labels.js +16 -0
- package/lib/semantic-reference/rpc.js +39 -18
- package/lib/semantic-reference/search.js +5 -4
- package/lib/semantic-reference/usage.js +12 -3
- package/lib/tool-lifecycle.js +22 -0
- package/lib/types/client/credentials/changes.d.ts +9 -0
- package/lib/types/client/credentials/create-datasource.d.ts +3 -1
- 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 -1
- package/lib/types/client/credentials/model.d.ts +31 -11
- package/lib/types/client/credentials/report-publishing.d.ts +5 -0
- package/lib/types/client/credentials/styles.d.ts +1 -1
- 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 +4 -8
- 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-tooltip.d.ts +2 -2
- package/lib/types/client/presentation/context-reference.d.ts +12 -0
- package/lib/types/client/presentation/copy-context.d.ts +2 -2
- package/lib/types/client/presentation/delivery-model.d.ts +27 -6
- package/lib/types/client/presentation/delivery.d.ts +8 -7
- package/lib/types/client/presentation/download.d.ts +2 -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/host-entry.d.ts +8 -2
- package/lib/types/client/presentation/icons.d.ts +1 -1
- package/lib/types/client/presentation/model.d.ts +14 -15
- package/lib/types/client/presentation/reader.d.ts +15 -2
- package/lib/types/client/presentation/reference-source.d.ts +9 -0
- package/lib/types/client/presentation/source-code-model.d.ts +2 -1
- package/lib/types/client/presentation/source-code.d.ts +0 -5
- package/lib/types/client/presentation/source-dialog.d.ts +1 -2
- package/lib/types/client/presentation/sources.d.ts +5 -5
- package/lib/types/client/presentation/styles.d.ts +1 -1
- package/lib/types/client/presentation/table.d.ts +5 -1
- 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 +1 -1
- package/lib/types/client/workspace-shortcuts.d.ts +15 -0
- package/lib/types/client.d.ts +6 -3
- package/lib/types/compatibility.d.ts +1 -1
- package/lib/types/datasource/authoring.d.ts +10 -0
- package/lib/types/datasource/bridge.d.ts +23 -2
- 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 +6 -1
- package/lib/types/datasource/python-options.d.ts +9 -0
- package/lib/types/datasource/python.d.ts +17 -2
- package/lib/types/datasource/rpc.d.ts +2 -1
- package/lib/types/datasource/service.d.ts +48 -5
- package/lib/types/datasource/test.d.ts +1 -1
- 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/types.d.ts +2 -1
- package/lib/types/environment/workspace.d.ts +1 -0
- package/lib/types/lifecycle.d.ts +8 -0
- package/lib/types/plugin-agents.d.ts +27 -0
- package/lib/types/plugin.d.ts +12 -28
- package/lib/types/presentation/asset.d.ts +6 -0
- package/lib/types/presentation/build/index.d.ts +7 -2
- package/lib/types/presentation/commit.d.ts +1 -1
- 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 +0 -2
- package/lib/types/presentation/contracts/types.d.ts +7 -4
- package/lib/types/presentation/delivery.d.ts +1 -1
- package/lib/types/presentation/export-html.d.ts +13 -0
- package/lib/types/presentation/reports.d.ts +6 -1
- package/lib/types/presentation/rpc.d.ts +3 -2
- package/lib/types/presentation/tool.d.ts +2 -2
- 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 +7 -0
- package/lib/types/semantic-browser/service.d.ts +7 -1
- package/lib/types/semantic-reference/bridge.d.ts +1 -0
- package/lib/types/semantic-reference/contracts.d.ts +5 -2
- package/lib/types/semantic-reference/environment.d.ts +22 -0
- package/lib/types/semantic-reference/labels.d.ts +16 -0
- package/lib/types/semantic-reference/rpc.d.ts +1 -1
- package/lib/types/semantic-reference/search.d.ts +1 -1
- 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 +83 -61
- 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 +10 -6
- package/skills/dsh-data-analysis-presentation/references/examples/artifact.draft.json +13 -4
- package/skills/dsh-data-analysis-presentation/references/examples/charts.draft.json +3 -2
- package/skills/dsh-data-analysis-presentation/references/examples/comparison.draft.json +3 -2
- package/skills/dsh-data-analysis-presentation/references/examples/computed-sources.draft.json +12 -5
- package/skills/dsh-data-analysis-presentation/references/examples/computed.draft.json +3 -2
- package/skills/dsh-data-analysis-presentation/references/examples/interaction.draft.json +3 -2
- package/skills/dsh-data-analysis-presentation/references/examples/source-only.draft.json +12 -4
- package/skills/dsh-data-analysis-presentation/references/examples.md +3 -1
- package/skills/dsh-data-analysis-presentation/references/export-html.md +7 -0
- package/skills/dsh-data-analysis-presentation/references/interaction.md +1 -1
- package/skills/dsh-data-analysis-presentation/references/narrative.md +1 -1
- package/skills/dsh-data-analysis-presentation/references/publishing.md +3 -0
- package/skills/dsh-data-analysis-presentation/references/schema.md +25 -1
- package/lib/types/client/presentation/install.d.ts +0 -15
package/lib/compatibility.js
CHANGED
|
@@ -56,7 +56,7 @@ const packageManifest = parsePackageManifest(createRequire(import.meta.url)('../
|
|
|
56
56
|
/** The package semver and the compatibility contract shipped in this exact installation. */
|
|
57
57
|
export const PLUGIN_VERSION = packageManifest.version;
|
|
58
58
|
export const DSH_DATA_ANALYSIS_COMPATIBILITY = packageManifest.dshDataAnalysisCompatibility;
|
|
59
|
-
/** Every DSH peer is required and must
|
|
59
|
+
/** Every DSH peer is required and must satisfy this declared compatibility range. */
|
|
60
60
|
export const DSH_PEER_RANGE = DSH_DATA_ANALYSIS_COMPATIBILITY.dsh.peerRange;
|
|
61
61
|
export const DSH_DISTRIBUTION = DSH_DATA_ANALYSIS_COMPATIBILITY.dsh.distribution;
|
|
62
62
|
export const DSH_PEER_DEPENDENCIES = Object.freeze(Object.fromEntries(Object.entries(packageManifest.peerDependencies).filter(([name]) => name.startsWith('@deepseek-ai/dsh-'))));
|
|
@@ -17,6 +17,7 @@ export const datasourceAuthoringSchema = z.object({
|
|
|
17
17
|
export const DATASOURCE_AUTHORING_PROGRAM = String.raw `
|
|
18
18
|
import contextlib
|
|
19
19
|
import dataclasses
|
|
20
|
+
import hashlib
|
|
20
21
|
import json
|
|
21
22
|
import sys
|
|
22
23
|
import typing
|
|
@@ -43,28 +44,66 @@ def spec_fields(cls):
|
|
|
43
44
|
result.append(item)
|
|
44
45
|
return result
|
|
45
46
|
|
|
47
|
+
def configuration(name, cls):
|
|
48
|
+
import marivo.semantic as ms
|
|
49
|
+
description = md.describe(name)
|
|
50
|
+
details = ms.load().datasources.get(name).details()
|
|
51
|
+
if details.backend_type != description.backend_type or dict(details.fields) != description.literal_fields or dict(details.env_refs) != description.env_refs:
|
|
52
|
+
raise ValueError("context-changed")
|
|
53
|
+
context = {"business_definition": details.context.business_definition,
|
|
54
|
+
"guardrails": list(details.context.guardrails)}
|
|
55
|
+
allowed = {field["name"] for field in spec_fields(cls)}
|
|
56
|
+
fields = {"name": name}
|
|
57
|
+
extra = {}
|
|
58
|
+
for key, value in description.literal_fields.items():
|
|
59
|
+
if key in allowed and key != "extra": fields[key] = value
|
|
60
|
+
else: extra[key] = value
|
|
61
|
+
if extra: fields["extra"] = extra
|
|
62
|
+
for key, value in description.env_refs.items():
|
|
63
|
+
if key.startswith("http_header:"):
|
|
64
|
+
fields.setdefault("http_headers_env", {})[key.removeprefix("http_header:")] = value
|
|
65
|
+
elif key + "_env" in allowed: fields[key + "_env"] = value
|
|
66
|
+
else: raise ValueError("datasource-definition-invalid")
|
|
67
|
+
version = hashlib.sha256(json.dumps([description.backend_type, fields, context], sort_keys=True).encode()).hexdigest()
|
|
68
|
+
return {"name": name, "backend": description.backend_type, "fields": fields, "version": version}, context
|
|
69
|
+
|
|
46
70
|
try:
|
|
47
71
|
payload = json.load(sys.stdin)
|
|
48
72
|
with contextlib.redirect_stdout(Discard()), contextlib.redirect_stderr(Discard()):
|
|
49
73
|
specs = {cls.backend_type: cls for cls in typing.get_args(md.DatasourceSpec)}
|
|
50
74
|
if payload["action"] == "schema":
|
|
51
75
|
result = {"backends": [{"name": key, "fields": spec_fields(cls)} for key, cls in specs.items()]}
|
|
76
|
+
elif payload["action"] == "read":
|
|
77
|
+
description = md.describe(payload["name"])
|
|
78
|
+
result, _ = configuration(payload["name"], specs[description.backend_type])
|
|
52
79
|
else:
|
|
53
80
|
cls = specs[payload["backend"]]
|
|
54
81
|
fields = payload["fields"]
|
|
55
82
|
allowed = {field["name"] for field in spec_fields(cls)}
|
|
83
|
+
context = None
|
|
84
|
+
if payload["action"] == "update":
|
|
85
|
+
current = md.describe(payload["name"])
|
|
86
|
+
if fields.get("name") != payload["name"] or current.backend_type != payload["backend"]:
|
|
87
|
+
raise ValueError("datasource-identity-fixed")
|
|
88
|
+
original, context = configuration(payload["name"], cls)
|
|
89
|
+
if original["version"] != payload["version"]:
|
|
90
|
+
raise ValueError("datasource-config-changed")
|
|
56
91
|
if set(fields) - allowed:
|
|
57
92
|
raise ValueError("unknown-field")
|
|
58
93
|
spec = cls(**fields)
|
|
59
|
-
if any(item.name == spec.name for item in md.list()):
|
|
94
|
+
if payload["action"] == "create" and any(item.name == spec.name for item in md.list()):
|
|
60
95
|
result = {"error": "datasource-already-exists"}
|
|
61
96
|
else:
|
|
62
|
-
# Validate references before writing; the Host applies its own reserved-name check too.
|
|
63
97
|
if sorted(spec.env_refs.values()) != sorted(payload["refs"]):
|
|
64
98
|
raise ValueError("credential-reference-mismatch")
|
|
99
|
+
if context is not None:
|
|
100
|
+
import marivo.semantic as ms
|
|
101
|
+
spec = cls(**fields, ai_context=ms.ai_context(**context))
|
|
65
102
|
md.register(spec)
|
|
66
103
|
result = {"name": spec.name}
|
|
67
|
-
|
|
68
|
-
|
|
104
|
+
|
|
105
|
+
except Exception as exc:
|
|
106
|
+
code = str(exc)
|
|
107
|
+
result = {"error": code if code in ("datasource-config-changed", "datasource-identity-fixed", "context-changed") else "datasource-definition-invalid"}
|
|
69
108
|
print(json.dumps(result))
|
|
70
109
|
`.trim();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RESOLVER_PROGRAM } from "./resolver-program.js";
|
|
2
|
-
|
|
2
|
+
const DATASOURCE_DESCRIPTION_PROGRAM = String.raw `
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
5
|
import sys
|
|
@@ -16,29 +16,32 @@ def project_description(description):
|
|
|
16
16
|
"fields": description.env_refs,
|
|
17
17
|
"definition": hashlib.sha256(json.dumps(payload, sort_keys=True, default=str).encode()).hexdigest()}
|
|
18
18
|
|
|
19
|
-
print(json.dumps(project_description(md.describe(sys.argv[1])), sort_keys=True))
|
|
20
19
|
`.trim();
|
|
21
|
-
export const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
import
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
20
|
+
export const MARIVO_DATASOURCE_DESCRIBE_PROGRAM = `${DATASOURCE_DESCRIPTION_PROGRAM}
|
|
21
|
+
print(json.dumps(project_description(md.describe(sys.argv[1])), sort_keys=True))`;
|
|
22
|
+
export const MARIVO_DATASOURCE_REMOVE_PROGRAM = `${DATASOURCE_DESCRIPTION_PROGRAM}
|
|
23
|
+
import contextlib
|
|
24
|
+
class Discard:
|
|
25
|
+
def write(self, value): return len(value)
|
|
26
|
+
def flush(self): pass
|
|
27
|
+
try:
|
|
28
|
+
payload = json.load(sys.stdin)
|
|
29
|
+
with contextlib.redirect_stdout(Discard()), contextlib.redirect_stderr(Discard()):
|
|
30
|
+
current = project_description(md.describe(payload["name"]))
|
|
31
|
+
if current["definition"] != payload["definition"]:
|
|
32
|
+
result = {"error": "context-changed"}
|
|
33
|
+
elif md.remove(payload["name"]):
|
|
34
|
+
result = {"name": payload["name"]}
|
|
35
|
+
else:
|
|
36
|
+
result = {"error": "datasource-not-removable"}
|
|
37
|
+
except Exception:
|
|
38
|
+
result = {"error": "datasource-remove-failed"}
|
|
39
|
+
print(json.dumps(result))`;
|
|
40
|
+
export const MARIVO_DATASOURCE_INVENTORY_PROGRAM = `${DATASOURCE_DESCRIPTION_PROGRAM}
|
|
38
41
|
print(json.dumps({
|
|
39
42
|
"datasources": [project_description(md.describe(item.name)) for item in md.list()],
|
|
40
43
|
}, sort_keys=True))
|
|
41
|
-
|
|
44
|
+
`;
|
|
42
45
|
export const MARIVO_DATASOURCE_TEST_PROGRAM = `${RESOLVER_PROGRAM}\n${String.raw `
|
|
43
46
|
import contextlib
|
|
44
47
|
import io
|
package/lib/datasource/bridge.js
CHANGED
|
@@ -2,7 +2,7 @@ import { isCredentialRefName } from '@deepseek-ai/dsh-credentials';
|
|
|
2
2
|
import { MarivoEnvironmentError } from "../environment/errors.js";
|
|
3
3
|
import { resolveMarivoBridgeSource } from "../environment/source.js";
|
|
4
4
|
import { DATASOURCE_AUTHORING_PROGRAM, datasourceAuthoringSchema, } from "./authoring.js";
|
|
5
|
-
import { MARIVO_DATASOURCE_DESCRIBE_PROGRAM, MARIVO_DATASOURCE_INVENTORY_PROGRAM, MARIVO_DATASOURCE_TEST_PROGRAM, } from "./bridge-programs.js";
|
|
5
|
+
import { MARIVO_DATASOURCE_DESCRIBE_PROGRAM, MARIVO_DATASOURCE_INVENTORY_PROGRAM, MARIVO_DATASOURCE_REMOVE_PROGRAM, MARIVO_DATASOURCE_TEST_PROGRAM, } from "./bridge-programs.js";
|
|
6
6
|
import { marivoCredentialStorageRef } from "./shell-env.js";
|
|
7
7
|
const DATASOURCE_LIMITS = Object.freeze({
|
|
8
8
|
timeoutMs: 30_000,
|
|
@@ -210,7 +210,30 @@ export class MarivoDatasourceBridge {
|
|
|
210
210
|
fingerprint: this.binding.fingerprint,
|
|
211
211
|
});
|
|
212
212
|
}
|
|
213
|
+
async configuration(name, signal) {
|
|
214
|
+
const result = await this.#runner.runChecked({
|
|
215
|
+
program: DATASOURCE_AUTHORING_PROGRAM,
|
|
216
|
+
stdin: JSON.stringify({ action: 'read', name }),
|
|
217
|
+
limits: DATASOURCE_LIMITS,
|
|
218
|
+
signal,
|
|
219
|
+
});
|
|
220
|
+
this.#assertSuccess(result, 'inventory');
|
|
221
|
+
const value = parseJsonObject(result.stdout, 'inventory');
|
|
222
|
+
if (value.name !== name ||
|
|
223
|
+
typeof value.backend !== 'string' ||
|
|
224
|
+
typeof value.version !== 'string' ||
|
|
225
|
+
!object(value.fields) ||
|
|
226
|
+
!jsonValue(value.fields))
|
|
227
|
+
throw new Error('Datasource configuration cannot be read completely');
|
|
228
|
+
return value;
|
|
229
|
+
}
|
|
230
|
+
async update(input, signal) {
|
|
231
|
+
return this.#write(input, 'update', signal);
|
|
232
|
+
}
|
|
213
233
|
async create(input, signal) {
|
|
234
|
+
return this.#write(input, 'create', signal);
|
|
235
|
+
}
|
|
236
|
+
async #write(input, action, signal) {
|
|
214
237
|
const refs = Object.entries(input.fields)
|
|
215
238
|
.filter(([key]) => key.endsWith('_env'))
|
|
216
239
|
.flatMap(([, value]) => typeof value === 'string'
|
|
@@ -233,14 +256,17 @@ export class MarivoDatasourceBridge {
|
|
|
233
256
|
}
|
|
234
257
|
const result = await this.#runner.runChecked({
|
|
235
258
|
program: DATASOURCE_AUTHORING_PROGRAM,
|
|
236
|
-
stdin: JSON.stringify({ action
|
|
259
|
+
stdin: JSON.stringify({ action, ...input, refs }),
|
|
237
260
|
limits: DATASOURCE_LIMITS,
|
|
238
261
|
signal,
|
|
239
262
|
});
|
|
240
263
|
this.#assertSuccess(result, 'inventory');
|
|
241
264
|
const value = parseJsonObject(result.stdout, 'inventory');
|
|
242
265
|
if (value.error === 'datasource-already-exists' ||
|
|
243
|
-
value.error === 'datasource-definition-invalid'
|
|
266
|
+
value.error === 'datasource-definition-invalid' ||
|
|
267
|
+
value.error === 'datasource-config-changed' ||
|
|
268
|
+
value.error === 'datasource-identity-fixed' ||
|
|
269
|
+
value.error === 'context-changed')
|
|
244
270
|
return { error: value.error };
|
|
245
271
|
if (typeof value.name !== 'string')
|
|
246
272
|
throw new Error('Invalid datasource creation result');
|
|
@@ -256,6 +282,23 @@ export class MarivoDatasourceBridge {
|
|
|
256
282
|
this.#assertSuccess(result, 'describe');
|
|
257
283
|
return parseDescription(parseJsonObject(result.stdout, 'describe'), 'describe');
|
|
258
284
|
}
|
|
285
|
+
async remove(description, signal) {
|
|
286
|
+
const result = await this.#runner.runChecked({
|
|
287
|
+
program: MARIVO_DATASOURCE_REMOVE_PROGRAM,
|
|
288
|
+
stdin: JSON.stringify({ name: description.name, definition: description.definition }),
|
|
289
|
+
limits: DATASOURCE_LIMITS,
|
|
290
|
+
signal,
|
|
291
|
+
});
|
|
292
|
+
this.#assertSuccess(result, 'inventory');
|
|
293
|
+
const value = parseJsonObject(result.stdout, 'inventory');
|
|
294
|
+
if (value.error === 'context-changed' ||
|
|
295
|
+
value.error === 'datasource-not-removable' ||
|
|
296
|
+
value.error === 'datasource-remove-failed')
|
|
297
|
+
return { error: value.error };
|
|
298
|
+
if (value.name !== description.name)
|
|
299
|
+
throw new Error('Invalid datasource removal result');
|
|
300
|
+
return { name: description.name };
|
|
301
|
+
}
|
|
259
302
|
async inventory(signal) {
|
|
260
303
|
const result = await this.#runner.runChecked({
|
|
261
304
|
program: MARIVO_DATASOURCE_INVENTORY_PROGRAM,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const CREDENTIAL_CHANGES_SERVICE = 'dshDataAnalysisCredentials';
|
|
3
|
+
const owner = '@chengxianglibra/dsh-data-analysis';
|
|
4
|
+
const sessionId = z.string().min(1).max(256);
|
|
5
|
+
export const credentialRevisionSchema = z
|
|
6
|
+
.object({ generation: z.string().uuid(), cursor: z.string().min(1).max(256) })
|
|
7
|
+
.strict();
|
|
8
|
+
/** Plugin-owned strict boundary shared by both faces; no Host registry is duplicated. */
|
|
9
|
+
const descriptor = {
|
|
10
|
+
id: `${owner}#${CREDENTIAL_CHANGES_SERVICE}/changes`,
|
|
11
|
+
service: CREDENTIAL_CHANGES_SERVICE,
|
|
12
|
+
namespace: CREDENTIAL_CHANGES_SERVICE,
|
|
13
|
+
method: 'changes',
|
|
14
|
+
mode: 'stream',
|
|
15
|
+
invocation: { kind: 'direct' },
|
|
16
|
+
parameters: [
|
|
17
|
+
{
|
|
18
|
+
name: 'sessionId',
|
|
19
|
+
wire: 'sessionId',
|
|
20
|
+
source: 'json',
|
|
21
|
+
codec: { mode: 'strict', typeSymbol: `${owner}#CredentialSessionId`, schema: sessionId },
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
cancellation: { parameter: 'signal' },
|
|
25
|
+
result: {
|
|
26
|
+
mode: 'strict',
|
|
27
|
+
typeSymbol: `${owner}#CredentialRevision`,
|
|
28
|
+
schema: credentialRevisionSchema,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
export const credentialChangesHost = {
|
|
32
|
+
package: owner,
|
|
33
|
+
face: 'host',
|
|
34
|
+
schemas: [],
|
|
35
|
+
model: { services: [], events: [], objects: [] },
|
|
36
|
+
invocations: [descriptor],
|
|
37
|
+
};
|
|
38
|
+
export const credentialChangesRemote = {
|
|
39
|
+
package: owner,
|
|
40
|
+
descriptors: [descriptor],
|
|
41
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
2
|
+
import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
3
|
+
import { PendingTasks } from "../lifecycle.js";
|
|
4
|
+
import { CREDENTIAL_CHANGES_SERVICE } from "./changes-contract.js";
|
|
5
|
+
import { CredentialServiceError } from "./service.js";
|
|
6
|
+
/** Reuses the Host's logical stream carrier; credentials and waiting calls keep their owner. */
|
|
7
|
+
export class CredentialChangesService extends TypertRemoteService {
|
|
8
|
+
credentials;
|
|
9
|
+
lifetime = new AbortController();
|
|
10
|
+
tasks = new PendingTasks();
|
|
11
|
+
closing;
|
|
12
|
+
constructor(ctx, credentials) {
|
|
13
|
+
super(ctx, CREDENTIAL_CHANGES_SERVICE);
|
|
14
|
+
this.credentials = credentials;
|
|
15
|
+
}
|
|
16
|
+
async *changes(sessionId, caller) {
|
|
17
|
+
const signal = AbortSignal.any([caller, this.lifetime.signal]);
|
|
18
|
+
let cursor;
|
|
19
|
+
while (!signal.aborted) {
|
|
20
|
+
const snapshot = await this.tasks.track(this.snapshot(sessionId, cursor, signal));
|
|
21
|
+
signal.throwIfAborted();
|
|
22
|
+
if (snapshot.cursor !== cursor) {
|
|
23
|
+
cursor = snapshot.cursor;
|
|
24
|
+
yield { generation: snapshot.generation, cursor };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async snapshot(sessionId, cursor, signal) {
|
|
29
|
+
let attempt = 0;
|
|
30
|
+
for (;;) {
|
|
31
|
+
signal.throwIfAborted();
|
|
32
|
+
try {
|
|
33
|
+
return await this.credentials.waitWatch(sessionId, cursor, signal);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
signal.throwIfAborted();
|
|
37
|
+
if (!(error instanceof CredentialServiceError) ||
|
|
38
|
+
error.code !== 'credential-state-unavailable')
|
|
39
|
+
throw new Error('credential-notification-unavailable');
|
|
40
|
+
await delay([1000, 2000, 5000][Math.min(attempt++, 2)], undefined, { signal });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
close() {
|
|
45
|
+
this.lifetime.abort();
|
|
46
|
+
this.closing ??= this.tasks.drain();
|
|
47
|
+
return this.closing;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
2
|
+
import { registerMarivoTool } from "../tool-lifecycle.js";
|
|
3
|
+
import { resolveMarivoDatasourceBridge } from "./bridge.js";
|
|
4
|
+
export const MARIVO_DATASOURCE_CONFIGURE_TOOL_NAME = 'marivo_datasource_configure';
|
|
5
|
+
export function createMarivoDatasourceConfigureTool(source, service) {
|
|
6
|
+
return defineTool({
|
|
7
|
+
name: MARIVO_DATASOURCE_CONFIGURE_TOOL_NAME,
|
|
8
|
+
description: 'Ask the user to create/select or edit a datasource in the owning Web session right tab. Waits until the user saves and tests successfully, cancels, or hands off a failure. Never pass credentials or configuration values. After success, rediscover the datasource and verify the requested table before analysis.',
|
|
9
|
+
parameters: {
|
|
10
|
+
mode: {
|
|
11
|
+
type: 'string',
|
|
12
|
+
required: true,
|
|
13
|
+
description: 'create (also permits choosing an existing datasource) or edit (fixed name and engine).',
|
|
14
|
+
},
|
|
15
|
+
name: { type: 'string', description: 'Existing datasource name, required only for edit.' },
|
|
16
|
+
reason: {
|
|
17
|
+
type: 'string',
|
|
18
|
+
required: true,
|
|
19
|
+
description: 'Brief reason for configuration, at most 1000 characters. No secrets.',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
output: {
|
|
23
|
+
schema: { type: 'json' },
|
|
24
|
+
render: (_args, value) => [{ type: 'text', text: JSON.stringify(value) }],
|
|
25
|
+
},
|
|
26
|
+
async execute(args, exec) {
|
|
27
|
+
if (args.mode !== 'create' && args.mode !== 'edit')
|
|
28
|
+
throw new Error('Invalid configuration mode');
|
|
29
|
+
return JSON.parse(JSON.stringify(await service.track(service.configure(exec, () => resolveMarivoDatasourceBridge(source), {
|
|
30
|
+
mode: args.mode,
|
|
31
|
+
name: args.name,
|
|
32
|
+
reason: args.reason,
|
|
33
|
+
}))));
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
export function registerMarivoDatasourceConfigureTool(ctx, source, service) {
|
|
38
|
+
return registerMarivoTool(ctx, createMarivoDatasourceConfigureTool(source, service));
|
|
39
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { CredentialServiceError } from "./service.js";
|
|
3
|
+
/** Validate against this Workspace's live Runtime; never include config values in errors. */
|
|
4
|
+
export function withDatasourceDefaults(schema, configured) {
|
|
5
|
+
if (configured === undefined)
|
|
6
|
+
return schema;
|
|
7
|
+
const parsed = z.record(z.string(), z.record(z.string(), z.json())).safeParse(configured);
|
|
8
|
+
if (!parsed.success)
|
|
9
|
+
throw new CredentialServiceError('datasource-defaults-invalid');
|
|
10
|
+
for (const [backend, values] of Object.entries(parsed.data)) {
|
|
11
|
+
const definition = schema.backends.find((item) => item.name === backend);
|
|
12
|
+
if (!definition)
|
|
13
|
+
throw new CredentialServiceError('datasource-defaults-backend-invalid');
|
|
14
|
+
for (const [name, value] of Object.entries(values)) {
|
|
15
|
+
const field = definition.fields.find((item) => item.name === name);
|
|
16
|
+
if (name.endsWith('_env'))
|
|
17
|
+
throw new CredentialServiceError('datasource-defaults-credential-forbidden');
|
|
18
|
+
if (!field)
|
|
19
|
+
throw new CredentialServiceError('datasource-defaults-field-invalid');
|
|
20
|
+
if (field.type !== 'json' && typeof value !== field.type)
|
|
21
|
+
throw new CredentialServiceError('datasource-defaults-type-invalid');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return { ...schema, creationDefaults: parsed.data };
|
|
25
|
+
}
|
package/lib/datasource/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { MarivoDatasourceBridge, resolveMarivoDatasourceBridge, resolveMarivoDatasourceInventoryBridge, } from "./bridge.js";
|
|
2
|
-
export {
|
|
2
|
+
export { createMarivoDatasourceConfigureTool, MARIVO_DATASOURCE_CONFIGURE_TOOL_NAME, registerMarivoDatasourceConfigureTool, } from "./configure.js";
|
|
3
|
+
export { MarivoPythonExecutionError, registerMarivoPythonTool } from "./python.js";
|
|
3
4
|
export { MarivoCredentialService } from "./service.js";
|
|
4
5
|
export { assertMarivoCredentialReferences, MARIVO_CREDENTIAL_STORAGE_PREFIX, marivoCredentialStorageRef, } from "./shell-env.js";
|
|
5
6
|
export { createMarivoDatasourceTestTool, MARIVO_DATASOURCE_TEST_TOOL_NAME, registerMarivoDatasourceTestTool, } from "./test.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const DEFAULT_PYTHON_TIMEOUT_MS = 120_000;
|
|
2
|
+
export const DEFAULT_PYTHON_MAX_TIMEOUT_MS = 600_000;
|
|
3
|
+
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
4
|
+
/** Do not echo rejected inputs: tool arguments and configuration are not trusted diagnostics. */
|
|
5
|
+
export function pythonTimeout(value, name) {
|
|
6
|
+
if (typeof value !== 'number' ||
|
|
7
|
+
!Number.isInteger(value) ||
|
|
8
|
+
value <= 0 ||
|
|
9
|
+
value > MAX_TIMER_DELAY_MS)
|
|
10
|
+
throw new Error(`${name} must be a positive integer no greater than ${MAX_TIMER_DELAY_MS}`);
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
export function resolvePythonOptions(options) {
|
|
14
|
+
const pythonTimeoutMs = pythonTimeout(options.pythonTimeoutMs === undefined ? DEFAULT_PYTHON_TIMEOUT_MS : options.pythonTimeoutMs, 'pythonTimeoutMs');
|
|
15
|
+
const pythonMaxTimeoutMs = pythonTimeout(options.pythonMaxTimeoutMs === undefined
|
|
16
|
+
? DEFAULT_PYTHON_MAX_TIMEOUT_MS
|
|
17
|
+
: options.pythonMaxTimeoutMs, 'pythonMaxTimeoutMs');
|
|
18
|
+
if (pythonTimeoutMs > pythonMaxTimeoutMs)
|
|
19
|
+
throw new Error('pythonTimeoutMs must not exceed pythonMaxTimeoutMs');
|
|
20
|
+
return { pythonTimeoutMs, pythonMaxTimeoutMs };
|
|
21
|
+
}
|
package/lib/datasource/python.js
CHANGED
|
@@ -1,29 +1,57 @@
|
|
|
1
|
+
import { performance } from 'node:perf_hooks';
|
|
1
2
|
import process from 'node:process';
|
|
2
3
|
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
3
4
|
import { savePythonExecution } from "../python-execution.js";
|
|
5
|
+
import { registerMarivoTool } from "../tool-lifecycle.js";
|
|
4
6
|
import { resolveMarivoDatasourceBridge } from "./bridge.js";
|
|
7
|
+
import { pythonTimeout, resolvePythonOptions } from "./python-options.js";
|
|
5
8
|
import { PYTHON_LAUNCHER, PYTHON_WORKER } from "./resolver-program.js";
|
|
9
|
+
import { CredentialServiceError, } from "./service.js";
|
|
6
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
|
+
]);
|
|
7
30
|
function quote(value) {
|
|
8
31
|
return process.platform === 'win32'
|
|
9
32
|
? `'${value.replaceAll("'", "''")}'`
|
|
10
33
|
: `'${value.replaceAll("'", "'\\''")}'`;
|
|
11
34
|
}
|
|
12
|
-
export function registerMarivoPythonTool(ctx, source, service) {
|
|
13
|
-
|
|
35
|
+
export function registerMarivoPythonTool(ctx, source, service, options = {}) {
|
|
36
|
+
const limits = resolvePythonOptions(options);
|
|
37
|
+
return registerMarivoTool(ctx, defineTool({
|
|
14
38
|
name: 'marivo_python',
|
|
15
|
-
description: 'Execute foreground Python in the bound
|
|
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.',
|
|
16
40
|
parameters: {
|
|
17
41
|
code: {
|
|
18
42
|
type: 'string',
|
|
19
43
|
required: true,
|
|
20
|
-
description: 'Python code, never credential values.
|
|
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.',
|
|
21
45
|
},
|
|
22
46
|
datasources: {
|
|
23
47
|
type: 'array',
|
|
24
48
|
required: true,
|
|
25
49
|
items: { type: 'string' },
|
|
26
|
-
description: 'All exact datasource names this execution may access through data or metadata connections, including inspection and datasources without passwords
|
|
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.`,
|
|
27
55
|
},
|
|
28
56
|
},
|
|
29
57
|
output: {
|
|
@@ -31,28 +59,60 @@ export function registerMarivoPythonTool(ctx, source, service) {
|
|
|
31
59
|
render: (_args, value) => [{ type: 'text', text: JSON.stringify(value) }],
|
|
32
60
|
},
|
|
33
61
|
async execute(args, exec) {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
+
};
|
|
49
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;
|
|
50
110
|
const { binding } = prepared.bridge;
|
|
51
111
|
const policy = policyService?.resolve(exec.agent ? { session: exec.agent.session } : {});
|
|
52
112
|
const spec = shell.resolve({
|
|
53
113
|
command: `${process.platform === 'win32' ? '& ' : ''}${quote(binding.pythonExecutable)} -c ${quote(PYTHON_LAUNCHER)}`,
|
|
54
114
|
workdir: binding.projectRoot,
|
|
55
|
-
timeoutMs:
|
|
115
|
+
timeoutMs: Math.min(requestedTimeoutMs, limits.pythonMaxTimeoutMs),
|
|
56
116
|
signal: prepared.signal,
|
|
57
117
|
stdin: JSON.stringify({
|
|
58
118
|
identity: binding,
|
|
@@ -66,19 +126,25 @@ export function registerMarivoPythonTool(ctx, source, service) {
|
|
|
66
126
|
dshEnv: shellEnv.collect(exec),
|
|
67
127
|
...(policy ? { sandboxPolicy: policy } : {}),
|
|
68
128
|
});
|
|
129
|
+
effectiveTimeoutMs = spec.timeoutMs;
|
|
69
130
|
// Keep this guard adjacent to the only launch, after synchronous Host hooks.
|
|
70
131
|
prepared.assertCurrent();
|
|
71
132
|
const startedAt = new Date().toISOString();
|
|
133
|
+
phase = 'executing';
|
|
134
|
+
shellStartedAt = performance.now();
|
|
72
135
|
const result = await service.track(shell.run(spec));
|
|
136
|
+
executionElapsedMs = performance.now() - shellStartedAt;
|
|
73
137
|
const finishedAt = new Date().toISOString();
|
|
138
|
+
const credentialValues = prepared.values;
|
|
74
139
|
// Defense at the result seam as well as before Harness collection/spill.
|
|
75
|
-
const redact = (text) => Object.values(
|
|
140
|
+
const redact = (text) => Object.values(credentialValues)
|
|
76
141
|
.filter(Boolean)
|
|
77
142
|
.sort((a, b) => b.length - a.length)
|
|
78
143
|
.reduce((out, value) => out.split(value).join('[REDACTED]'), text);
|
|
79
144
|
let codeRef;
|
|
80
145
|
let codeCaptureError;
|
|
81
146
|
if (result.exitCode === 0 && !result.timedOut && !result.aborted) {
|
|
147
|
+
phase = 'capturing-code';
|
|
82
148
|
try {
|
|
83
149
|
prepared.assertCurrent();
|
|
84
150
|
if (Object.values(prepared.values).some((value) => value && code.includes(value)))
|
|
@@ -92,6 +158,15 @@ export function registerMarivoPythonTool(ctx, source, service) {
|
|
|
92
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.';
|
|
93
159
|
}
|
|
94
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';
|
|
95
170
|
return JSON.parse(JSON.stringify({
|
|
96
171
|
exitCode: result.exitCode,
|
|
97
172
|
timedOut: result.timedOut,
|
|
@@ -102,13 +177,29 @@ export function registerMarivoPythonTool(ctx, source, service) {
|
|
|
102
177
|
sandbox: result.sandbox ?? null,
|
|
103
178
|
...(codeRef ? { codeRef } : {}),
|
|
104
179
|
...(codeCaptureError ? { codeCaptureError } : {}),
|
|
180
|
+
execution: summary(reason, codeCaptureError ?? (reason === 'succeeded' ? null : CHECK_EFFECTS)),
|
|
105
181
|
}));
|
|
106
182
|
}
|
|
107
|
-
catch {
|
|
108
|
-
|
|
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));
|
|
109
200
|
}
|
|
110
201
|
finally {
|
|
111
|
-
prepared
|
|
202
|
+
prepared?.release();
|
|
112
203
|
}
|
|
113
204
|
},
|
|
114
205
|
}));
|