@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.
Files changed (212) hide show
  1. package/README.md +136 -99
  2. package/cordis.patch.yml +1 -1
  3. package/lib/bin/environment.js +8 -6
  4. package/lib/client/credentials/changes.js +74 -0
  5. package/lib/client/credentials/create-datasource.js +126 -22
  6. package/lib/client/credentials/credential-draft.js +42 -0
  7. package/lib/client/credentials/defaults.js +11 -0
  8. package/lib/client/credentials/descriptions.js +46 -0
  9. package/lib/client/credentials/install.js +209 -81
  10. package/lib/client/credentials/model.js +323 -51
  11. package/lib/client/credentials/report-publishing.js +96 -0
  12. package/lib/client/credentials/styles.js +13 -9
  13. package/lib/client/i18n/context.js +22 -0
  14. package/lib/client/i18n/copy.js +57 -0
  15. package/lib/client/i18n/credentials.js +475 -0
  16. package/lib/client/i18n/host.js +27 -0
  17. package/lib/client/i18n/navigation.js +41 -0
  18. package/lib/client/i18n/presentation.js +577 -0
  19. package/lib/client/i18n/python.js +36 -0
  20. package/lib/client/i18n/semantic.js +441 -0
  21. package/lib/client/input-context.js +21 -0
  22. package/lib/client/python-tool/card.js +35 -0
  23. package/lib/client/python-tool/execution-error.js +35 -0
  24. package/lib/client/python-tool/install.js +9 -0
  25. package/lib/client/python-tool/model.js +101 -0
  26. package/lib/client/right-tabs/install.js +535 -0
  27. package/lib/client/right-tabs/live-delivery.js +58 -0
  28. package/lib/client/right-tabs/navigation.js +47 -0
  29. package/lib/client/right-tabs/page.js +178 -0
  30. package/lib/client/right-tabs/styles.js +11 -0
  31. package/lib/client/rpc.js +5 -0
  32. package/lib/client/semantic-reference.js +10 -0
  33. package/lib/client/workspace-header-action.js +14 -1
  34. package/lib/client/workspace-shortcuts.js +26 -0
  35. package/lib/client.js +365 -242
  36. package/lib/compatibility.js +1 -1
  37. package/lib/datasource/authoring.js +43 -4
  38. package/lib/datasource/bridge-programs.js +23 -20
  39. package/lib/datasource/bridge.js +46 -3
  40. package/lib/datasource/changes-contract.js +41 -0
  41. package/lib/datasource/changes.js +49 -0
  42. package/lib/datasource/configure.js +39 -0
  43. package/lib/datasource/defaults.js +25 -0
  44. package/lib/datasource/index.js +2 -1
  45. package/lib/datasource/python-options.js +21 -0
  46. package/lib/datasource/python.js +116 -25
  47. package/lib/datasource/rpc.js +75 -13
  48. package/lib/datasource/service.js +286 -11
  49. package/lib/datasource/test.js +2 -1
  50. package/lib/disclosure/activation.js +19 -7
  51. package/lib/disclosure/execution-guidance.js +52 -0
  52. package/lib/disclosure/help.js +4 -1
  53. package/lib/environment/runtime.js +35 -34
  54. package/lib/environment/workspace.js +18 -1
  55. package/lib/lifecycle.js +33 -0
  56. package/lib/plugin-agents.js +154 -0
  57. package/lib/plugin.js +94 -172
  58. package/lib/presentation/asset.js +34 -0
  59. package/lib/presentation/assets/portable.js +14 -14
  60. package/lib/presentation/assets/static.js +344 -73
  61. package/lib/presentation/build/index.js +3 -2
  62. package/lib/presentation/commit.js +19 -12
  63. package/lib/presentation/contracts/asset-path.js +9 -0
  64. package/lib/presentation/contracts/catalog.js +62 -0
  65. package/lib/presentation/contracts/charts.js +0 -23
  66. package/lib/presentation/contracts/index.js +17 -5
  67. package/lib/presentation/contracts/types.js +1 -1
  68. package/lib/presentation/delivery.js +45 -30
  69. package/lib/presentation/export-html.js +214 -0
  70. package/lib/presentation/files.js +4 -5
  71. package/lib/presentation/projection/index.js +2 -1
  72. package/lib/presentation/projection/program.js +2 -9
  73. package/lib/presentation/projection/sql-code.js +13 -13
  74. package/lib/presentation/reports.js +114 -17
  75. package/lib/presentation/rpc.js +96 -58
  76. package/lib/presentation/tool.js +6 -10
  77. package/lib/report-publishing/adapters.js +111 -0
  78. package/lib/report-publishing/config.js +69 -0
  79. package/lib/report-publishing/service.js +180 -0
  80. package/lib/rpc.js +49 -0
  81. package/lib/semantic-browser/contracts.js +17 -0
  82. package/lib/semantic-browser/program.js +10 -0
  83. package/lib/semantic-browser/service.js +40 -6
  84. package/lib/semantic-reference/bridge.js +10 -1
  85. package/lib/semantic-reference/contracts.js +20 -6
  86. package/lib/semantic-reference/environment.js +56 -0
  87. package/lib/semantic-reference/labels.js +16 -0
  88. package/lib/semantic-reference/rpc.js +39 -18
  89. package/lib/semantic-reference/search.js +5 -4
  90. package/lib/semantic-reference/usage.js +12 -3
  91. package/lib/tool-lifecycle.js +22 -0
  92. package/lib/types/client/credentials/changes.d.ts +9 -0
  93. package/lib/types/client/credentials/create-datasource.d.ts +3 -1
  94. package/lib/types/client/credentials/credential-draft.d.ts +14 -0
  95. package/lib/types/client/credentials/defaults.d.ts +2 -0
  96. package/lib/types/client/credentials/descriptions.d.ts +1 -0
  97. package/lib/types/client/credentials/install.d.ts +19 -1
  98. package/lib/types/client/credentials/model.d.ts +31 -11
  99. package/lib/types/client/credentials/report-publishing.d.ts +5 -0
  100. package/lib/types/client/credentials/styles.d.ts +1 -1
  101. package/lib/types/client/i18n/context.d.ts +17 -0
  102. package/lib/types/client/i18n/copy.d.ts +806 -0
  103. package/lib/types/client/i18n/credentials.d.ts +475 -0
  104. package/lib/types/client/i18n/host.d.ts +16 -0
  105. package/lib/types/client/i18n/navigation.d.ts +41 -0
  106. package/lib/types/client/i18n/presentation.d.ts +577 -0
  107. package/lib/types/client/i18n/python.d.ts +19 -0
  108. package/lib/types/client/i18n/semantic.d.ts +441 -0
  109. package/lib/types/client/input-context.d.ts +19 -0
  110. package/lib/types/client/presentation/ask-dsh.d.ts +4 -8
  111. package/lib/types/client/presentation/catalog-model.d.ts +24 -0
  112. package/lib/types/client/presentation/catalog.d.ts +20 -0
  113. package/lib/types/client/presentation/chart-tooltip.d.ts +2 -2
  114. package/lib/types/client/presentation/context-reference.d.ts +12 -0
  115. package/lib/types/client/presentation/copy-context.d.ts +2 -2
  116. package/lib/types/client/presentation/delivery-model.d.ts +27 -6
  117. package/lib/types/client/presentation/delivery.d.ts +8 -7
  118. package/lib/types/client/presentation/download.d.ts +2 -0
  119. package/lib/types/client/presentation/export-menu.d.ts +26 -0
  120. package/lib/types/client/presentation/export-view.d.ts +63 -0
  121. package/lib/types/client/presentation/host-entry.d.ts +8 -2
  122. package/lib/types/client/presentation/icons.d.ts +1 -1
  123. package/lib/types/client/presentation/model.d.ts +14 -15
  124. package/lib/types/client/presentation/reader.d.ts +15 -2
  125. package/lib/types/client/presentation/reference-source.d.ts +9 -0
  126. package/lib/types/client/presentation/source-code-model.d.ts +2 -1
  127. package/lib/types/client/presentation/source-code.d.ts +0 -5
  128. package/lib/types/client/presentation/source-dialog.d.ts +1 -2
  129. package/lib/types/client/presentation/sources.d.ts +5 -5
  130. package/lib/types/client/presentation/styles.d.ts +1 -1
  131. package/lib/types/client/presentation/table.d.ts +5 -1
  132. package/lib/types/client/python-tool/card.d.ts +2 -0
  133. package/lib/types/client/python-tool/execution-error.d.ts +3 -0
  134. package/lib/types/client/python-tool/install.d.ts +2 -0
  135. package/lib/types/client/python-tool/model.d.ts +20 -0
  136. package/lib/types/client/right-tabs/install.d.ts +18 -0
  137. package/lib/types/client/right-tabs/live-delivery.d.ts +8 -0
  138. package/lib/types/client/right-tabs/navigation.d.ts +17 -0
  139. package/lib/types/client/right-tabs/page.d.ts +36 -0
  140. package/lib/types/client/right-tabs/styles.d.ts +2 -0
  141. package/lib/types/client/rpc.d.ts +3 -0
  142. package/lib/types/client/semantic-reference.d.ts +4 -0
  143. package/lib/types/client/workspace-header-action.d.ts +1 -1
  144. package/lib/types/client/workspace-shortcuts.d.ts +15 -0
  145. package/lib/types/client.d.ts +6 -3
  146. package/lib/types/compatibility.d.ts +1 -1
  147. package/lib/types/datasource/authoring.d.ts +10 -0
  148. package/lib/types/datasource/bridge.d.ts +23 -2
  149. package/lib/types/datasource/changes-contract.d.ts +18 -0
  150. package/lib/types/datasource/changes.d.ts +15 -0
  151. package/lib/types/datasource/configure.d.ts +7 -0
  152. package/lib/types/datasource/defaults.d.ts +7 -0
  153. package/lib/types/datasource/index.d.ts +6 -1
  154. package/lib/types/datasource/python-options.d.ts +9 -0
  155. package/lib/types/datasource/python.d.ts +17 -2
  156. package/lib/types/datasource/rpc.d.ts +2 -1
  157. package/lib/types/datasource/service.d.ts +48 -5
  158. package/lib/types/datasource/test.d.ts +1 -1
  159. package/lib/types/disclosure/activation.d.ts +2 -1
  160. package/lib/types/disclosure/execution-guidance.d.ts +6 -0
  161. package/lib/types/disclosure/help.d.ts +1 -1
  162. package/lib/types/environment/types.d.ts +2 -1
  163. package/lib/types/environment/workspace.d.ts +1 -0
  164. package/lib/types/lifecycle.d.ts +8 -0
  165. package/lib/types/plugin-agents.d.ts +27 -0
  166. package/lib/types/plugin.d.ts +12 -28
  167. package/lib/types/presentation/asset.d.ts +6 -0
  168. package/lib/types/presentation/build/index.d.ts +7 -2
  169. package/lib/types/presentation/commit.d.ts +1 -1
  170. package/lib/types/presentation/contracts/asset-path.d.ts +2 -0
  171. package/lib/types/presentation/contracts/catalog.d.ts +27 -0
  172. package/lib/types/presentation/contracts/charts.d.ts +0 -2
  173. package/lib/types/presentation/contracts/types.d.ts +7 -4
  174. package/lib/types/presentation/delivery.d.ts +1 -1
  175. package/lib/types/presentation/export-html.d.ts +13 -0
  176. package/lib/types/presentation/reports.d.ts +6 -1
  177. package/lib/types/presentation/rpc.d.ts +3 -2
  178. package/lib/types/presentation/tool.d.ts +2 -2
  179. package/lib/types/report-publishing/adapters.d.ts +6 -0
  180. package/lib/types/report-publishing/config.d.ts +22 -0
  181. package/lib/types/report-publishing/service.d.ts +68 -0
  182. package/lib/types/rpc.d.ts +3 -0
  183. package/lib/types/semantic-browser/contracts.d.ts +7 -0
  184. package/lib/types/semantic-browser/service.d.ts +7 -1
  185. package/lib/types/semantic-reference/bridge.d.ts +1 -0
  186. package/lib/types/semantic-reference/contracts.d.ts +5 -2
  187. package/lib/types/semantic-reference/environment.d.ts +22 -0
  188. package/lib/types/semantic-reference/labels.d.ts +16 -0
  189. package/lib/types/semantic-reference/rpc.d.ts +1 -1
  190. package/lib/types/semantic-reference/search.d.ts +1 -1
  191. package/lib/types/tool-lifecycle.d.ts +4 -0
  192. package/lib/types/workspace-identity.d.ts +6 -0
  193. package/lib/workspace-identity.js +9 -0
  194. package/package.json +83 -61
  195. package/python/presentation-kit/dist/dsh_data_analysis_presentation_kit-1.1.0-py3-none-any.whl +0 -0
  196. package/skills/dsh-data-analysis-files/SKILL.md +32 -0
  197. package/skills/dsh-data-analysis-files/references/examples.md +93 -0
  198. package/skills/dsh-data-analysis-presentation/SKILL.md +10 -6
  199. package/skills/dsh-data-analysis-presentation/references/examples/artifact.draft.json +13 -4
  200. package/skills/dsh-data-analysis-presentation/references/examples/charts.draft.json +3 -2
  201. package/skills/dsh-data-analysis-presentation/references/examples/comparison.draft.json +3 -2
  202. package/skills/dsh-data-analysis-presentation/references/examples/computed-sources.draft.json +12 -5
  203. package/skills/dsh-data-analysis-presentation/references/examples/computed.draft.json +3 -2
  204. package/skills/dsh-data-analysis-presentation/references/examples/interaction.draft.json +3 -2
  205. package/skills/dsh-data-analysis-presentation/references/examples/source-only.draft.json +12 -4
  206. package/skills/dsh-data-analysis-presentation/references/examples.md +3 -1
  207. package/skills/dsh-data-analysis-presentation/references/export-html.md +7 -0
  208. package/skills/dsh-data-analysis-presentation/references/interaction.md +1 -1
  209. package/skills/dsh-data-analysis-presentation/references/narrative.md +1 -1
  210. package/skills/dsh-data-analysis-presentation/references/publishing.md +3 -0
  211. package/skills/dsh-data-analysis-presentation/references/schema.md +25 -1
  212. package/lib/types/client/presentation/install.d.ts +0 -15
