@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,369 @@
|
|
|
1
|
+
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
2
|
+
import { scopeParentOf } from '@deepseek-ai/dsh-scope';
|
|
3
|
+
import { MARIVO_HELP_TOOL_NAME, readMarivoHelpTargets, registerMarivoHelpTool, renderMarivoHelpValue, resolveMarivoHelpBridge, } from "./help.js";
|
|
4
|
+
const SKILL_TOOL_NAME = 'skill';
|
|
5
|
+
export const MARIVO_ROOT_HELP_TARGETS = Object.freeze({
|
|
6
|
+
'marivo-analysis': 'analysis',
|
|
7
|
+
'marivo-semantic': 'authoring',
|
|
8
|
+
});
|
|
9
|
+
const MARIVO_SKILL_ORDER = ['marivo-analysis', 'marivo-semantic'];
|
|
10
|
+
export class MarivoDisclosureError extends Error {
|
|
11
|
+
code = 'root-help-failed';
|
|
12
|
+
skills;
|
|
13
|
+
constructor(skills, cause) {
|
|
14
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
15
|
+
super(`Marivo root help disclosure failed for ${skills.join(', ')}: ${detail}`, { cause });
|
|
16
|
+
this.name = 'MarivoDisclosureError';
|
|
17
|
+
this.skills = [...skills];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function isMarivoSkillName(value) {
|
|
21
|
+
return typeof value === 'string' && Object.hasOwn(MARIVO_ROOT_HELP_TARGETS, value);
|
|
22
|
+
}
|
|
23
|
+
function rootHelpSource(value) {
|
|
24
|
+
if (typeof value !== 'object' || value === null)
|
|
25
|
+
return undefined;
|
|
26
|
+
const source = value;
|
|
27
|
+
if (source.kind !== 'marivo-disclosure' ||
|
|
28
|
+
source.form !== 'root-help' ||
|
|
29
|
+
!isMarivoSkillName(source.skill) ||
|
|
30
|
+
source.target !== MARIVO_ROOT_HELP_TARGETS[source.skill] ||
|
|
31
|
+
typeof source.environmentFingerprint !== 'string' ||
|
|
32
|
+
typeof source.bodyDigest !== 'string')
|
|
33
|
+
return undefined;
|
|
34
|
+
return source;
|
|
35
|
+
}
|
|
36
|
+
function focusedHelpMeta(value) {
|
|
37
|
+
if (typeof value !== 'object' || value === null)
|
|
38
|
+
return [];
|
|
39
|
+
const meta = value;
|
|
40
|
+
if (meta.kind !== 'marivo-help-disclosure' ||
|
|
41
|
+
typeof meta.environmentFingerprint !== 'string' ||
|
|
42
|
+
!Array.isArray(meta.targets))
|
|
43
|
+
return [];
|
|
44
|
+
return meta.targets.flatMap((item) => {
|
|
45
|
+
if (typeof item !== 'object' || item === null)
|
|
46
|
+
return [];
|
|
47
|
+
const target = item.target;
|
|
48
|
+
const bodyDigest = item.bodyDigest;
|
|
49
|
+
const delivery = item.delivery;
|
|
50
|
+
if (typeof target !== 'string' ||
|
|
51
|
+
typeof bodyDigest !== 'string' ||
|
|
52
|
+
(delivery !== 'delivered' && delivery !== 'replacement'))
|
|
53
|
+
return [];
|
|
54
|
+
return [
|
|
55
|
+
{
|
|
56
|
+
environmentFingerprint: meta.environmentFingerprint,
|
|
57
|
+
target,
|
|
58
|
+
bodyDigest,
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function successfulSkillName(result) {
|
|
64
|
+
if (result.isError || typeof result.value !== 'object' || result.value === null)
|
|
65
|
+
return undefined;
|
|
66
|
+
const name = result.value.name;
|
|
67
|
+
return isMarivoSkillName(name) ? name : undefined;
|
|
68
|
+
}
|
|
69
|
+
function successfulHelpValue(result) {
|
|
70
|
+
if (result.isError || typeof result.value !== 'object' || result.value === null)
|
|
71
|
+
return undefined;
|
|
72
|
+
const value = result.value;
|
|
73
|
+
if (typeof value.environment !== 'object' ||
|
|
74
|
+
value.environment === null ||
|
|
75
|
+
typeof value.environment.fingerprint !== 'string' ||
|
|
76
|
+
!Array.isArray(value.targets))
|
|
77
|
+
return undefined;
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
function renderRootHelpMessage(skill, value, update) {
|
|
81
|
+
const target = MARIVO_ROOT_HELP_TARGETS[skill];
|
|
82
|
+
const item = value.targets[0];
|
|
83
|
+
if (item === undefined || item.target !== target) {
|
|
84
|
+
throw new Error(`Marivo root help ${target} did not return its requested target`);
|
|
85
|
+
}
|
|
86
|
+
return createUserMessage({
|
|
87
|
+
content: [
|
|
88
|
+
{
|
|
89
|
+
type: 'text',
|
|
90
|
+
text: [
|
|
91
|
+
`<marivo_help_context skill="${skill}" target="${target}">`,
|
|
92
|
+
update
|
|
93
|
+
? 'This live root help replaces the earlier disclosure for this skill.'
|
|
94
|
+
: 'This live root help accompanies the loaded Marivo skill.',
|
|
95
|
+
'',
|
|
96
|
+
renderMarivoHelpValue(value),
|
|
97
|
+
'</marivo_help_context>',
|
|
98
|
+
].join('\n'),
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
source: {
|
|
102
|
+
kind: 'marivo-disclosure',
|
|
103
|
+
form: 'root-help',
|
|
104
|
+
skill,
|
|
105
|
+
target,
|
|
106
|
+
environmentFingerprint: value.environment.fingerprint,
|
|
107
|
+
bodyDigest: item.bodyDigest,
|
|
108
|
+
...(update ? { update: true } : {}),
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/** Per-Agent controller for skill-triggered, prompt-visible Marivo help disclosure. */
|
|
113
|
+
export class MarivoDisclosureController {
|
|
114
|
+
agent;
|
|
115
|
+
bridgeSource;
|
|
116
|
+
options;
|
|
117
|
+
#activeSkills = new Set();
|
|
118
|
+
#pendingSkills = new Set();
|
|
119
|
+
#visibleHelp = new Map();
|
|
120
|
+
#lifecycleAbort = new AbortController();
|
|
121
|
+
#disposers = [];
|
|
122
|
+
#disposed = false;
|
|
123
|
+
#telemetry = { activations: [], rootHelp: [], failures: [] };
|
|
124
|
+
constructor(agent, bridgeSource, options = {}) {
|
|
125
|
+
this.agent = agent;
|
|
126
|
+
this.bridgeSource = bridgeSource;
|
|
127
|
+
this.options = Object.freeze({
|
|
128
|
+
...(options.helpLimits === undefined ? {} : { helpLimits: { ...options.helpLimits } }),
|
|
129
|
+
});
|
|
130
|
+
this.#restoreActiveSkills();
|
|
131
|
+
}
|
|
132
|
+
get activeSkills() {
|
|
133
|
+
return MARIVO_SKILL_ORDER.filter((skill) => this.#activeSkills.has(skill));
|
|
134
|
+
}
|
|
135
|
+
telemetry() {
|
|
136
|
+
return {
|
|
137
|
+
activations: this.#telemetry.activations.map((record) => ({ ...record })),
|
|
138
|
+
rootHelp: this.#telemetry.rootHelp.map((record) => ({ ...record })),
|
|
139
|
+
failures: this.#telemetry.failures.map((record) => ({
|
|
140
|
+
...record,
|
|
141
|
+
skills: [...record.skills],
|
|
142
|
+
})),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
addDisposer(disposer) {
|
|
146
|
+
this.#disposers.push(disposer);
|
|
147
|
+
}
|
|
148
|
+
#activate(skill, source) {
|
|
149
|
+
this.#activeSkills.add(skill);
|
|
150
|
+
this.#pendingSkills.add(skill);
|
|
151
|
+
this.#telemetry.activations.push({ skill, source });
|
|
152
|
+
}
|
|
153
|
+
observeToolResult(exec, result) {
|
|
154
|
+
if (exec.agent !== this.agent)
|
|
155
|
+
return;
|
|
156
|
+
if (exec.name === SKILL_TOOL_NAME && this.#inheritedSkillToolVisible()) {
|
|
157
|
+
const skill = successfulSkillName(result);
|
|
158
|
+
if (skill !== undefined)
|
|
159
|
+
this.#activate(skill, 'model');
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (exec.name !== MARIVO_HELP_TOOL_NAME)
|
|
163
|
+
return;
|
|
164
|
+
const value = successfulHelpValue(result);
|
|
165
|
+
if (value === undefined)
|
|
166
|
+
return;
|
|
167
|
+
for (const item of value.targets) {
|
|
168
|
+
if (item.delivery === 'already-visible')
|
|
169
|
+
continue;
|
|
170
|
+
this.#visibleHelp.set(item.target, {
|
|
171
|
+
environmentFingerprint: value.environment.fingerprint,
|
|
172
|
+
target: item.target,
|
|
173
|
+
bodyDigest: item.bodyDigest,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
resolveDelivery(query) {
|
|
178
|
+
const visible = this.#visibleHelp.get(query.target);
|
|
179
|
+
if (visible?.environmentFingerprint === query.environmentFingerprint &&
|
|
180
|
+
visible.bodyDigest === query.bodyDigest)
|
|
181
|
+
return 'already-visible';
|
|
182
|
+
return visible === undefined ? 'delivered' : 'replacement';
|
|
183
|
+
}
|
|
184
|
+
async prepareStep(messages, signal) {
|
|
185
|
+
if (this.#disposed)
|
|
186
|
+
throw this.#lifecycleAbort.signal.reason;
|
|
187
|
+
this.#observeExplicitInvocations(messages);
|
|
188
|
+
this.#refreshVisibleHelp(messages);
|
|
189
|
+
if (this.#activeSkills.size === 0)
|
|
190
|
+
return [];
|
|
191
|
+
const bridge = await resolveMarivoHelpBridge(this.bridgeSource);
|
|
192
|
+
if (this.#disposed)
|
|
193
|
+
throw this.#lifecycleAbort.signal.reason;
|
|
194
|
+
const requested = MARIVO_SKILL_ORDER.filter((skill) => {
|
|
195
|
+
if (!this.#activeSkills.has(skill))
|
|
196
|
+
return false;
|
|
197
|
+
const target = MARIVO_ROOT_HELP_TARGETS[skill];
|
|
198
|
+
const visible = this.#visibleHelp.get(target);
|
|
199
|
+
return (this.#pendingSkills.has(skill) ||
|
|
200
|
+
visible?.environmentFingerprint !== bridge.binding.fingerprint);
|
|
201
|
+
});
|
|
202
|
+
if (requested.length === 0)
|
|
203
|
+
return [];
|
|
204
|
+
const batchAbort = new AbortController();
|
|
205
|
+
const readSignal = AbortSignal.any([signal, this.#lifecycleAbort.signal, batchAbort.signal]);
|
|
206
|
+
let primaryCause;
|
|
207
|
+
const settled = await Promise.allSettled(requested.map(async (skill) => {
|
|
208
|
+
try {
|
|
209
|
+
const target = MARIVO_ROOT_HELP_TARGETS[skill];
|
|
210
|
+
const reason = this.#pendingSkills.has(skill) ? 'activation' : 'recovery';
|
|
211
|
+
const startedAt = performance.now();
|
|
212
|
+
const value = await readMarivoHelpTargets(bridge, [target], {
|
|
213
|
+
limits: this.options.helpLimits,
|
|
214
|
+
signal: readSignal,
|
|
215
|
+
resolveDelivery: (query) => this.resolveDelivery(query),
|
|
216
|
+
});
|
|
217
|
+
return { skill, target, reason, startedAt, value };
|
|
218
|
+
}
|
|
219
|
+
catch (cause) {
|
|
220
|
+
primaryCause ??= cause;
|
|
221
|
+
batchAbort.abort(cause);
|
|
222
|
+
throw cause;
|
|
223
|
+
}
|
|
224
|
+
}));
|
|
225
|
+
if (this.#disposed)
|
|
226
|
+
throw this.#lifecycleAbort.signal.reason;
|
|
227
|
+
const rejected = settled.find((result) => result.status === 'rejected');
|
|
228
|
+
if (rejected !== undefined) {
|
|
229
|
+
const error = new MarivoDisclosureError(requested, primaryCause ?? rejected.reason);
|
|
230
|
+
this.#telemetry.failures.push({ skills: [...requested], message: error.message });
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
readSignal.throwIfAborted();
|
|
234
|
+
const reads = settled.map((result) => {
|
|
235
|
+
if (result.status !== 'fulfilled')
|
|
236
|
+
throw new Error('unreachable rejected root Help read');
|
|
237
|
+
return result.value;
|
|
238
|
+
});
|
|
239
|
+
const injections = [];
|
|
240
|
+
for (const read of reads) {
|
|
241
|
+
const item = read.value.targets[0];
|
|
242
|
+
if (item === undefined || item.target !== read.target) {
|
|
243
|
+
const error = new MarivoDisclosureError([read.skill], new Error('root target missing'));
|
|
244
|
+
this.#telemetry.failures.push({ skills: [read.skill], message: error.message });
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
const update = this.#visibleHelp.has(read.target) || this.#hasHistoricalDisclosure(read.skill);
|
|
248
|
+
this.#telemetry.rootHelp.push({
|
|
249
|
+
skill: read.skill,
|
|
250
|
+
target: read.target,
|
|
251
|
+
environmentFingerprint: read.value.environment.fingerprint,
|
|
252
|
+
bodyDigest: item.bodyDigest,
|
|
253
|
+
delivery: item.delivery,
|
|
254
|
+
reason: read.reason,
|
|
255
|
+
helpTextBytes: Buffer.byteLength(item.body),
|
|
256
|
+
latencyMs: Math.max(0, Math.round(performance.now() - read.startedAt)),
|
|
257
|
+
});
|
|
258
|
+
this.#pendingSkills.delete(read.skill);
|
|
259
|
+
if (item.delivery === 'already-visible')
|
|
260
|
+
continue;
|
|
261
|
+
const message = renderRootHelpMessage(read.skill, read.value, update);
|
|
262
|
+
injections.push(message);
|
|
263
|
+
this.#visibleHelp.set(read.target, {
|
|
264
|
+
environmentFingerprint: read.value.environment.fingerprint,
|
|
265
|
+
target: read.target,
|
|
266
|
+
bodyDigest: item.bodyDigest,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
return injections;
|
|
270
|
+
}
|
|
271
|
+
dispose() {
|
|
272
|
+
if (this.#disposed)
|
|
273
|
+
return;
|
|
274
|
+
this.#disposed = true;
|
|
275
|
+
this.#lifecycleAbort.abort(new Error('Marivo disclosure controller disposed'));
|
|
276
|
+
for (const disposer of this.#disposers.reverse())
|
|
277
|
+
disposer();
|
|
278
|
+
this.#disposers = [];
|
|
279
|
+
this.#activeSkills.clear();
|
|
280
|
+
this.#pendingSkills.clear();
|
|
281
|
+
this.#visibleHelp.clear();
|
|
282
|
+
}
|
|
283
|
+
#inheritedSkillToolVisible() {
|
|
284
|
+
const inheritedSkillTool = this.agent.ctx.tools.get(SKILL_TOOL_NAME, scopeParentOf(this.agent));
|
|
285
|
+
return (inheritedSkillTool !== undefined &&
|
|
286
|
+
this.agent.ctx.tools.get(SKILL_TOOL_NAME, this.agent) === inheritedSkillTool);
|
|
287
|
+
}
|
|
288
|
+
#observeExplicitInvocations(messages) {
|
|
289
|
+
for (const message of messages) {
|
|
290
|
+
const source = message.source;
|
|
291
|
+
if (source.kind === 'skill-invocation' && isMarivoSkillName(source.name)) {
|
|
292
|
+
this.#activate(source.name, 'user');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
#restoreActiveSkills() {
|
|
297
|
+
for (const event of this.agent.session.events) {
|
|
298
|
+
if (event.type !== 'user/message')
|
|
299
|
+
continue;
|
|
300
|
+
const disclosure = rootHelpSource(event.data.source);
|
|
301
|
+
if (disclosure !== undefined)
|
|
302
|
+
this.#activeSkills.add(disclosure.skill);
|
|
303
|
+
const source = event.data.source;
|
|
304
|
+
if (source.kind === 'skill-invocation' && isMarivoSkillName(source.name)) {
|
|
305
|
+
this.#activeSkills.add(source.name);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
#refreshVisibleHelp(messages) {
|
|
310
|
+
this.#visibleHelp.clear();
|
|
311
|
+
const visible = new Set(this.agent.session.surface.nodes);
|
|
312
|
+
for (const event of this.agent.session.events) {
|
|
313
|
+
if (!visible.has(event.seq))
|
|
314
|
+
continue;
|
|
315
|
+
if (event.type === 'user/message') {
|
|
316
|
+
const source = rootHelpSource(event.data.source);
|
|
317
|
+
if (source !== undefined) {
|
|
318
|
+
this.#visibleHelp.set(source.target, {
|
|
319
|
+
environmentFingerprint: source.environmentFingerprint,
|
|
320
|
+
target: source.target,
|
|
321
|
+
bodyDigest: source.bodyDigest,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
else if (event.type === 'tool/result') {
|
|
326
|
+
for (const item of focusedHelpMeta(event.data.meta)) {
|
|
327
|
+
this.#visibleHelp.set(item.target, item);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
for (const message of messages) {
|
|
332
|
+
const source = rootHelpSource(message.source);
|
|
333
|
+
if (source === undefined)
|
|
334
|
+
continue;
|
|
335
|
+
this.#visibleHelp.set(source.target, {
|
|
336
|
+
environmentFingerprint: source.environmentFingerprint,
|
|
337
|
+
target: source.target,
|
|
338
|
+
bodyDigest: source.bodyDigest,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
#hasHistoricalDisclosure(skill) {
|
|
343
|
+
return this.agent.session.events.some((event) => event.type === 'user/message' && rootHelpSource(event.data.source)?.skill === skill);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/** Install skill-triggered Marivo live-help disclosure for one Agent scope. */
|
|
347
|
+
export function installMarivoDisclosure(_ctx, agent, bridgeSource, options = {}) {
|
|
348
|
+
const controller = new MarivoDisclosureController(agent, bridgeSource, options);
|
|
349
|
+
try {
|
|
350
|
+
controller.addDisposer(registerMarivoHelpTool(agent.ctx, bridgeSource, options.helpLimits, (query) => controller.resolveDelivery(query)));
|
|
351
|
+
controller.addDisposer(agent.ctx.on('tools/result', (exec, result) => {
|
|
352
|
+
controller.observeToolResult(exec, result);
|
|
353
|
+
}));
|
|
354
|
+
controller.addDisposer(agent.ctx.on('agent/pre-step', async ({ signal }, next) => {
|
|
355
|
+
const decision = await next();
|
|
356
|
+
if (decision.kind === 'reject')
|
|
357
|
+
return decision;
|
|
358
|
+
const injections = await controller.prepareStep(decision.messages, signal);
|
|
359
|
+
return injections.length === 0
|
|
360
|
+
? decision
|
|
361
|
+
: { kind: 'enter', messages: [...decision.messages, ...injections] };
|
|
362
|
+
}, true));
|
|
363
|
+
return controller;
|
|
364
|
+
}
|
|
365
|
+
catch (cause) {
|
|
366
|
+
controller.dispose();
|
|
367
|
+
throw cause;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { MarivoEnvironmentError } from "../environment/errors.js";
|
|
2
|
+
import { resolveMarivoBridgeSource } from "../environment/source.js";
|
|
3
|
+
import { MARIVO_HELP_INVENTORY_PROGRAM, MARIVO_HELP_PROGRAM } from "./bridge-program.js";
|
|
4
|
+
export class MarivoHelpError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
details;
|
|
7
|
+
constructor(code, message, details = {}, options) {
|
|
8
|
+
super(message, options);
|
|
9
|
+
this.name = 'MarivoHelpError';
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.details = details;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function boundedStderr(stderr) {
|
|
15
|
+
const text = stderr.toString('utf8').trim();
|
|
16
|
+
return text.length === 0 ? 'no stderr was returned' : text.slice(0, 4_000);
|
|
17
|
+
}
|
|
18
|
+
/** Identity-checked adapter for the live Marivo Help surface. */
|
|
19
|
+
export class MarivoHelpBridge {
|
|
20
|
+
binding;
|
|
21
|
+
#runner;
|
|
22
|
+
constructor(runner) {
|
|
23
|
+
this.#runner = runner;
|
|
24
|
+
this.binding = runner.binding;
|
|
25
|
+
}
|
|
26
|
+
async runTarget(target, limits, signal) {
|
|
27
|
+
return this.#run(MARIVO_HELP_PROGRAM, [target], limits, signal, `target ${JSON.stringify(target)}`);
|
|
28
|
+
}
|
|
29
|
+
async inventory(limits, signal) {
|
|
30
|
+
return this.#run(MARIVO_HELP_INVENTORY_PROGRAM, [], limits, signal, 'inventory');
|
|
31
|
+
}
|
|
32
|
+
async #run(program, args, limits, signal, subject) {
|
|
33
|
+
try {
|
|
34
|
+
const result = await this.#runner.runChecked({
|
|
35
|
+
program,
|
|
36
|
+
args,
|
|
37
|
+
limits,
|
|
38
|
+
signal,
|
|
39
|
+
});
|
|
40
|
+
if (result.exitCode !== 0) {
|
|
41
|
+
throw new MarivoHelpError('target-failed', `marivo_help ${subject} failed with exit code ${String(result.exitCode)}: ${boundedStderr(result.stderr)}`, { subject, exitCode: result.exitCode });
|
|
42
|
+
}
|
|
43
|
+
if (result.stdout.byteLength === 0) {
|
|
44
|
+
throw new MarivoHelpError('empty-help', `marivo_help ${subject} returned empty stdout`, {
|
|
45
|
+
subject,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return result.stdout;
|
|
49
|
+
}
|
|
50
|
+
catch (cause) {
|
|
51
|
+
if (cause instanceof MarivoHelpError)
|
|
52
|
+
throw cause;
|
|
53
|
+
if (cause instanceof MarivoEnvironmentError &&
|
|
54
|
+
(cause.code === 'binding-identity-mismatch' || cause.code === 'binding-failed'))
|
|
55
|
+
throw cause;
|
|
56
|
+
if (cause instanceof MarivoEnvironmentError) {
|
|
57
|
+
throw new MarivoHelpError('target-failed', `marivo_help ${subject} failed: ${cause.message}`, { subject, environmentFailureCode: cause.code }, { cause });
|
|
58
|
+
}
|
|
59
|
+
throw new MarivoHelpError('target-failed', `marivo_help ${subject} failed`, { subject }, { cause });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export function resolveMarivoHelpBridge(source) {
|
|
64
|
+
return resolveMarivoBridgeSource(source);
|
|
65
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
3
|
+
import { MarivoHelpError, resolveMarivoHelpBridge, } from "./bridge.js";
|
|
4
|
+
export { MarivoHelpError, resolveMarivoHelpBridge } from "./bridge.js";
|
|
5
|
+
export const MARIVO_HELP_TOOL_NAME = 'marivo_help';
|
|
6
|
+
export const DEFAULT_MARIVO_HELP_LIMITS = Object.freeze({
|
|
7
|
+
maxTargets: 8,
|
|
8
|
+
maxTargetChars: 256,
|
|
9
|
+
maxTotalTargetChars: 2_048,
|
|
10
|
+
targetTimeoutMs: 30_000,
|
|
11
|
+
focusedStdoutMaxBytes: 262_144,
|
|
12
|
+
inventoryStdoutMaxBytes: 1_048_576,
|
|
13
|
+
stderrMaxBytes: 65_536,
|
|
14
|
+
combinedStdoutMaxBytes: 1_048_576,
|
|
15
|
+
toolTimeoutMs: 245_000,
|
|
16
|
+
});
|
|
17
|
+
function positiveInteger(name, value) {
|
|
18
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
19
|
+
throw new TypeError(`marivo_help ${name} must be a positive safe integer`);
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
export function resolveMarivoHelpLimits(overrides = {}) {
|
|
24
|
+
const merged = { ...DEFAULT_MARIVO_HELP_LIMITS, ...overrides };
|
|
25
|
+
return Object.freeze({
|
|
26
|
+
maxTargets: positiveInteger('maxTargets', merged.maxTargets),
|
|
27
|
+
maxTargetChars: positiveInteger('maxTargetChars', merged.maxTargetChars),
|
|
28
|
+
maxTotalTargetChars: positiveInteger('maxTotalTargetChars', merged.maxTotalTargetChars),
|
|
29
|
+
targetTimeoutMs: positiveInteger('targetTimeoutMs', merged.targetTimeoutMs),
|
|
30
|
+
focusedStdoutMaxBytes: positiveInteger('focusedStdoutMaxBytes', merged.focusedStdoutMaxBytes),
|
|
31
|
+
inventoryStdoutMaxBytes: positiveInteger('inventoryStdoutMaxBytes', merged.inventoryStdoutMaxBytes),
|
|
32
|
+
stderrMaxBytes: positiveInteger('stderrMaxBytes', merged.stderrMaxBytes),
|
|
33
|
+
combinedStdoutMaxBytes: positiveInteger('combinedStdoutMaxBytes', merged.combinedStdoutMaxBytes),
|
|
34
|
+
toolTimeoutMs: positiveInteger('toolTimeoutMs', merged.toolTimeoutMs),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/** Validate only mechanical resource boundaries and deduplicate in first-seen order. */
|
|
38
|
+
export function normalizeHelpTargets(input, limits) {
|
|
39
|
+
if (!Array.isArray(input)) {
|
|
40
|
+
throw new MarivoHelpError('invalid-request', 'marivo_help targets must be an array');
|
|
41
|
+
}
|
|
42
|
+
if (input.length === 0) {
|
|
43
|
+
throw new MarivoHelpError('invalid-request', 'marivo_help targets must contain at least one target');
|
|
44
|
+
}
|
|
45
|
+
if (input.length > limits.maxTargets) {
|
|
46
|
+
throw new MarivoHelpError('invalid-request', `marivo_help accepts at most ${limits.maxTargets} targets per call`, { maxTargets: limits.maxTargets, received: input.length });
|
|
47
|
+
}
|
|
48
|
+
const targets = [];
|
|
49
|
+
const seen = new Set();
|
|
50
|
+
let totalChars = 0;
|
|
51
|
+
for (const [index, target] of input.entries()) {
|
|
52
|
+
if (typeof target !== 'string' || target.length === 0) {
|
|
53
|
+
throw new MarivoHelpError('invalid-request', `marivo_help target at index ${index} must be a non-empty string`, { index });
|
|
54
|
+
}
|
|
55
|
+
if (target.length > limits.maxTargetChars) {
|
|
56
|
+
throw new MarivoHelpError('invalid-request', `marivo_help target at index ${index} exceeds ${limits.maxTargetChars} characters`, { index, maxTargetChars: limits.maxTargetChars, received: target.length });
|
|
57
|
+
}
|
|
58
|
+
totalChars += target.length;
|
|
59
|
+
if (totalChars > limits.maxTotalTargetChars) {
|
|
60
|
+
throw new MarivoHelpError('invalid-request', `marivo_help targets exceed ${limits.maxTotalTargetChars} total characters`, { maxTotalTargetChars: limits.maxTotalTargetChars, received: totalChars });
|
|
61
|
+
}
|
|
62
|
+
if (!seen.has(target)) {
|
|
63
|
+
seen.add(target);
|
|
64
|
+
targets.push(target);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return targets;
|
|
68
|
+
}
|
|
69
|
+
/** Read the current raw canonical inventory without parsing or caching it. */
|
|
70
|
+
export async function loadTargetInventory(bridge, options = {}) {
|
|
71
|
+
const limits = resolveMarivoHelpLimits(options.limits);
|
|
72
|
+
const stdout = await bridge.inventory({
|
|
73
|
+
timeoutMs: limits.targetTimeoutMs,
|
|
74
|
+
stdoutMaxBytes: limits.inventoryStdoutMaxBytes,
|
|
75
|
+
stderrMaxBytes: limits.stderrMaxBytes,
|
|
76
|
+
}, options.signal);
|
|
77
|
+
return stdout.toString('utf8');
|
|
78
|
+
}
|
|
79
|
+
export function marivoHelpBodyDigest(body) {
|
|
80
|
+
return createHash('sha256').update(body).digest('hex');
|
|
81
|
+
}
|
|
82
|
+
export function renderMarivoHelpValue(value) {
|
|
83
|
+
const header = `Marivo version: ${value.environment.version}`;
|
|
84
|
+
return [
|
|
85
|
+
header,
|
|
86
|
+
...value.targets.map((item) => {
|
|
87
|
+
if (item.delivery === 'already-visible') {
|
|
88
|
+
return `Target: ${item.target}\nCurrent help is already visible in this prompt.`;
|
|
89
|
+
}
|
|
90
|
+
return `Target: ${item.target}\n${item.body}`;
|
|
91
|
+
}),
|
|
92
|
+
].join('\n\n');
|
|
93
|
+
}
|
|
94
|
+
function helpPresentationKey(value) {
|
|
95
|
+
return JSON.stringify([
|
|
96
|
+
value.environment.version,
|
|
97
|
+
value.targets.map((item) => [item.target, item.bodyDigest, item.delivery]),
|
|
98
|
+
]);
|
|
99
|
+
}
|
|
100
|
+
/** Read one all-or-nothing batch from the bound live Marivo help surface. */
|
|
101
|
+
export async function readMarivoHelpTargets(bridge, rawTargets, options = {}) {
|
|
102
|
+
const limits = resolveMarivoHelpLimits(options.limits);
|
|
103
|
+
const targets = normalizeHelpTargets(rawTargets, limits);
|
|
104
|
+
const environmentIdentity = {
|
|
105
|
+
version: bridge.binding.marivoVersion,
|
|
106
|
+
fingerprint: bridge.binding.fingerprint,
|
|
107
|
+
};
|
|
108
|
+
const results = [];
|
|
109
|
+
let combinedBytes = 0;
|
|
110
|
+
for (const target of targets) {
|
|
111
|
+
const stdout = await bridge.runTarget(target, {
|
|
112
|
+
timeoutMs: limits.targetTimeoutMs,
|
|
113
|
+
stdoutMaxBytes: limits.focusedStdoutMaxBytes,
|
|
114
|
+
stderrMaxBytes: limits.stderrMaxBytes,
|
|
115
|
+
}, options.signal);
|
|
116
|
+
combinedBytes += stdout.byteLength;
|
|
117
|
+
if (combinedBytes > limits.combinedStdoutMaxBytes) {
|
|
118
|
+
throw new MarivoHelpError('combined-output-limit', `marivo_help combined stdout exceeds ${limits.combinedStdoutMaxBytes} bytes`, { maxBytes: limits.combinedStdoutMaxBytes, observedBytes: combinedBytes, target });
|
|
119
|
+
}
|
|
120
|
+
const body = stdout.toString('utf8');
|
|
121
|
+
const bodyDigest = marivoHelpBodyDigest(body);
|
|
122
|
+
results.push({
|
|
123
|
+
target,
|
|
124
|
+
body,
|
|
125
|
+
bodyDigest,
|
|
126
|
+
delivery: options.resolveDelivery?.({
|
|
127
|
+
environmentFingerprint: environmentIdentity.fingerprint,
|
|
128
|
+
target,
|
|
129
|
+
bodyDigest,
|
|
130
|
+
}) ?? 'delivered',
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return { environment: environmentIdentity, targets: results };
|
|
134
|
+
}
|
|
135
|
+
/** Build the native Harness ToolDefinition without registering Agent activation behavior. */
|
|
136
|
+
export function createMarivoHelpTool(bridgeSource, limitOverrides = {}, resolveDelivery) {
|
|
137
|
+
const limits = resolveMarivoHelpLimits(limitOverrides);
|
|
138
|
+
const presentations = new Map();
|
|
139
|
+
return defineTool({
|
|
140
|
+
name: MARIVO_HELP_TOOL_NAME,
|
|
141
|
+
description: 'Request current live Marivo API help for one or more canonical string targets from the exact shared Runtime.',
|
|
142
|
+
parameters: {
|
|
143
|
+
targets: {
|
|
144
|
+
type: 'array',
|
|
145
|
+
required: true,
|
|
146
|
+
description: 'One or more canonical Marivo help targets.',
|
|
147
|
+
items: { type: 'string' },
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
output: {
|
|
151
|
+
schema: {
|
|
152
|
+
type: 'object',
|
|
153
|
+
additionalProperties: false,
|
|
154
|
+
properties: {
|
|
155
|
+
environment: {
|
|
156
|
+
type: 'object',
|
|
157
|
+
required: true,
|
|
158
|
+
additionalProperties: false,
|
|
159
|
+
properties: {
|
|
160
|
+
version: { type: 'string', required: true },
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
targets: {
|
|
164
|
+
type: 'array',
|
|
165
|
+
required: true,
|
|
166
|
+
items: {
|
|
167
|
+
type: 'object',
|
|
168
|
+
additionalProperties: false,
|
|
169
|
+
properties: {
|
|
170
|
+
target: { type: 'string', required: true },
|
|
171
|
+
body: { type: 'string', required: true },
|
|
172
|
+
bodyDigest: { type: 'string', required: true },
|
|
173
|
+
delivery: {
|
|
174
|
+
type: 'string',
|
|
175
|
+
required: true,
|
|
176
|
+
enum: ['delivered', 'already-visible', 'replacement'],
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
render: (_args, value) => [{ type: 'text', text: renderMarivoHelpValue(value) }],
|
|
184
|
+
presentationMeta: (_args, value) => {
|
|
185
|
+
const key = helpPresentationKey(value);
|
|
186
|
+
const pending = presentations.get(key);
|
|
187
|
+
const environmentFingerprint = pending?.shift();
|
|
188
|
+
if (pending?.length === 0)
|
|
189
|
+
presentations.delete(key);
|
|
190
|
+
if (environmentFingerprint === undefined) {
|
|
191
|
+
throw new Error('marivo_help presentation identity is unavailable');
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
kind: 'marivo-help-disclosure',
|
|
195
|
+
environmentFingerprint,
|
|
196
|
+
targets: value.targets.map((item) => ({
|
|
197
|
+
target: item.target,
|
|
198
|
+
bodyDigest: item.bodyDigest,
|
|
199
|
+
delivery: item.delivery,
|
|
200
|
+
})),
|
|
201
|
+
};
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
timeoutMs: limits.toolTimeoutMs,
|
|
205
|
+
async execute(args, exec) {
|
|
206
|
+
const bridge = await resolveMarivoHelpBridge(bridgeSource);
|
|
207
|
+
const internal = await readMarivoHelpTargets(bridge, args.targets, {
|
|
208
|
+
limits,
|
|
209
|
+
signal: exec.signal,
|
|
210
|
+
resolveDelivery,
|
|
211
|
+
});
|
|
212
|
+
const value = {
|
|
213
|
+
environment: { version: internal.environment.version },
|
|
214
|
+
targets: internal.targets,
|
|
215
|
+
};
|
|
216
|
+
const key = helpPresentationKey(value);
|
|
217
|
+
const pending = presentations.get(key) ?? [];
|
|
218
|
+
pending.push(internal.environment.fingerprint);
|
|
219
|
+
presentations.set(key, pending);
|
|
220
|
+
return value;
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
/** Register the focused Help Tool; skill-triggered root disclosure is composed separately. */
|
|
225
|
+
export function registerMarivoHelpTool(ctx, bridgeSource, limits = {}, resolveDelivery) {
|
|
226
|
+
return ctx.tools.register(createMarivoHelpTool(bridgeSource, limits, resolveDelivery));
|
|
227
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { installMarivoDisclosure, MARIVO_ROOT_HELP_TARGETS, MarivoDisclosureController, MarivoDisclosureError, } from "./activation.js";
|
|
2
|
+
export { MarivoHelpBridge } from "./bridge.js";
|
|
3
|
+
export { createMarivoHelpTool, DEFAULT_MARIVO_HELP_LIMITS, loadTargetInventory, MARIVO_HELP_TOOL_NAME, MarivoHelpError, marivoHelpBodyDigest, normalizeHelpTargets, readMarivoHelpTargets, registerMarivoHelpTool, renderMarivoHelpValue, resolveMarivoHelpBridge, resolveMarivoHelpLimits, } from "./help.js";
|