@chengxianglibra/dsh-data-analysis 0.1.1
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/LICENSE +21 -0
- package/README.md +72 -0
- package/cordis.patch.yml +11 -0
- package/lib/bin/environment.js +91 -0
- package/lib/bridges.js +11 -0
- package/lib/client.js +744 -0
- package/lib/compatibility.js +66 -0
- package/lib/datasource/access.js +60 -0
- package/lib/datasource/bridge-programs.js +80 -0
- package/lib/datasource/bridge.js +204 -0
- package/lib/datasource/credentials.js +24 -0
- package/lib/datasource/index.js +4 -0
- package/lib/datasource/shell-env.js +386 -0
- package/lib/datasource/test.js +65 -0
- package/lib/disclosure/activation.js +369 -0
- package/lib/disclosure/bridge-program.js +11 -0
- package/lib/disclosure/bridge.js +65 -0
- package/lib/disclosure/help.js +227 -0
- package/lib/disclosure/index.js +3 -0
- package/lib/environment/binding.js +273 -0
- package/lib/environment/doctor.js +123 -0
- package/lib/environment/errors.js +11 -0
- package/lib/environment/index.js +7 -0
- package/lib/environment/runtime.js +558 -0
- package/lib/environment/source.js +6 -0
- package/lib/environment/subprocess.js +178 -0
- package/lib/environment/summary.js +19 -0
- package/lib/environment/workspace.js +49 -0
- package/lib/evidence/bridge-program.js +153 -0
- package/lib/evidence/bridge.js +166 -0
- package/lib/evidence/index.js +2 -0
- package/lib/evidence/sources.js +263 -0
- package/lib/index.js +7 -0
- package/lib/plugin.js +179 -0
- package/lib/types/bridges.d.ts +11 -0
- package/lib/types/client.d.ts +125 -0
- package/lib/types/compatibility.d.ts +29 -0
- package/lib/types/datasource/access.d.ts +27 -0
- package/lib/types/datasource/bridge.d.ts +53 -0
- package/lib/types/datasource/index.d.ts +8 -0
- package/lib/types/datasource/shell-env.d.ts +38 -0
- package/lib/types/datasource/test.d.ts +32 -0
- package/lib/types/disclosure/activation.d.ts +73 -0
- package/lib/types/disclosure/bridge.d.ts +28 -0
- package/lib/types/disclosure/help.d.ts +64 -0
- package/lib/types/disclosure/index.d.ts +5 -0
- package/lib/types/environment/binding.d.ts +26 -0
- package/lib/types/environment/doctor.d.ts +8 -0
- package/lib/types/environment/errors.d.ts +7 -0
- package/lib/types/environment/index.d.ts +9 -0
- package/lib/types/environment/runtime.d.ts +17 -0
- package/lib/types/environment/source.d.ts +7 -0
- package/lib/types/environment/subprocess.d.ts +19 -0
- package/lib/types/environment/types.d.ts +96 -0
- package/lib/types/environment/workspace.d.ts +10 -0
- package/lib/types/evidence/bridge.d.ts +40 -0
- package/lib/types/evidence/index.d.ts +4 -0
- package/lib/types/evidence/sources.d.ts +57 -0
- package/lib/types/index.d.ts +9 -0
- package/lib/types/plugin.d.ts +43 -0
- package/package.json +149 -0
- 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 +57 -0
- package/skills/dsh-data-analysis-report/assets/marivo-artifact.js +113 -0
- package/skills/dsh-data-analysis-report/assets/marivo-session-dag.js +1201 -0
- package/skills/dsh-data-analysis-report/assets/report-data.js +449 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { credentialRef, isCredentialRefName, } from '@deepseek-ai/dsh-credentials';
|
|
3
|
+
import { MarivoEnvironmentError } from "../environment/index.js";
|
|
4
|
+
import { resolveMarivoDatasourceBridge, } from "./bridge.js";
|
|
5
|
+
export const MARIVO_CREDENTIAL_STORAGE_PREFIX = 'DSH_DATA_ANALYSIS_CREDENTIAL_';
|
|
6
|
+
export const MARIVO_CREDENTIAL_LEASE_PREFIX = '# dsh-marivo-credential-lease:';
|
|
7
|
+
export const DEFAULT_MARIVO_CREDENTIAL_LEASE_TTL_MS = 30 * 60_000;
|
|
8
|
+
export const DEFAULT_MARIVO_CREDENTIAL_LEASE_MAX_USES = 64;
|
|
9
|
+
const TOKEN_PATTERN = /^[A-Za-z0-9_-]{43}$/;
|
|
10
|
+
const SHELL_TOOL_NAMES = new Set(['bash', 'pwsh']);
|
|
11
|
+
const HOST_SHELL_ENVIRONMENT_NAMES = new Set([
|
|
12
|
+
'DSH_HOME',
|
|
13
|
+
'DSH_SESSION_ID',
|
|
14
|
+
'DSH_SESSION_JSONL',
|
|
15
|
+
'DSH_SHELL',
|
|
16
|
+
]);
|
|
17
|
+
function shellEnvironmentRegistry(ctx) {
|
|
18
|
+
return ctx.shellEnv;
|
|
19
|
+
}
|
|
20
|
+
/** Publish the exact shared interpreter as a non-secret, per-execution DSH fact. */
|
|
21
|
+
export function registerMarivoRuntimeShellEnvironment(ctx, pythonExecutable) {
|
|
22
|
+
return shellEnvironmentRegistry(ctx).register({
|
|
23
|
+
name: 'dsh-data-analysis:runtime',
|
|
24
|
+
variables: {
|
|
25
|
+
DSH_DATA_ANALYSIS_PYTHON: {
|
|
26
|
+
description: 'Exact Python interpreter admitted by the shared Marivo Runtime.',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
resolve: () => ({ DSH_DATA_ANALYSIS_PYTHON: pythonExecutable }),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function invalidReference(ref) {
|
|
33
|
+
const valid = isCredentialRefName(ref);
|
|
34
|
+
return new MarivoEnvironmentError('datasource-credential-ref-invalid', valid
|
|
35
|
+
? `Marivo datasource credential reference uses a reserved runtime namespace or Host name: ${ref}`
|
|
36
|
+
: `Marivo datasource credential reference must be a POSIX environment name: ${ref}`, {
|
|
37
|
+
ref,
|
|
38
|
+
expected: valid
|
|
39
|
+
? 'a POSIX environment name outside MARIVO_*, DSH_DATA_ANALYSIS_*, and Host-owned DSH shell facts'
|
|
40
|
+
: '[A-Za-z_][A-Za-z0-9_]*',
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function isReservedDatasourceCredentialReference(ref) {
|
|
44
|
+
const upper = ref.toUpperCase();
|
|
45
|
+
return (upper.startsWith('MARIVO_') ||
|
|
46
|
+
upper.startsWith('DSH_DATA_ANALYSIS_') ||
|
|
47
|
+
HOST_SHELL_ENVIRONMENT_NAMES.has(upper));
|
|
48
|
+
}
|
|
49
|
+
/** Map one user-authored datasource environment reference to plugin-owned DSH storage. */
|
|
50
|
+
export function marivoCredentialStorageRef(ref) {
|
|
51
|
+
if (!isCredentialRefName(ref) || isReservedDatasourceCredentialReference(ref)) {
|
|
52
|
+
throw invalidReference(ref);
|
|
53
|
+
}
|
|
54
|
+
const encoded = [...ref]
|
|
55
|
+
.map((character) => character.charCodeAt(0).toString(16).padStart(2, '0').toUpperCase())
|
|
56
|
+
.join('');
|
|
57
|
+
return `${MARIVO_CREDENTIAL_STORAGE_PREFIX}${encoded}`;
|
|
58
|
+
}
|
|
59
|
+
function credentialBindings(refs) {
|
|
60
|
+
return Object.freeze([...new Set(refs)].map((environmentRef) => ({
|
|
61
|
+
environmentRef,
|
|
62
|
+
storageRef: marivoCredentialStorageRef(environmentRef),
|
|
63
|
+
})));
|
|
64
|
+
}
|
|
65
|
+
function bashPrelude(token, bindings) {
|
|
66
|
+
return [
|
|
67
|
+
`${MARIVO_CREDENTIAL_LEASE_PREFIX}${token}`,
|
|
68
|
+
...bindings.flatMap(({ environmentRef, storageRef }) => [
|
|
69
|
+
`export ${environmentRef}="\${${storageRef}}"`,
|
|
70
|
+
`unset ${storageRef}`,
|
|
71
|
+
]),
|
|
72
|
+
'export MARIVO_PERSIST_CREDENTIALS=0',
|
|
73
|
+
].join('\n');
|
|
74
|
+
}
|
|
75
|
+
function pwshPrelude(token, bindings) {
|
|
76
|
+
return [
|
|
77
|
+
`${MARIVO_CREDENTIAL_LEASE_PREFIX}${token}`,
|
|
78
|
+
...bindings.flatMap(({ environmentRef, storageRef }) => [
|
|
79
|
+
`$env:${environmentRef} = $env:${storageRef}`,
|
|
80
|
+
`Remove-Item Env:${storageRef}`,
|
|
81
|
+
]),
|
|
82
|
+
"$env:MARIVO_PERSIST_CREDENTIALS = '0'",
|
|
83
|
+
].join('\n');
|
|
84
|
+
}
|
|
85
|
+
function leaseFailure(code, message, details = {}) {
|
|
86
|
+
return new MarivoEnvironmentError(code, message, details);
|
|
87
|
+
}
|
|
88
|
+
function commandArguments(execution) {
|
|
89
|
+
return typeof execution.arguments === 'object' &&
|
|
90
|
+
execution.arguments !== null &&
|
|
91
|
+
!Array.isArray(execution.arguments)
|
|
92
|
+
? execution.arguments
|
|
93
|
+
: undefined;
|
|
94
|
+
}
|
|
95
|
+
function hasCredentialStorageRefAt(command, offset, caseInsensitive) {
|
|
96
|
+
const candidate = caseInsensitive ? command.slice(offset).toUpperCase() : command.slice(offset);
|
|
97
|
+
if (!candidate.startsWith(MARIVO_CREDENTIAL_STORAGE_PREFIX))
|
|
98
|
+
return false;
|
|
99
|
+
let cursor = MARIVO_CREDENTIAL_STORAGE_PREFIX.length;
|
|
100
|
+
const firstHex = cursor;
|
|
101
|
+
while (cursor < candidate.length && /[A-F0-9]/.test(candidate[cursor] ?? ''))
|
|
102
|
+
cursor++;
|
|
103
|
+
return (cursor > firstHex &&
|
|
104
|
+
(cursor === candidate.length || !/[A-Z0-9_]/i.test(candidate[cursor] ?? '')));
|
|
105
|
+
}
|
|
106
|
+
function isBashCommentStart(command, offset) {
|
|
107
|
+
if (offset === 0)
|
|
108
|
+
return true;
|
|
109
|
+
return /[\s;&|()]/.test(command[offset - 1] ?? '');
|
|
110
|
+
}
|
|
111
|
+
function containsCredentialDereference(execution) {
|
|
112
|
+
const command = commandArguments(execution)?.command;
|
|
113
|
+
if (typeof command !== 'string')
|
|
114
|
+
return false;
|
|
115
|
+
const pwsh = execution.name === 'pwsh';
|
|
116
|
+
let quote;
|
|
117
|
+
let comment = false;
|
|
118
|
+
for (let index = 0; index < command.length; index++) {
|
|
119
|
+
const character = command[index];
|
|
120
|
+
if (comment) {
|
|
121
|
+
if (character === '\n' || character === '\r')
|
|
122
|
+
comment = false;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (quote === "'") {
|
|
126
|
+
if (character === "'") {
|
|
127
|
+
if (pwsh && command[index + 1] === "'")
|
|
128
|
+
index++;
|
|
129
|
+
else
|
|
130
|
+
quote = undefined;
|
|
131
|
+
}
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (character === (pwsh ? '`' : '\\')) {
|
|
135
|
+
index++;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (character === '#' && quote === undefined && (pwsh || isBashCommentStart(command, index))) {
|
|
139
|
+
comment = true;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (character === "'" && quote === undefined) {
|
|
143
|
+
quote = "'";
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (character === '"') {
|
|
147
|
+
quote = quote === '"' ? undefined : '"';
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (character !== '$')
|
|
151
|
+
continue;
|
|
152
|
+
if (pwsh) {
|
|
153
|
+
if (command.slice(index + 1, index + 5).toLowerCase() === 'env:' &&
|
|
154
|
+
hasCredentialStorageRefAt(command, index + 5, true)) {
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
if (command.slice(index + 1, index + 6).toLowerCase() === '{env:' &&
|
|
158
|
+
hasCredentialStorageRefAt(command, index + 6, true)) {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const refOffset = command[index + 1] === '{' ? index + 2 : index + 1;
|
|
164
|
+
if (hasCredentialStorageRefAt(command, refOffset, false))
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
function leaseToken(execution) {
|
|
170
|
+
const command = commandArguments(execution)?.command;
|
|
171
|
+
if (typeof command !== 'string')
|
|
172
|
+
return undefined;
|
|
173
|
+
const firstLine = command.split(/\r?\n/, 1)[0] ?? '';
|
|
174
|
+
const candidate = firstLine.startsWith(MARIVO_CREDENTIAL_LEASE_PREFIX)
|
|
175
|
+
? firstLine.slice(MARIVO_CREDENTIAL_LEASE_PREFIX.length)
|
|
176
|
+
: undefined;
|
|
177
|
+
if (candidate !== undefined && TOKEN_PATTERN.test(candidate))
|
|
178
|
+
return candidate;
|
|
179
|
+
if (firstLine.includes('dsh-marivo-credential-')) {
|
|
180
|
+
throw leaseFailure('shell-credential-lease-invalid', 'Marivo shell credential lease marker is malformed or unsupported');
|
|
181
|
+
}
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
function isHarnessPersistentShell(agent, name) {
|
|
185
|
+
const definition = agent.ctx.tools.get(name, agent);
|
|
186
|
+
return definition?.output.schema.type === 'string';
|
|
187
|
+
}
|
|
188
|
+
export function assertMarivoCredentialReferences(refs) {
|
|
189
|
+
for (const ref of refs) {
|
|
190
|
+
marivoCredentialStorageRef(ref);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/** Own bounded leases and expose credential values only to each admitted Shell execution. */
|
|
194
|
+
export class MarivoShellCredentialLeases {
|
|
195
|
+
#ctx;
|
|
196
|
+
#credentials;
|
|
197
|
+
#ttlMs;
|
|
198
|
+
#maxUses;
|
|
199
|
+
#leases = new Map();
|
|
200
|
+
#executionValues = new Map();
|
|
201
|
+
#contributors = new Map();
|
|
202
|
+
#disposed = false;
|
|
203
|
+
constructor(ctx, credentials, options = {}) {
|
|
204
|
+
const ttlMs = options.ttlMs ?? DEFAULT_MARIVO_CREDENTIAL_LEASE_TTL_MS;
|
|
205
|
+
const maxUses = options.maxUses ?? DEFAULT_MARIVO_CREDENTIAL_LEASE_MAX_USES;
|
|
206
|
+
if (!Number.isSafeInteger(ttlMs) ||
|
|
207
|
+
ttlMs < 1 ||
|
|
208
|
+
ttlMs > DEFAULT_MARIVO_CREDENTIAL_LEASE_TTL_MS) {
|
|
209
|
+
throw new TypeError(`Marivo shell credential lease ttlMs must be an integer from 1 to ${DEFAULT_MARIVO_CREDENTIAL_LEASE_TTL_MS}`);
|
|
210
|
+
}
|
|
211
|
+
if (!Number.isSafeInteger(maxUses) ||
|
|
212
|
+
maxUses < 1 ||
|
|
213
|
+
maxUses > DEFAULT_MARIVO_CREDENTIAL_LEASE_MAX_USES) {
|
|
214
|
+
throw new TypeError(`Marivo shell credential lease maxUses must be an integer from 1 to ${DEFAULT_MARIVO_CREDENTIAL_LEASE_MAX_USES}`);
|
|
215
|
+
}
|
|
216
|
+
this.#ctx = ctx;
|
|
217
|
+
this.#credentials = credentials;
|
|
218
|
+
this.#ttlMs = ttlMs;
|
|
219
|
+
this.#maxUses = maxUses;
|
|
220
|
+
}
|
|
221
|
+
#ensureContributor(ref) {
|
|
222
|
+
if (this.#disposed)
|
|
223
|
+
throw new Error('Marivo shell credential leases are disposed');
|
|
224
|
+
if (this.#contributors.has(ref))
|
|
225
|
+
return;
|
|
226
|
+
const dispose = shellEnvironmentRegistry(this.#ctx).register({
|
|
227
|
+
name: `dsh-data-analysis:credential:${ref}`,
|
|
228
|
+
variables: {
|
|
229
|
+
[ref]: {
|
|
230
|
+
description: 'Marivo datasource credential authorized for this Shell execution.',
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
resolve: (execution) => {
|
|
234
|
+
const value = this.#executionValues.get(execution)?.values.get(ref);
|
|
235
|
+
return value === undefined ? {} : { [ref]: value };
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
this.#contributors.set(ref, dispose);
|
|
239
|
+
}
|
|
240
|
+
#purgeExpired(now) {
|
|
241
|
+
for (const [token, lease] of this.#leases) {
|
|
242
|
+
if (lease.expiresAt <= now)
|
|
243
|
+
this.#leases.delete(token);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
revokeLease(agent, bridge, datasourceName) {
|
|
247
|
+
for (const [token, lease] of this.#leases) {
|
|
248
|
+
if (lease.agent === agent &&
|
|
249
|
+
lease.workspaceFingerprint === bridge.binding.fingerprint &&
|
|
250
|
+
lease.datasourceName === datasourceName) {
|
|
251
|
+
this.#leases.delete(token);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
issueLease(agent, bridge, datasourceName, refs) {
|
|
256
|
+
if (this.#disposed)
|
|
257
|
+
throw new Error('Marivo shell credential leases are disposed');
|
|
258
|
+
const now = Date.now();
|
|
259
|
+
this.#purgeExpired(now);
|
|
260
|
+
this.revokeLease(agent, bridge, datasourceName);
|
|
261
|
+
assertMarivoCredentialReferences(refs);
|
|
262
|
+
const bindings = credentialBindings(refs);
|
|
263
|
+
for (const binding of bindings)
|
|
264
|
+
this.#ensureContributor(binding.storageRef);
|
|
265
|
+
const token = randomBytes(32).toString('base64url');
|
|
266
|
+
this.#leases.set(token, {
|
|
267
|
+
agent,
|
|
268
|
+
workspaceFingerprint: bridge.binding.fingerprint,
|
|
269
|
+
datasourceName,
|
|
270
|
+
bindings,
|
|
271
|
+
expiresAt: now + this.#ttlMs,
|
|
272
|
+
remainingUses: this.#maxUses,
|
|
273
|
+
});
|
|
274
|
+
return {
|
|
275
|
+
token,
|
|
276
|
+
expires_in_ms: this.#ttlMs,
|
|
277
|
+
max_uses: this.#maxUses,
|
|
278
|
+
usage: 'bounded-foreground-shell-lease',
|
|
279
|
+
bash_prelude: bashPrelude(token, bindings),
|
|
280
|
+
pwsh_prelude: pwshPrelude(token, bindings),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
async #claim(agent, bridgeSource, execution, token) {
|
|
284
|
+
const lease = this.#leases.get(token);
|
|
285
|
+
if (lease === undefined) {
|
|
286
|
+
throw leaseFailure('shell-credential-lease-invalid', 'Marivo shell credential lease is unknown; call marivo_datasource_access again');
|
|
287
|
+
}
|
|
288
|
+
if (lease.expiresAt <= Date.now()) {
|
|
289
|
+
this.#leases.delete(token);
|
|
290
|
+
throw leaseFailure('shell-credential-lease-expired', 'Marivo shell credential lease has expired; call marivo_datasource_access again', { datasourceName: lease.datasourceName });
|
|
291
|
+
}
|
|
292
|
+
if (execution.agent !== agent || lease.agent !== agent) {
|
|
293
|
+
throw leaseFailure('shell-credential-lease-scope-mismatch', 'Marivo shell credential lease belongs to another Agent');
|
|
294
|
+
}
|
|
295
|
+
const bridge = await resolveMarivoDatasourceBridge(bridgeSource);
|
|
296
|
+
if (this.#leases.get(token) !== lease) {
|
|
297
|
+
throw leaseFailure('shell-credential-lease-invalid', 'Marivo shell credential lease was revoked; call marivo_datasource_access again');
|
|
298
|
+
}
|
|
299
|
+
if (lease.workspaceFingerprint !== bridge.binding.fingerprint) {
|
|
300
|
+
throw leaseFailure('shell-credential-lease-scope-mismatch', 'Marivo shell credential lease belongs to another Workspace binding');
|
|
301
|
+
}
|
|
302
|
+
if (lease.expiresAt <= Date.now()) {
|
|
303
|
+
this.#leases.delete(token);
|
|
304
|
+
throw leaseFailure('shell-credential-lease-expired', 'Marivo shell credential lease has expired; call marivo_datasource_access again', { datasourceName: lease.datasourceName });
|
|
305
|
+
}
|
|
306
|
+
const args = commandArguments(execution);
|
|
307
|
+
if (args?.run_in_background === true) {
|
|
308
|
+
throw leaseFailure('shell-credential-injection-unsupported', 'Marivo shell credential leases cannot be used by background Shell executions', { tool: execution.name });
|
|
309
|
+
}
|
|
310
|
+
if (isHarnessPersistentShell(agent, execution.name)) {
|
|
311
|
+
throw leaseFailure('shell-credential-injection-unsupported', `Marivo shell credential leases cannot be used by persistent ${execution.name}`, { tool: execution.name });
|
|
312
|
+
}
|
|
313
|
+
if (lease.remainingUses < 1) {
|
|
314
|
+
this.#leases.delete(token);
|
|
315
|
+
throw leaseFailure('shell-credential-lease-exhausted', 'Marivo shell credential lease is exhausted; call marivo_datasource_access again', { datasourceName: lease.datasourceName });
|
|
316
|
+
}
|
|
317
|
+
lease.remainingUses--;
|
|
318
|
+
const values = new Map();
|
|
319
|
+
for (const binding of lease.bindings) {
|
|
320
|
+
execution.signal.throwIfAborted();
|
|
321
|
+
let resolved;
|
|
322
|
+
try {
|
|
323
|
+
resolved = await this.#credentials.resolve(credentialRef(binding.storageRef));
|
|
324
|
+
}
|
|
325
|
+
catch {
|
|
326
|
+
throw leaseFailure('shell-credential-lease-resolve-failed', 'A credential authorized by the Marivo shell lease could not be resolved', { datasourceName: lease.datasourceName, ref: binding.environmentRef });
|
|
327
|
+
}
|
|
328
|
+
if (resolved === undefined) {
|
|
329
|
+
throw leaseFailure('shell-credential-lease-resolve-failed', 'A credential authorized by the Marivo shell lease is no longer configured', { datasourceName: lease.datasourceName, ref: binding.environmentRef });
|
|
330
|
+
}
|
|
331
|
+
values.set(binding.storageRef, resolved.value);
|
|
332
|
+
}
|
|
333
|
+
this.#executionValues.set(execution, { agent, values });
|
|
334
|
+
}
|
|
335
|
+
async prepareExecution(agent, bridgeSource, execution) {
|
|
336
|
+
if (this.#disposed)
|
|
337
|
+
throw new Error('Marivo shell credential leases are disposed');
|
|
338
|
+
const token = leaseToken(execution);
|
|
339
|
+
if (token === undefined) {
|
|
340
|
+
if (containsCredentialDereference(execution)) {
|
|
341
|
+
throw leaseFailure('shell-credential-lease-invalid', 'Marivo shell credential reference is not authorized because the exact prelude lease marker is missing. Call marivo_datasource_access again, copy the exact prelude, and use a foreground Shell execution', { tool: execution.name });
|
|
342
|
+
}
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
await this.#claim(agent, bridgeSource, execution, token);
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
installAgent(agent, bridgeSource) {
|
|
349
|
+
const stopPreExecute = agent.ctx.on('tools/pre-execute', async (execution, next) => {
|
|
350
|
+
if (execution.agent === agent && SHELL_TOOL_NAMES.has(execution.name)) {
|
|
351
|
+
await this.prepareExecution(agent, bridgeSource, execution);
|
|
352
|
+
}
|
|
353
|
+
return next();
|
|
354
|
+
});
|
|
355
|
+
const stopResult = agent.ctx.on('tools/result', (execution) => {
|
|
356
|
+
if (execution.agent === agent)
|
|
357
|
+
this.#executionValues.delete(execution);
|
|
358
|
+
});
|
|
359
|
+
let active = true;
|
|
360
|
+
return () => {
|
|
361
|
+
if (!active)
|
|
362
|
+
return;
|
|
363
|
+
active = false;
|
|
364
|
+
stopPreExecute();
|
|
365
|
+
stopResult();
|
|
366
|
+
for (const [token, lease] of this.#leases) {
|
|
367
|
+
if (lease.agent === agent)
|
|
368
|
+
this.#leases.delete(token);
|
|
369
|
+
}
|
|
370
|
+
for (const [execution, snapshot] of this.#executionValues) {
|
|
371
|
+
if (snapshot.agent === agent)
|
|
372
|
+
this.#executionValues.delete(execution);
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
dispose() {
|
|
377
|
+
if (this.#disposed)
|
|
378
|
+
return;
|
|
379
|
+
this.#disposed = true;
|
|
380
|
+
this.#leases.clear();
|
|
381
|
+
this.#executionValues.clear();
|
|
382
|
+
for (const dispose of this.#contributors.values())
|
|
383
|
+
dispose();
|
|
384
|
+
this.#contributors.clear();
|
|
385
|
+
}
|
|
386
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
2
|
+
import { resolveMarivoDatasourceBridge, } from "./bridge.js";
|
|
3
|
+
import { inspectMarivoDatasourceCredentials } from "./credentials.js";
|
|
4
|
+
export const MARIVO_DATASOURCE_TEST_TOOL_NAME = 'marivo_datasource_test';
|
|
5
|
+
function datasourceName(value) {
|
|
6
|
+
if (typeof value !== 'string' || value.trim() === '' || value.length > 256) {
|
|
7
|
+
throw new TypeError('marivo_datasource_test name must be a non-empty string of at most 256 characters');
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
function renderValue(value) {
|
|
12
|
+
if (value.status === 'needs-credentials')
|
|
13
|
+
return JSON.stringify(value);
|
|
14
|
+
if (value.status === 'ok') {
|
|
15
|
+
return `Marivo datasource ${value.name} connection test succeeded${value.latency_ms === null ? '' : ` in ${value.latency_ms} ms`}.`;
|
|
16
|
+
}
|
|
17
|
+
return JSON.stringify(value);
|
|
18
|
+
}
|
|
19
|
+
/** Build the scoped datasource connection-test Tool. */
|
|
20
|
+
export function createMarivoDatasourceTestTool(bridgeSource, credentials, options) {
|
|
21
|
+
return defineTool({
|
|
22
|
+
name: MARIVO_DATASOURCE_TEST_TOOL_NAME,
|
|
23
|
+
description: 'Test one configured Marivo datasource connection. Missing datasource environment references are requested through the DSH credential service.',
|
|
24
|
+
parameters: {
|
|
25
|
+
name: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
required: true,
|
|
28
|
+
description: 'Configured Marivo datasource name.',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
output: {
|
|
32
|
+
schema: { type: 'json' },
|
|
33
|
+
render: (_args, value) => [
|
|
34
|
+
{ type: 'text', text: renderValue(value) },
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
timeoutMs: 65_000,
|
|
38
|
+
async execute(args, exec) {
|
|
39
|
+
const name = datasourceName(args.name);
|
|
40
|
+
const bridge = await resolveMarivoDatasourceBridge(bridgeSource);
|
|
41
|
+
options.revokeShellLease(bridge, name);
|
|
42
|
+
const described = await bridge.describe(name, exec.signal);
|
|
43
|
+
const overlay = {};
|
|
44
|
+
const missing = await inspectMarivoDatasourceCredentials(described.refs, credentials, exec.signal, (ref, value) => {
|
|
45
|
+
overlay[ref] = value;
|
|
46
|
+
});
|
|
47
|
+
if (missing.length > 0)
|
|
48
|
+
return { status: 'needs-credentials', name: described.name, refs: missing };
|
|
49
|
+
const tested = await bridge.test(name, overlay, exec.signal);
|
|
50
|
+
if (tested.ok) {
|
|
51
|
+
return { status: 'ok', name: tested.name, latency_ms: tested.latency_ms };
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
status: 'failed',
|
|
55
|
+
name: tested.name,
|
|
56
|
+
latency_ms: tested.latency_ms,
|
|
57
|
+
failure: tested.failure,
|
|
58
|
+
repair: tested.repair,
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
export function registerMarivoDatasourceTestTool(ctx, bridgeSource, credentials, options) {
|
|
64
|
+
return ctx.tools.register(createMarivoDatasourceTestTool(bridgeSource, credentials, options));
|
|
65
|
+
}
|