@@ -1,5 +1,6 @@
1
1
  import { createUserMessage } from '@deepseek-ai/dsh-llm';
2
2
  import { scopeParentOf } from '@deepseek-ai/dsh-scope';
3
+ import { finishCleanup, PendingTasks } from "../lifecycle.js";
3
4
  import { MARIVO_HELP_TOOL_NAME, readMarivoHelpTargets, registerMarivoHelpTool, renderMarivoHelpValue, resolveMarivoHelpBridge, } from "./help.js";
4
5
  const SKILL_TOOL_NAME = 'skill';
5
6
  export const MARIVO_ROOT_HELP_TARGETS = Object.freeze({
@@ -119,6 +120,8 @@ export class MarivoDisclosureController {
119
120
  #visibleHelp = new Map();
120
121
  #lifecycleAbort = new AbortController();
121
122
  #disposers = [];
123
+ #tasks = new PendingTasks();
124
+ #closing;
122
125
  #disposed = false;
123
126
  #telemetry = { activations: [], rootHelp: [], failures: [] };
124
127
  constructor(agent, bridgeSource, options = {}) {
@@ -181,7 +184,10 @@ export class MarivoDisclosureController {
181
184
  return 'already-visible';
182
185
  return visible === undefined ? 'delivered' : 'replacement';
183
186
  }
184
- async prepareStep(messages, signal) {
187
+ prepareStep(messages, signal) {
188
+ return this.#tasks.track(this.#prepareStep(messages, signal));
189
+ }
190
+ async #prepareStep(messages, signal) {
185
191
  if (this.#disposed)
186
192
  throw this.#lifecycleAbort.signal.reason;
187
193
  this.#observeExplicitInvocations(messages);
@@ -273,13 +279,16 @@ export class MarivoDisclosureController {
273
279
  return;
274
280
  this.#disposed = true;
275
281
  this.#lifecycleAbort.abort(new Error('Marivo disclosure controller disposed'));
276
- for (const disposer of this.#disposers.reverse())
277
- disposer();
282
+ this.#closing = finishCleanup([...this.#disposers.reverse(), () => this.#tasks.drain()]);
278
283
  this.#disposers = [];
279
284
  this.#activeSkills.clear();
280
285
  this.#pendingSkills.clear();
281
286
  this.#visibleHelp.clear();
282
287
  }
288
+ close() {
289
+ this.dispose();
290
+ return this.#closing;
291
+ }
283
292
  #inheritedSkillToolVisible() {
284
293
  const inheritedSkillTool = this.agent.ctx.tools.get(SKILL_TOOL_NAME, scopeParentOf(this.agent));
285
294
  return (inheritedSkillTool !== undefined &&
@@ -294,7 +303,7 @@ export class MarivoDisclosureController {
294
303
  }
295
304
  }
296
305
  #restoreActiveSkills() {
297
- for (const event of this.agent.session.events) {
306
+ for (const event of this.agent.session.snapshotEvents()) {
298
307
  if (event.type !== 'user/message')
299
308
  continue;
300
309
  const disclosure = rootHelpSource(event.data.source);
@@ -309,7 +318,7 @@ export class MarivoDisclosureController {
309
318
  #refreshVisibleHelp(messages) {
310
319
  this.#visibleHelp.clear();
311
320
  const visible = new Set(this.agent.session.surface.nodes);
312
- for (const event of this.agent.session.events) {
321
+ for (const event of this.agent.session.snapshotEvents()) {
313
322
  if (!visible.has(event.seq))
314
323
  continue;
315
324
  if (event.type === 'user/message') {
@@ -340,7 +349,9 @@ export class MarivoDisclosureController {
340
349
  }
341
350
  }
342
351
  #hasHistoricalDisclosure(skill) {
343
- return this.agent.session.events.some((event) => event.type === 'user/message' && rootHelpSource(event.data.source)?.skill === skill);
352
+ return this.agent.session
353
+ .snapshotEvents()
354
+ .some((event) => event.type === 'user/message' && rootHelpSource(event.data.source)?.skill === skill);
344
355
  }
345
356
  }
346
357
  /** Install skill-triggered Marivo live-help disclosure for one Agent scope. */
@@ -356,9 +367,10 @@ export function installMarivoDisclosure(_ctx, agent, bridgeSource, options = {})
356
367
  if (decision.kind === 'reject')
357
368
  return decision;
358
369
  const injections = await controller.prepareStep(decision.messages, signal);
370
+ signal.throwIfAborted();
359
371
  return injections.length === 0
360
372
  ? decision
361
- : { kind: 'enter', messages: [...decision.messages, ...injections] };
373
+ : { ...decision, messages: [...decision.messages, ...injections] };
362
374
  }, true));
363
375
  return controller;
364
376
  }
@@ -0,0 +1,52 @@
1
+ import { createUserMessage } from '@deepseek-ai/dsh-llm';
2
+ export const MARIVO_DATASOURCE_CREDENTIAL_PROMPT = [
3
+ 'When a requested table has no usable configured datasource, call marivo_datasource_configure with mode=create and a brief reason. For a datasource needing connection configuration repair, use mode=edit and its exact name. The user configures it in the owning Web right tab; do not request secrets in chat or write connection configuration on the user behalf. Only status=ok allows continuing: rediscover the datasource and verify the requested table and read access before analysis. Other statuses are not connection success. A provided local file may be analyzed directly when appropriate; do not invent a remote datasource requirement.',
4
+ 'DSH Credentials owns Marivo datasource secrets. Never request values in chat, read credential files or ~/.marivo/secrets.toml, or write secrets to scripts, arguments, environment variables, reports or logs.',
5
+ 'Use marivo_datasource_test after datasource changes, credential rotation, connection failures, or explicit user requests. Missing credentials wait for the Web form only while the original call remains alive.',
6
+ 'Execute analysis, metadata inspection and semantic data reads through marivo_python. Declare all exact Marivo datasource names this execution may access, including datasources without passwords. Local file analysis with pandas or native DuckDB uses datasources: [] when no Marivo datasource is accessed.',
7
+ 'marivo_python waits for all missing credentials and validates the bound Workspace and datasource identities before taking one fresh snapshot and starting user code once. Configured credentials need no extra connection test. Ordinary Shell receives no datasource secret.',
8
+ 'marivo_python installs credential_scope before user code. When using Marivo Session/reader objects, create or resume them inside that execution, and close Sessions in finally. Do not replace the resolver, read SecretValue contents, or bypass Host scope with environment/cache configuration.',
9
+ "Each marivo_python call uses a separate process. Closing releases that process's Session resources; it does not end the analytical question or delete durable Artifacts. When continuing a persisted Marivo analysis, reuse its Session identity across calls.",
10
+ 'Configured credentials do not imply a valid connection or query permissions. Preserve real failures; never automatically replay a script with possible side effects.',
11
+ 'Session lifetime pattern inside one marivo_python call (create/resume using current Runtime Help):\nsession = ...\ntry:\n ... # analysis using this Session\nfinally:\n session.close()',
12
+ ].join('\n');
13
+ export const MARIVO_ANALYSIS_CLOSEOUT_PROMPT = [
14
+ 'Analysis semantics and execution contracts come from the activated Runtime Skill and current Help.',
15
+ 'Before completing any analysis, including a text-only answer, check each user question and each requested comparison scope against its evidence. Keep incomplete branches explicit; preserve comparison sides, direction and denominator. Match conclusion strength to evidence, and reflect limitations in the claims themselves.',
16
+ ].join(' ');
17
+ /** Bridge rules activated after system prompt assembly, without changing their wording. */
18
+ function executionGuidanceSupplement(before, after) {
19
+ const rules = [];
20
+ if (before.length === 0 && after.length > 0)
21
+ rules.push(MARIVO_DATASOURCE_CREDENTIAL_PROMPT);
22
+ if (!before.includes('marivo-analysis') && after.includes('marivo-analysis'))
23
+ rules.push(MARIVO_ANALYSIS_CLOSEOUT_PROMPT);
24
+ if (rules.length === 0)
25
+ return [];
26
+ return [
27
+ createUserMessage({
28
+ content: [
29
+ {
30
+ type: 'text',
31
+ text: ['<marivo_execution_guidance>', ...rules, '</marivo_execution_guidance>'].join('\n\n'),
32
+ },
33
+ ],
34
+ source: { kind: 'plugin', plugin: 'dsh-data-analysis' },
35
+ }),
36
+ ];
37
+ }
38
+ /** Full-plugin seam: requires its execution tools, credentials service and system sections. */
39
+ export function installMarivoExecutionGuidance(agent, controller) {
40
+ return agent.ctx.on('agent/pre-step', async ({ signal }, next) => {
41
+ // Prepend after Help installation to wrap its successful disclosure decision.
42
+ const previouslyActive = controller.activeSkills;
43
+ const decision = await next();
44
+ if (decision.kind === 'reject')
45
+ return decision;
46
+ signal.throwIfAborted();
47
+ const guidance = executionGuidanceSupplement(previouslyActive, controller.activeSkills);
48
+ return guidance.length === 0
49
+ ? decision
50
+ : { ...decision, messages: [...decision.messages, ...guidance] };
51
+ }, true);
52
+ }
@@ -1,5 +1,6 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { defineTool } from '@deepseek-ai/dsh-tools';
3
+ import { registerMarivoTool } from "../tool-lifecycle.js";
3
4
  import { MarivoHelpError, resolveMarivoHelpBridge, } from "./bridge.js";
4
5
  export { MarivoHelpError, resolveMarivoHelpBridge } from "./bridge.js";
5
6
  export const MARIVO_HELP_TOOL_NAME = 'marivo_help';
@@ -203,12 +204,14 @@ export function createMarivoHelpTool(bridgeSource, limitOverrides = {}, resolveD
203
204
  },
204
205
  timeoutMs: limits.toolTimeoutMs,
205
206
  async execute(args, exec) {
207
+ exec.signal.throwIfAborted();
206
208
  const bridge = await resolveMarivoHelpBridge(bridgeSource);
207
209
  const internal = await readMarivoHelpTargets(bridge, args.targets, {
208
210
  limits,
209
211
  signal: exec.signal,
210
212
  resolveDelivery,
211
213
  });
214
+ exec.signal.throwIfAborted();
212
215
  const value = {
213
216
  environment: { version: internal.environment.version },
214
217
  targets: internal.targets,
@@ -223,5 +226,5 @@ export function createMarivoHelpTool(bridgeSource, limitOverrides = {}, resolveD
223
226
  }
224
227
  /** Register the focused Help Tool; skill-triggered root disclosure is composed separately. */
225
228
  export function registerMarivoHelpTool(ctx, bridgeSource, limits = {}, resolveDelivery) {
226
- return ctx.tools.register(createMarivoHelpTool(bridgeSource, limits, resolveDelivery));
229
+ return registerMarivoTool(ctx, createMarivoHelpTool(bridgeSource, limits, resolveDelivery));
227
230
  }
@@ -25,6 +25,7 @@ import os
25
25
  import sys
26
26
  from importlib.metadata import distribution
27
27
  import marivo
28
+ import ibis.backends.duckdb
28
29
  from marivo.semantic.definition import SemanticDefinition
29
30
  import pandas
30
31
  import dsh_data_analysis_presentation
@@ -166,7 +167,7 @@ async function probeRuntime(runtimeRoot, executable, environment, timeoutMs, pre
166
167
  });
167
168
  const repair = administrator ? administratorRepair(canonical, presentationKitWheel) : undefined;
168
169
  if (administrator && result.exitCode !== 0) {
169
- throw new MarivoEnvironmentError('shared-runtime-package-unavailable', `Administrator Python must provide Marivo ${PINNED_MARIVO_VERSION}, pandas ${PRESENTATION_KIT_PANDAS_RANGE}, and ${PRESENTATION_KIT_DISTRIBUTION} ${PRESENTATION_KIT_VERSION}. ${repair.message}`, {
170
+ throw new MarivoEnvironmentError('shared-runtime-package-unavailable', `Administrator Python must provide Marivo ${PINNED_MARIVO_VERSION} with the duckdb extra, pandas ${PRESENTATION_KIT_PANDAS_RANGE}, and ${PRESENTATION_KIT_DISTRIBUTION} ${PRESENTATION_KIT_VERSION}. ${repair.message}`, {
170
171
  pythonExecutable: canonical,
171
172
  marivoVersion: PINNED_MARIVO_VERSION,
172
173
  pandasRange: PRESENTATION_KIT_PANDAS_RANGE,
@@ -462,50 +463,48 @@ async function backupInvalidRuntime(runtimeRoot) {
462
463
  }
463
464
  await rename(runtimeRoot, `${runtimeRoot}.invalid-${Date.now()}-${randomUUID()}`);
464
465
  }
465
- async function installManagedRuntime(runtimeRoot, uvExecutable, presentationKitWheel, environment, timeoutMs) {
466
+ async function installManagedRuntime(runtimeRoot, bootstrapPythonExecutable, presentationKitWheel, environment, timeoutMs) {
466
467
  const policy = new FixedSubprocessPolicy(runtimeRoot, environment);
467
468
  const limits = { timeoutMs, stdoutMaxBytes: 1_048_576, stderrMaxBytes: 1_048_576 };
468
- requireSuccess('install managed Python', await policy.run({
469
- executable: uvExecutable,
470
- args: ['python', 'install', SHARED_PYTHON_SPEC],
471
- limits,
472
- }));
473
- const found = requireSuccess('resolve managed Python', await policy.run({
474
- executable: uvExecutable,
475
- args: ['python', 'find', '--managed-python', SHARED_PYTHON_SPEC],
476
- limits,
477
- }));
478
- const managedPython = found.stdout.toString('utf8').trim();
479
- if (!path.isAbsolute(managedPython)) {
480
- throw new MarivoEnvironmentError('shared-runtime-install-failed', 'uv returned a non-absolute managed Python path', { managedPython });
481
- }
482
- const canonicalManagedPython = await assertExecutable(managedPython);
483
- const version = parseJsonObject('validate managed Python', await policy.run({
484
- executable: canonicalManagedPython,
485
- args: ['-c', PYTHON_VERSION_SCRIPT],
486
- limits,
487
- }));
469
+ let version;
470
+ try {
471
+ version = parseJsonObject('validate local Python', await policy.run({
472
+ executable: bootstrapPythonExecutable,
473
+ args: ['-c', PYTHON_VERSION_SCRIPT],
474
+ limits,
475
+ }));
476
+ }
477
+ catch (error) {
478
+ if (!(error instanceof MarivoEnvironmentError))
479
+ throw error;
480
+ throw new MarivoEnvironmentError('shared-runtime-install-failed', 'Could not validate local Python. Install Python 3.10+ with venv/ensurepip, or set bootstrapPythonExecutable to its absolute path.', { pythonExecutable: bootstrapPythonExecutable, causeCode: error.code });
481
+ }
488
482
  if (!Array.isArray(version.version) ||
489
483
  typeof version.version[0] !== 'number' ||
490
484
  typeof version.version[1] !== 'number' ||
491
485
  version.version[0] < 3 ||
492
486
  (version.version[0] === 3 && version.version[1] < 10)) {
493
- throw new MarivoEnvironmentError('shared-runtime-install-failed', 'uv managed Python does not satisfy Marivo >=3.10', { version: version.version, pythonExecutable: canonicalManagedPython });
487
+ throw new MarivoEnvironmentError('shared-runtime-install-failed', `Local Python does not satisfy Marivo >=${SHARED_PYTHON_SPEC}; detected ${Array.isArray(version.version) ? version.version.join('.') : 'unknown'}`, { version: version.version, pythonExecutable: bootstrapPythonExecutable });
488
+ }
489
+ if (typeof version.python_executable !== 'string' ||
490
+ !path.isAbsolute(version.python_executable)) {
491
+ throw new MarivoEnvironmentError('shared-runtime-install-failed', 'Local Python returned a non-absolute interpreter path');
494
492
  }
495
- requireSuccess('create shared virtual environment', await policy.run({
496
- executable: uvExecutable,
497
- args: ['venv', '--python', canonicalManagedPython, '--seed', path.join(runtimeRoot, '.venv')],
493
+ const canonicalPython = await assertExecutable(version.python_executable);
494
+ requireSuccess('create shared virtual environment (requires Python venv/ensurepip; install python3-venv on Debian/Ubuntu)', await policy.run({
495
+ executable: canonicalPython,
496
+ args: ['-m', 'venv', path.join(runtimeRoot, '.venv')],
498
497
  limits,
499
498
  }));
500
499
  const executable = venvPython(runtimeRoot);
501
500
  requireSuccess(`install Marivo ${PINNED_MARIVO_VERSION}`, await policy.run({
502
- executable: uvExecutable,
503
- args: ['pip', 'install', '--python', executable, '--upgrade', SHARED_MARIVO_PACKAGE_SPEC],
501
+ executable,
502
+ args: ['-m', 'pip', 'install', '--upgrade', SHARED_MARIVO_PACKAGE_SPEC],
504
503
  limits,
505
504
  }));
506
505
  requireSuccess(`install presentation kit ${PRESENTATION_KIT_VERSION}`, await policy.run({
507
- executable: uvExecutable,
508
- args: ['pip', 'install', '--python', executable, '--no-deps', presentationKitWheel],
506
+ executable,
507
+ args: ['-m', 'pip', 'install', '--no-deps', presentationKitWheel],
509
508
  limits,
510
509
  }));
511
510
  return probeRuntime(runtimeRoot, executable, environment, timeoutMs, presentationKitWheel, false, PINNED_MARIVO_VERSION);
@@ -518,9 +517,11 @@ export async function ensureSharedMarivoRuntime(config = {}, options = {}) {
518
517
  const configuredPython = config.pythonExecutable === undefined
519
518
  ? undefined
520
519
  : normalizeAbsolute('pythonExecutable', config.pythonExecutable);
521
- const uvExecutable = config.uvExecutable === undefined
522
- ? 'uv'
523
- : normalizeAbsolute('uvExecutable', config.uvExecutable);
520
+ const bootstrapPythonExecutable = config.bootstrapPythonExecutable === undefined
521
+ ? process.platform === 'win32'
522
+ ? 'python'
523
+ : 'python3'
524
+ : normalizeAbsolute('bootstrapPythonExecutable', config.bootstrapPythonExecutable);
524
525
  const timeoutMs = positiveTimeout(config.installTimeoutMs);
525
526
  const presentationKitWheel = path.normalize(path.resolve(options.presentationKitWheelPath ?? bundledPresentationKitWheel()));
526
527
  const existing = await validatedExisting(runtimeRoot, configuredPython, options.environment, timeoutMs, presentationKitWheel);
@@ -538,7 +539,7 @@ export async function ensureSharedMarivoRuntime(config = {}, options = {}) {
538
539
  await backupInvalidRuntime(runtimeRoot);
539
540
  await mkdir(runtimeRoot, { recursive: true });
540
541
  const probe = configuredPython === undefined
541
- ? await installManagedRuntime(runtimeRoot, uvExecutable, presentationKitWheel, options.environment, timeoutMs)
542
+ ? await installManagedRuntime(runtimeRoot, bootstrapPythonExecutable, presentationKitWheel, options.environment, timeoutMs)
542
543
  : await probeRuntime(runtimeRoot, configuredPython, options.environment, timeoutMs, presentationKitWheel, true, PINNED_MARIVO_VERSION);
543
544
  const skillsRoot = await syncSkills(runtimeRoot, probe.package_path);
544
545
  const record = {
@@ -1,5 +1,6 @@
1
1
  import { realpath, stat } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
+ import { finishCleanup, PendingTasks } from "../lifecycle.js";
3
4
  import { bindMarivoEnvironment } from "./binding.js";
4
5
  import { MarivoEnvironmentError } from "./errors.js";
5
6
  async function assertDirectory(target, label) {
@@ -26,13 +27,23 @@ async function canonicalWorkspaceRoot(projectRoot) {
26
27
  }
27
28
  /** Cache one zero-init Workspace binding while retaining project-specific doctor state. */
28
29
  export class MarivoWorkspaceEnvironmentManager {
30
+ #tasks = new PendingTasks();
31
+ #disposed = false;
32
+ #closing;
29
33
  runtime;
30
34
  #bindings = new Map();
31
35
  constructor(runtime) {
32
36
  this.runtime = runtime;
33
37
  }
34
- async resolve(projectRoot) {
38
+ resolve(projectRoot) {
39
+ return this.#tasks.track(this.#resolve(projectRoot));
40
+ }
41
+ async #resolve(projectRoot) {
42
+ if (this.#disposed)
43
+ throw new Error('Marivo Workspace manager disposed');
35
44
  const canonicalRoot = await canonicalWorkspaceRoot(projectRoot);
45
+ if (this.#disposed)
46
+ throw new Error('Marivo Workspace manager disposed');
36
47
  let binding = this.#bindings.get(canonicalRoot);
37
48
  if (binding === undefined) {
38
49
  binding = bindMarivoEnvironment({
@@ -48,7 +59,13 @@ export class MarivoWorkspaceEnvironmentManager {
48
59
  }
49
60
  return binding;
50
61
  }
62
+ close() {
63
+ this.dispose();
64
+ this.#closing ??= finishCleanup([() => this.#tasks.drain()]);
65
+ return this.#closing;
66
+ }
51
67
  dispose() {
68
+ this.#disposed = true;
52
69
  this.#bindings.clear();
53
70
  }
54
71
  }
@@ -0,0 +1,33 @@
1
+ /** Tracks owned work, including work whose caller stopped waiting after cancellation. */
2
+ export class PendingTasks {
3
+ #tasks = new Set();
4
+ track(task) {
5
+ const settled = task.then(() => { }, () => { });
6
+ this.#tasks.add(settled);
7
+ void settled.then(() => this.#tasks.delete(settled));
8
+ return task;
9
+ }
10
+ async drain() {
11
+ while (this.#tasks.size)
12
+ await Promise.all([...this.#tasks]);
13
+ }
14
+ }
15
+ /** Start every stop synchronously, then await every cleanup even if another one fails. */
16
+ export function finishCleanup(actions) {
17
+ const pending = actions.map((action) => {
18
+ try {
19
+ return Promise.resolve(action());
20
+ }
21
+ catch (error) {
22
+ return Promise.reject(error);
23
+ }
24
+ });
25
+ const completion = Promise.allSettled(pending).then((results) => {
26
+ const failures = results.flatMap((result) => result.status === 'rejected' ? [result.reason] : []);
27
+ if (failures.length)
28
+ throw new AggregateError(failures, 'Marivo cleanup failed');
29
+ });
30
+ // Legacy synchronous callers may ignore completion; awaiting it still reports failures.
31
+ void completion.catch(() => { });
32
+ return completion;
33
+ }
@@ -0,0 +1,154 @@
1
+ import { createMarivoBridgeSet } from "./bridges.js";
2
+ import { registerMarivoDatasourceConfigureTool } from "./datasource/configure.js";
3
+ import { registerMarivoPythonTool } from "./datasource/python.js";
4
+ import { resolvePythonOptions } from "./datasource/python-options.js";
5
+ import { MarivoCredentialService } from "./datasource/service.js";
6
+ import { registerMarivoDatasourceTestTool } from "./datasource/test.js";
7
+ import { installMarivoExecutionGuidance, MARIVO_ANALYSIS_CLOSEOUT_PROMPT, MARIVO_DATASOURCE_CREDENTIAL_PROMPT, } from "./disclosure/execution-guidance.js";
8
+ import { installMarivoDisclosure, } from "./disclosure/index.js";
9
+ import { MarivoEnvironment, resolveMarivoEnvironmentSource, } from "./environment/index.js";
10
+ import { finishCleanup, PendingTasks } from "./lifecycle.js";
11
+ import { registerMarivoExportHtmlTool } from "./presentation/export-html.js";
12
+ import { installMarivoPresentationCodeDelivery, registerMarivoPresentTool, } from "./presentation/index.js";
13
+ import { registerReportPublishTool } from "./report-publishing/adapters.js";
14
+ import { resolvePresentationWorkspace } from "./workspace-identity.js";
15
+ const PRESENTATION_PROMPT = 'Answer ordinary factual questions in text. For analysis of provided data files, load dsh-data-analysis-files; local pandas or native DuckDB work needs no Marivo semantic setup. For charts, tables, reports, dashboards or a readable source presentation, load dsh-data-analysis-presentation and deliver through marivo_present. When the user requests an offline HTML file, export the saved report with marivo_export_html, then call native present with the returned path before the final response. Use the Runtime skills and live Help when the task needs Marivo analysis or semantic authoring; existing data can be presented directly.';
16
+ /**
17
+ * Install one already-bound environment into every live or subsequently published Agent scope.
18
+ * Installation is transactional for existing Agents and is disposed with the Cordis plugin.
19
+ */
20
+ export function createMarivoAgentInstallation(ctx, environmentOrResolver, options = {}) {
21
+ const pythonOptions = resolvePythonOptions(options);
22
+ const installed = new Map();
23
+ const credentials = options.credentials ?? ctx.credentials;
24
+ if (credentials === undefined) {
25
+ throw new Error('dsh-data-analysis requires the DSH credentials service');
26
+ }
27
+ const credentialService = options.credentialService ??
28
+ new MarivoCredentialService(credentials, options.credentialInteraction);
29
+ const ownsCredentials = options.credentialService === undefined;
30
+ const retiring = new PendingTasks();
31
+ const failures = [];
32
+ const stops = [];
33
+ let active = true;
34
+ let closing;
35
+ const retire = (controller) => {
36
+ retiring.track(controller.close().catch((error) => {
37
+ failures.push(error);
38
+ }));
39
+ };
40
+ const close = () => {
41
+ if (closing)
42
+ return closing;
43
+ active = false;
44
+ closing = finishCleanup([
45
+ ...stops.reverse(),
46
+ ...[...installed.values()].map((controller) => () => retire(controller)),
47
+ ...(ownsCredentials ? [() => credentialService.close()] : []),
48
+ async () => {
49
+ await retiring.drain();
50
+ if (failures.length)
51
+ throw new AggregateError(failures, 'Marivo Agent cleanup failed');
52
+ },
53
+ ]);
54
+ installed.clear();
55
+ return closing;
56
+ };
57
+ const bridgeSets = new WeakMap();
58
+ const install = (agent) => {
59
+ if (!active || installed.has(agent))
60
+ return;
61
+ const source = environmentOrResolver instanceof MarivoEnvironment
62
+ ? environmentOrResolver
63
+ : () => Promise.resolve(environmentOrResolver(agent));
64
+ const resolveBridgeSet = async () => {
65
+ const environment = await resolveMarivoEnvironmentSource(source);
66
+ let bridges = bridgeSets.get(environment);
67
+ if (bridges === undefined) {
68
+ bridges = createMarivoBridgeSet(environment);
69
+ bridgeSets.set(environment, bridges);
70
+ }
71
+ return bridges;
72
+ };
73
+ const helpSource = options.helpBridgeSource ?? (async () => (await resolveBridgeSet()).help);
74
+ const datasourceSource = async () => (await resolveBridgeSet()).datasource;
75
+ const presentationSource = async () => {
76
+ const workspace = resolvePresentationWorkspace(ctx, String(agent.session.id));
77
+ const projection = (await resolveBridgeSet()).presentation;
78
+ const current = resolvePresentationWorkspace(ctx, String(agent.session.id));
79
+ if (workspace.id !== current.id ||
80
+ workspace.path !== current.path ||
81
+ projection.binding.projectRoot !== current.path)
82
+ throw new Error('Presentation Workspace changed or does not match the bound Runtime');
83
+ return { workspaceId: current.id, projection };
84
+ };
85
+ const controller = installMarivoDisclosure(ctx, agent, helpSource, options);
86
+ try {
87
+ controller.addDisposer(installMarivoExecutionGuidance(agent, controller));
88
+ controller.addDisposer(registerMarivoDatasourceTestTool(agent.ctx, datasourceSource, credentialService));
89
+ controller.addDisposer(registerMarivoDatasourceConfigureTool(agent.ctx, datasourceSource, credentialService));
90
+ controller.addDisposer(registerMarivoPythonTool(agent.ctx, datasourceSource, credentialService, pythonOptions));
91
+ controller.addDisposer(() => credentialService.disposeAgent(agent));
92
+ controller.addDisposer(registerMarivoPresentTool(agent.ctx, presentationSource, agent.session));
93
+ controller.addDisposer(registerMarivoExportHtmlTool(agent.ctx, () => resolvePresentationWorkspace(ctx, String(agent.session.id)), agent.session));
94
+ if (options.reportPublishing)
95
+ controller.addDisposer(registerReportPublishTool(agent.ctx, options.reportPublishing, agent.session.id));
96
+ controller.addDisposer(installMarivoPresentationCodeDelivery(agent.ctx));
97
+ controller.addDisposer(agent.ctx.systemPrompt.section({
98
+ name: 'marivo:presentation',
99
+ order: 180,
100
+ text: PRESENTATION_PROMPT,
101
+ }));
102
+ controller.addDisposer(agent.ctx.systemPrompt.section({
103
+ name: 'marivo:datasource-credentials',
104
+ order: 170,
105
+ text: () => controller.activeSkills.some((skill) => ['marivo-analysis', 'marivo-semantic'].includes(skill))
106
+ ? MARIVO_DATASOURCE_CREDENTIAL_PROMPT
107
+ : '',
108
+ }));
109
+ controller.addDisposer(agent.ctx.systemPrompt.section({
110
+ name: 'marivo:analysis-closeout',
111
+ order: 175,
112
+ text: () => controller.activeSkills.includes('marivo-analysis')
113
+ ? MARIVO_ANALYSIS_CLOSEOUT_PROMPT
114
+ : '',
115
+ }));
116
+ installed.set(agent, controller);
117
+ }
118
+ catch (error) {
119
+ retire(controller);
120
+ throw error;
121
+ }
122
+ };
123
+ return {
124
+ install() {
125
+ if (!active)
126
+ throw new Error('Marivo Agent installation disposed');
127
+ try {
128
+ stops.push(ctx.on('credentials/reference-updated', (ref) => credentialService.invalidateStorageRef(ref)));
129
+ for (const agent of ctx.agents.list())
130
+ install(agent);
131
+ stops.push(ctx.on('agent/created', ({ agent }) => {
132
+ install(agent);
133
+ }));
134
+ stops.push(ctx.on('agent/disposed', ({ agent }) => {
135
+ const controller = installed.get(agent);
136
+ if (controller)
137
+ retire(controller);
138
+ installed.delete(agent);
139
+ }));
140
+ }
141
+ catch (error) {
142
+ void close();
143
+ throw error;
144
+ }
145
+ },
146
+ close,
147
+ };
148
+ }
149
+ /** Stops synchronously; the returned Promise also waits for all owned work. */
150
+ export function installMarivoPlugin(...args) {
151
+ const installation = createMarivoAgentInstallation(...args);
152
+ installation.install();
153
+ return installation.close;
154
+ }