@chengxianglibra/dsh-data-analysis 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (303) hide show
  1. package/README.md +139 -53
  2. package/cordis.patch.yml +1 -1
  3. package/lib/bin/environment.js +9 -7
  4. package/lib/bin/presentation-lint.js +21 -0
  5. package/lib/bridges.js +2 -2
  6. package/lib/client/credentials/changes.js +74 -0
  7. package/lib/client/credentials/create-datasource.js +171 -0
  8. package/lib/client/credentials/credential-draft.js +42 -0
  9. package/lib/client/credentials/defaults.js +11 -0
  10. package/lib/client/credentials/descriptions.js +46 -0
  11. package/lib/client/credentials/install.js +319 -0
  12. package/lib/client/credentials/model.js +766 -0
  13. package/lib/client/credentials/report-publishing.js +96 -0
  14. package/lib/client/credentials/styles.js +21 -0
  15. package/lib/client/i18n/context.js +22 -0
  16. package/lib/client/i18n/copy.js +57 -0
  17. package/lib/client/i18n/credentials.js +475 -0
  18. package/lib/client/i18n/host.js +27 -0
  19. package/lib/client/i18n/navigation.js +41 -0
  20. package/lib/client/i18n/presentation.js +577 -0
  21. package/lib/client/i18n/python.js +36 -0
  22. package/lib/client/i18n/semantic.js +441 -0
  23. package/lib/client/input-context.js +21 -0
  24. package/lib/client/python-tool/card.js +35 -0
  25. package/lib/client/python-tool/execution-error.js +35 -0
  26. package/lib/client/python-tool/install.js +9 -0
  27. package/lib/client/python-tool/model.js +101 -0
  28. package/lib/client/right-tabs/install.js +535 -0
  29. package/lib/client/right-tabs/live-delivery.js +58 -0
  30. package/lib/client/right-tabs/navigation.js +47 -0
  31. package/lib/client/right-tabs/page.js +178 -0
  32. package/lib/client/right-tabs/styles.js +11 -0
  33. package/lib/client/rpc.js +5 -0
  34. package/lib/client/semantic-reference.js +10 -0
  35. package/lib/client/workspace-header-action.js +24 -0
  36. package/lib/client/workspace-shortcuts.js +26 -0
  37. package/lib/client.js +1250 -730
  38. package/lib/compatibility.js +2 -2
  39. package/lib/datasource/authoring.js +109 -0
  40. package/lib/datasource/bridge-programs.js +42 -27
  41. package/lib/datasource/bridge.js +144 -9
  42. package/lib/datasource/changes-contract.js +41 -0
  43. package/lib/datasource/changes.js +49 -0
  44. package/lib/datasource/configure.js +39 -0
  45. package/lib/datasource/defaults.js +25 -0
  46. package/lib/datasource/index.js +4 -2
  47. package/lib/datasource/python-options.js +21 -0
  48. package/lib/datasource/python.js +206 -0
  49. package/lib/datasource/resolver-program.js +112 -0
  50. package/lib/datasource/rpc.js +150 -0
  51. package/lib/datasource/service.js +962 -0
  52. package/lib/datasource/shell-env.js +2 -333
  53. package/lib/datasource/test.js +25 -51
  54. package/lib/disclosure/activation.js +19 -7
  55. package/lib/disclosure/execution-guidance.js +52 -0
  56. package/lib/disclosure/help.js +4 -1
  57. package/lib/environment/binding.js +54 -9
  58. package/lib/environment/runtime.js +120 -108
  59. package/lib/environment/subprocess.js +6 -1
  60. package/lib/environment/summary.js +3 -3
  61. package/lib/environment/workspace.js +23 -1
  62. package/lib/index.js +1 -1
  63. package/lib/lifecycle.js +33 -0
  64. package/lib/plugin-agents.js +154 -0
  65. package/lib/plugin.js +172 -133
  66. package/lib/presentation/asset.js +34 -0
  67. package/lib/presentation/assets/portable.js +606 -0
  68. package/lib/presentation/assets/static.js +1216 -0
  69. package/lib/presentation/build/index.js +72 -0
  70. package/lib/presentation/commit.js +163 -0
  71. package/lib/presentation/contracts/asset-path.js +9 -0
  72. package/lib/presentation/contracts/catalog.js +62 -0
  73. package/lib/presentation/contracts/charts.js +453 -0
  74. package/lib/presentation/contracts/editing.js +58 -0
  75. package/lib/presentation/contracts/index.js +663 -0
  76. package/lib/presentation/contracts/interaction.js +62 -0
  77. package/lib/presentation/contracts/types.js +15 -0
  78. package/lib/presentation/contracts/values.js +28 -0
  79. package/lib/presentation/delivery.js +71 -0
  80. package/lib/presentation/export-html.js +214 -0
  81. package/lib/presentation/files.js +82 -0
  82. package/lib/presentation/index.js +6 -0
  83. package/lib/presentation/lint.js +46 -0
  84. package/lib/presentation/projection/files.js +85 -0
  85. package/lib/presentation/projection/index.js +196 -0
  86. package/lib/presentation/projection/program.js +137 -0
  87. package/lib/presentation/projection/sql-code.js +79 -0
  88. package/lib/presentation/receipt.js +34 -0
  89. package/lib/presentation/reports.js +219 -0
  90. package/lib/presentation/rpc.js +186 -0
  91. package/lib/presentation/tool.js +118 -0
  92. package/lib/python-execution-contracts.js +1 -0
  93. package/lib/python-execution.js +232 -0
  94. package/lib/report-publishing/adapters.js +111 -0
  95. package/lib/report-publishing/config.js +69 -0
  96. package/lib/report-publishing/service.js +180 -0
  97. package/lib/rpc.js +49 -0
  98. package/lib/semantic-browser/contracts.js +97 -0
  99. package/lib/semantic-browser/definition.js +64 -0
  100. package/lib/semantic-browser/program.js +129 -0
  101. package/lib/semantic-browser/service.js +106 -0
  102. package/lib/semantic-reference/bridge.js +127 -0
  103. package/lib/semantic-reference/contracts.js +127 -0
  104. package/lib/semantic-reference/environment.js +56 -0
  105. package/lib/semantic-reference/index.js +3 -0
  106. package/lib/semantic-reference/labels.js +16 -0
  107. package/lib/semantic-reference/rpc.js +143 -0
  108. package/lib/semantic-reference/search.js +95 -0
  109. package/lib/semantic-reference/usage.js +152 -0
  110. package/lib/tool-lifecycle.js +22 -0
  111. package/lib/types/bridges.d.ts +2 -2
  112. package/lib/types/client/credentials/changes.d.ts +9 -0
  113. package/lib/types/client/credentials/create-datasource.d.ts +7 -0
  114. package/lib/types/client/credentials/credential-draft.d.ts +14 -0
  115. package/lib/types/client/credentials/defaults.d.ts +2 -0
  116. package/lib/types/client/credentials/descriptions.d.ts +1 -0
  117. package/lib/types/client/credentials/install.d.ts +19 -0
  118. package/lib/types/client/credentials/model.d.ts +79 -0
  119. package/lib/types/client/credentials/report-publishing.d.ts +5 -0
  120. package/lib/types/client/credentials/styles.d.ts +1 -0
  121. package/lib/types/client/i18n/context.d.ts +17 -0
  122. package/lib/types/client/i18n/copy.d.ts +806 -0
  123. package/lib/types/client/i18n/credentials.d.ts +475 -0
  124. package/lib/types/client/i18n/host.d.ts +16 -0
  125. package/lib/types/client/i18n/navigation.d.ts +41 -0
  126. package/lib/types/client/i18n/presentation.d.ts +577 -0
  127. package/lib/types/client/i18n/python.d.ts +19 -0
  128. package/lib/types/client/i18n/semantic.d.ts +441 -0
  129. package/lib/types/client/input-context.d.ts +19 -0
  130. package/lib/types/client/presentation/ask-dsh.d.ts +5 -0
  131. package/lib/types/client/presentation/catalog-model.d.ts +24 -0
  132. package/lib/types/client/presentation/catalog.d.ts +20 -0
  133. package/lib/types/client/presentation/chart-explorer.d.ts +12 -0
  134. package/lib/types/client/presentation/chart-geometry.d.ts +30 -0
  135. package/lib/types/client/presentation/chart-renderer.d.ts +10 -0
  136. package/lib/types/client/presentation/chart-specials.d.ts +10 -0
  137. package/lib/types/client/presentation/chart-styles.d.ts +1 -0
  138. package/lib/types/client/presentation/chart-tooltip.d.ts +13 -0
  139. package/lib/types/client/presentation/chart-view.d.ts +23 -0
  140. package/lib/types/client/presentation/context-reference.d.ts +12 -0
  141. package/lib/types/client/presentation/copy-context.d.ts +5 -0
  142. package/lib/types/client/presentation/delivery-model.d.ts +75 -0
  143. package/lib/types/client/presentation/delivery.d.ts +78 -0
  144. package/lib/types/client/presentation/download.d.ts +2 -0
  145. package/lib/types/client/presentation/editor-controls.d.ts +12 -0
  146. package/lib/types/client/presentation/export-menu.d.ts +26 -0
  147. package/lib/types/client/presentation/export-view.d.ts +63 -0
  148. package/lib/types/client/presentation/global-filters.d.ts +6 -0
  149. package/lib/types/client/presentation/host-entry.d.ts +18 -0
  150. package/lib/types/client/presentation/icons.d.ts +4 -0
  151. package/lib/types/client/presentation/markdown.d.ts +5 -0
  152. package/lib/types/client/presentation/model.d.ts +61 -0
  153. package/lib/types/client/presentation/reader.d.ts +24 -0
  154. package/lib/types/client/presentation/reference-source.d.ts +9 -0
  155. package/lib/types/client/presentation/source-code-model.d.ts +17 -0
  156. package/lib/types/client/presentation/source-code.d.ts +6 -0
  157. package/lib/types/client/presentation/source-dialog.d.ts +12 -0
  158. package/lib/types/client/presentation/source-facts.d.ts +20 -0
  159. package/lib/types/client/presentation/source-format.d.ts +5 -0
  160. package/lib/types/client/presentation/source-highlight.d.ts +7 -0
  161. package/lib/types/client/presentation/sources.d.ts +13 -0
  162. package/lib/types/client/presentation/styles.d.ts +1 -0
  163. package/lib/types/client/presentation/table.d.ts +16 -0
  164. package/lib/types/client/presentation/use-element-width.d.ts +5 -0
  165. package/lib/types/client/python-tool/card.d.ts +2 -0
  166. package/lib/types/client/python-tool/execution-error.d.ts +3 -0
  167. package/lib/types/client/python-tool/install.d.ts +2 -0
  168. package/lib/types/client/python-tool/model.d.ts +20 -0
  169. package/lib/types/client/right-tabs/install.d.ts +18 -0
  170. package/lib/types/client/right-tabs/live-delivery.d.ts +8 -0
  171. package/lib/types/client/right-tabs/navigation.d.ts +17 -0
  172. package/lib/types/client/right-tabs/page.d.ts +36 -0
  173. package/lib/types/client/right-tabs/styles.d.ts +2 -0
  174. package/lib/types/client/rpc.d.ts +3 -0
  175. package/lib/types/client/semantic-reference.d.ts +4 -0
  176. package/lib/types/client/workspace-header-action.d.ts +7 -0
  177. package/lib/types/client/workspace-shortcuts.d.ts +15 -0
  178. package/lib/types/client.d.ts +9 -124
  179. package/lib/types/compatibility.d.ts +3 -3
  180. package/lib/types/datasource/authoring.d.ts +35 -0
  181. package/lib/types/datasource/bridge.d.ts +43 -4
  182. package/lib/types/datasource/changes-contract.d.ts +18 -0
  183. package/lib/types/datasource/changes.d.ts +15 -0
  184. package/lib/types/datasource/configure.d.ts +7 -0
  185. package/lib/types/datasource/defaults.d.ts +7 -0
  186. package/lib/types/datasource/index.d.ts +8 -5
  187. package/lib/types/datasource/python-options.d.ts +9 -0
  188. package/lib/types/datasource/python.d.ts +19 -0
  189. package/lib/types/datasource/resolver-program.d.ts +6 -0
  190. package/lib/types/datasource/rpc.d.ts +5 -0
  191. package/lib/types/datasource/service.d.ts +155 -0
  192. package/lib/types/datasource/shell-env.d.ts +0 -29
  193. package/lib/types/datasource/test.d.ts +6 -29
  194. package/lib/types/disclosure/activation.d.ts +2 -1
  195. package/lib/types/disclosure/execution-guidance.d.ts +6 -0
  196. package/lib/types/disclosure/help.d.ts +1 -1
  197. package/lib/types/environment/binding.d.ts +5 -1
  198. package/lib/types/environment/errors.d.ts +1 -1
  199. package/lib/types/environment/runtime.d.ts +5 -5
  200. package/lib/types/environment/types.d.ts +15 -3
  201. package/lib/types/environment/workspace.d.ts +1 -0
  202. package/lib/types/index.d.ts +1 -1
  203. package/lib/types/lifecycle.d.ts +8 -0
  204. package/lib/types/plugin-agents.d.ts +27 -0
  205. package/lib/types/plugin.d.ts +14 -24
  206. package/lib/types/presentation/asset.d.ts +6 -0
  207. package/lib/types/presentation/build/index.d.ts +16 -0
  208. package/lib/types/presentation/commit.d.ts +4 -0
  209. package/lib/types/presentation/contracts/asset-path.d.ts +2 -0
  210. package/lib/types/presentation/contracts/catalog.d.ts +27 -0
  211. package/lib/types/presentation/contracts/charts.d.ts +11 -0
  212. package/lib/types/presentation/contracts/editing.d.ts +8 -0
  213. package/lib/types/presentation/contracts/index.d.ts +11 -0
  214. package/lib/types/presentation/contracts/interaction.d.ts +8 -0
  215. package/lib/types/presentation/contracts/types.d.ts +255 -0
  216. package/lib/types/presentation/contracts/values.d.ts +9 -0
  217. package/lib/types/presentation/delivery.d.ts +3 -0
  218. package/lib/types/presentation/export-html.d.ts +13 -0
  219. package/lib/types/presentation/files.d.ts +7 -0
  220. package/lib/types/presentation/index.d.ts +6 -0
  221. package/lib/types/presentation/lint.d.ts +13 -0
  222. package/lib/types/presentation/projection/files.d.ts +2 -0
  223. package/lib/types/presentation/projection/index.d.ts +20 -0
  224. package/lib/types/presentation/projection/program.d.ts +2 -0
  225. package/lib/types/presentation/projection/sql-code.d.ts +2 -0
  226. package/lib/types/presentation/receipt.d.ts +14 -0
  227. package/lib/types/presentation/reports.d.ts +10 -0
  228. package/lib/types/presentation/rpc.d.ts +25 -0
  229. package/lib/types/presentation/tool.d.ts +12 -0
  230. package/lib/types/python-execution-contracts.d.ts +10 -0
  231. package/lib/types/python-execution.d.ts +10 -0
  232. package/lib/types/report-publishing/adapters.d.ts +6 -0
  233. package/lib/types/report-publishing/config.d.ts +22 -0
  234. package/lib/types/report-publishing/service.d.ts +68 -0
  235. package/lib/types/rpc.d.ts +3 -0
  236. package/lib/types/semantic-browser/contracts.d.ts +47 -0
  237. package/lib/types/semantic-browser/definition.d.ts +20 -0
  238. package/lib/types/semantic-browser/program.d.ts +2 -0
  239. package/lib/types/semantic-browser/service.d.ts +26 -0
  240. package/lib/types/semantic-reference/bridge.d.ts +12 -0
  241. package/lib/types/semantic-reference/contracts.d.ts +54 -0
  242. package/lib/types/semantic-reference/environment.d.ts +22 -0
  243. package/lib/types/semantic-reference/index.d.ts +3 -0
  244. package/lib/types/semantic-reference/labels.d.ts +16 -0
  245. package/lib/types/semantic-reference/rpc.d.ts +15 -0
  246. package/lib/types/semantic-reference/search.d.ts +10 -0
  247. package/lib/types/semantic-reference/usage.d.ts +43 -0
  248. package/lib/types/tool-lifecycle.d.ts +4 -0
  249. package/lib/types/workspace-identity.d.ts +6 -0
  250. package/lib/workspace-identity.js +9 -0
  251. package/package.json +99 -53
  252. package/python/presentation-kit/dist/dsh_data_analysis_presentation_kit-1.1.0-py3-none-any.whl +0 -0
  253. package/skills/dsh-data-analysis-files/SKILL.md +32 -0
  254. package/skills/dsh-data-analysis-files/references/examples.md +93 -0
  255. package/skills/dsh-data-analysis-presentation/SKILL.md +28 -0
  256. package/skills/dsh-data-analysis-presentation/references/chart-examples.md +18 -0
  257. package/skills/dsh-data-analysis-presentation/references/charts.md +55 -0
  258. package/skills/dsh-data-analysis-presentation/references/examples/artifact.draft.json +36 -0
  259. package/skills/dsh-data-analysis-presentation/references/examples/charts-bins.dataset.json +33 -0
  260. package/skills/dsh-data-analysis-presentation/references/examples/charts-boxes.dataset.json +45 -0
  261. package/skills/dsh-data-analysis-presentation/references/examples/charts-composition.dataset.json +33 -0
  262. package/skills/dsh-data-analysis-presentation/references/examples/charts-funnel.dataset.json +27 -0
  263. package/skills/dsh-data-analysis-presentation/references/examples/charts-precision.dataset.json +21 -0
  264. package/skills/dsh-data-analysis-presentation/references/examples/charts-series.dataset.json +58 -0
  265. package/skills/dsh-data-analysis-presentation/references/examples/charts-shares.dataset.json +33 -0
  266. package/skills/dsh-data-analysis-presentation/references/examples/charts-waterfall.dataset.json +44 -0
  267. package/skills/dsh-data-analysis-presentation/references/examples/charts.draft.json +393 -0
  268. package/skills/dsh-data-analysis-presentation/references/examples/comparison-selected.dataset.json +24 -0
  269. package/skills/dsh-data-analysis-presentation/references/examples/comparison-summary.dataset.json +22 -0
  270. package/skills/dsh-data-analysis-presentation/references/examples/comparison.dataset.json +18 -0
  271. package/skills/dsh-data-analysis-presentation/references/examples/comparison.draft.json +80 -0
  272. package/skills/dsh-data-analysis-presentation/references/examples/computed-sources.draft.json +42 -0
  273. package/skills/dsh-data-analysis-presentation/references/examples/computed.dataset.json +12 -0
  274. package/skills/dsh-data-analysis-presentation/references/examples/computed.draft.json +58 -0
  275. package/skills/dsh-data-analysis-presentation/references/examples/interaction-detail.dataset.json +30 -0
  276. package/skills/dsh-data-analysis-presentation/references/examples/interaction-summary.dataset.json +22 -0
  277. package/skills/dsh-data-analysis-presentation/references/examples/interaction.draft.json +279 -0
  278. package/skills/dsh-data-analysis-presentation/references/examples/source-only.draft.json +27 -0
  279. package/skills/dsh-data-analysis-presentation/references/examples/write-charts.py +73 -0
  280. package/skills/dsh-data-analysis-presentation/references/examples/write-comparison.py +42 -0
  281. package/skills/dsh-data-analysis-presentation/references/examples/write-computed.py +21 -0
  282. package/skills/dsh-data-analysis-presentation/references/examples/write-interaction.py +33 -0
  283. package/skills/dsh-data-analysis-presentation/references/examples.md +44 -0
  284. package/skills/dsh-data-analysis-presentation/references/export-html.md +7 -0
  285. package/skills/dsh-data-analysis-presentation/references/interaction.md +56 -0
  286. package/skills/dsh-data-analysis-presentation/references/narrative.md +37 -0
  287. package/skills/dsh-data-analysis-presentation/references/publishing.md +3 -0
  288. package/skills/dsh-data-analysis-presentation/references/schema.md +162 -0
  289. package/lib/datasource/access.js +0 -60
  290. package/lib/datasource/credentials.js +0 -24
  291. package/lib/evidence/bridge-program.js +0 -153
  292. package/lib/evidence/bridge.js +0 -166
  293. package/lib/evidence/index.js +0 -2
  294. package/lib/evidence/sources.js +0 -263
  295. package/lib/types/datasource/access.d.ts +0 -27
  296. package/lib/types/evidence/bridge.d.ts +0 -40
  297. package/lib/types/evidence/index.d.ts +0 -4
  298. package/lib/types/evidence/sources.d.ts +0 -57
  299. package/python/report-kit/dist/dsh_data_analysis_report_kit-3.0.0-py3-none-any.whl +0 -0
  300. package/skills/dsh-data-analysis-report/SKILL.md +0 -57
  301. package/skills/dsh-data-analysis-report/assets/marivo-artifact.js +0 -113
  302. package/skills/dsh-data-analysis-report/assets/marivo-session-dag.js +0 -1201
  303. package/skills/dsh-data-analysis-report/assets/report-data.js +0 -449
@@ -0,0 +1,62 @@
1
+ export function defaultSelection(interaction) {
2
+ return Object.fromEntries(interaction.filters.map((filter) => [filter.id, filter.allOptionId]));
3
+ }
4
+ export function selectedSlice(interaction, selection) {
5
+ const slice = interaction.slices.find((slice) => interaction.filters.every((filter) => Object.hasOwn(selection, filter.id) && slice.selection[filter.id] === selection[filter.id]));
6
+ if (!slice)
7
+ throw new Error('Unknown presentation filter combination.');
8
+ return slice;
9
+ }
10
+ export function interactionRows(interaction, selection, block) {
11
+ if (!interaction?.blockIds.includes(block.id) || !('datasetId' in block))
12
+ return undefined;
13
+ const binding = selectedSlice(interaction, selection).datasets.find((entry) => entry.datasetId === block.datasetId);
14
+ if (!binding)
15
+ throw new Error('Missing presentation slice binding.');
16
+ return binding.rowIndices;
17
+ }
18
+ export function filterSummary(interaction, selection) {
19
+ return interaction.filters
20
+ .map((filter) => {
21
+ const option = filter.options.find((option) => option.id === selection[filter.id]);
22
+ if (!option)
23
+ throw new Error('Unknown presentation filter option.');
24
+ return `${filter.label}:${option.label}`;
25
+ })
26
+ .join(' · ');
27
+ }
28
+ /** Preserve the authored before / interactive / after boundary during host edits. */
29
+ export function blockRegion(document, id) {
30
+ if (!document.interaction)
31
+ return 0;
32
+ if (document.interaction.blockIds.includes(id))
33
+ return 1;
34
+ return document.blocks.findIndex((block) => block.id === id) <
35
+ document.blocks.findIndex((block) => block.id === document.interaction.blockIds[0])
36
+ ? 0
37
+ : 2;
38
+ }
39
+ export function editedInteraction(document, blocks) {
40
+ if (!document.interaction)
41
+ return undefined;
42
+ const original = document.interaction;
43
+ const targets = blocks.filter((block) => original.blockIds.includes(block.id));
44
+ if (!targets.length)
45
+ return undefined;
46
+ const datasets = new Set(targets.flatMap((block) => 'datasetId' in block
47
+ ? [
48
+ block.datasetId,
49
+ ...(block.kind === 'chart'
50
+ ? (block.preparedViews ?? []).map((view) => view.datasetId)
51
+ : []),
52
+ ]
53
+ : []));
54
+ return {
55
+ ...original,
56
+ blockIds: targets.map((block) => block.id),
57
+ slices: original.slices.map((slice) => ({
58
+ ...slice,
59
+ datasets: slice.datasets.filter((entry) => datasets.has(entry.datasetId)),
60
+ })),
61
+ };
62
+ }
@@ -0,0 +1,15 @@
1
+ export const PRESENTATION_SCHEMA_VERSION = 3;
2
+ export const PRESENTATION_BUDGETS = {
3
+ documentBytes: 4 * 1024 * 1024,
4
+ draftBytes: 256 * 1024,
5
+ datasetBytes: 2 * 1024 * 1024,
6
+ htmlBytes: 8 * 1024 * 1024,
7
+ datasets: 16,
8
+ columns: 64,
9
+ rows: 5_000,
10
+ cells: 100_000,
11
+ sources: 64,
12
+ codeSnippets: 32,
13
+ blocks: 64,
14
+ text: 32_768,
15
+ };
@@ -0,0 +1,28 @@
1
+ export class PresentationContractError extends Error {
2
+ code;
3
+ path;
4
+ hint;
5
+ reason;
6
+ constructor(code, path, message, hint = 'Correct the value at this path.') {
7
+ super(`${path || '/'}: ${message}`);
8
+ this.name = 'PresentationContractError';
9
+ this.reason = message;
10
+ this.code = code;
11
+ this.path = path;
12
+ this.hint = hint;
13
+ }
14
+ }
15
+ export function chartNumber(value, column, mode, path = '') {
16
+ if (value === null)
17
+ return null;
18
+ if (!['float64', 'int64', 'decimal'].includes(column.type))
19
+ throw new PresentationContractError('invalid_value', path, 'Chart series must be numeric.');
20
+ if (mode === 'exact' &&
21
+ (column.type === 'decimal' || (column.type === 'int64' && !Number.isSafeInteger(Number(value))))) {
22
+ throw new PresentationContractError('numeric_precision', path, 'Exact chart encoding cannot represent this value.', 'Set numericMode to approximate explicitly, or show the exact value in a table.');
23
+ }
24
+ const number = Number(value);
25
+ if (!Number.isFinite(number))
26
+ throw new PresentationContractError('numeric_precision', path, 'Chart encoding must remain finite.');
27
+ return number;
28
+ }
@@ -0,0 +1,71 @@
1
+ import { finishCleanup, PendingTasks } from "../lifecycle.js";
2
+ import { MARIVO_PRESENT_TOOL_NAME, MARIVO_PRESENTATION_DELIVERY_KIND, parsePresentationDelivery, } from "./receipt.js";
3
+ /** Persist the same envelope on nested Code dispatch even when code discards the return value. */
4
+ export function installMarivoPresentationCodeDelivery(ctx) {
5
+ const tasks = new PendingTasks();
6
+ let active = true;
7
+ const pending = new Map();
8
+ const stopResult = ctx.on('tools/result', (exec, result) => {
9
+ if (!active)
10
+ return;
11
+ if (exec.name !== MARIVO_PRESENT_TOOL_NAME || exec.parent === undefined || result.isError)
12
+ return;
13
+ try {
14
+ const value = result.value;
15
+ const delivery = parsePresentationDelivery(JSON.parse(value.deliveryJson));
16
+ if (delivery.dshSessionId !== String(exec.agent?.session.id))
17
+ return;
18
+ pending.set(String(exec.callId), { rootCallId: String(exec.rootCallId), delivery });
19
+ // Bounded transient dispatch rendezvous; receipts remain in Host events, never this map.
20
+ if (pending.size > 256)
21
+ pending.delete(pending.keys().next().value);
22
+ }
23
+ catch {
24
+ /* Invalid output cannot create a success card. */
25
+ }
26
+ });
27
+ let stopLog;
28
+ try {
29
+ stopLog = ctx.on('tools/ptc-dispatch-log', (dispatch, next) => tasks.track((async () => {
30
+ const content = await next();
31
+ if (!active)
32
+ return content;
33
+ if (dispatch.name !== MARIVO_PRESENT_TOOL_NAME)
34
+ return content;
35
+ const key = String(dispatch.subCallId);
36
+ const item = pending.get(key);
37
+ pending.delete(key);
38
+ if (dispatch.isError ||
39
+ !item ||
40
+ item.delivery.dshSessionId !== String(dispatch.agent?.session.id) ||
41
+ item.rootCallId !== String(dispatch.exec.rootCallId))
42
+ return content;
43
+ const root = [...(dispatch.agent?.session.snapshotEvents() ?? [])]
44
+ .reverse()
45
+ .find((event) => event.type === 'tool/call' && String(event.data.callId) === item.rootCallId);
46
+ if (root?.type !== 'tool/call' || root.data.turn !== item.delivery.turn)
47
+ return content;
48
+ return [
49
+ ...content,
50
+ {
51
+ type: MARIVO_PRESENTATION_DELIVERY_KIND,
52
+ delivery: item.delivery,
53
+ },
54
+ ];
55
+ })()), { prepend: true });
56
+ }
57
+ catch (error) {
58
+ active = false;
59
+ void finishCleanup([stopResult, () => tasks.drain()]);
60
+ throw error;
61
+ }
62
+ let closing;
63
+ return () => {
64
+ if (closing)
65
+ return closing;
66
+ active = false;
67
+ closing = finishCleanup([stopLog, stopResult, () => tasks.drain()]);
68
+ pending.clear();
69
+ return closing;
70
+ };
71
+ }
@@ -0,0 +1,214 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { constants } from 'node:fs';
3
+ import { link, lstat, mkdir, open, realpath, unlink } from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { defineTool } from '@deepseek-ai/dsh-tools';
6
+ import { registerMarivoTool } from "../tool-lifecycle.js";
7
+ import { readReceiptAsset } from "./asset.js";
8
+ import { parsePresentationBuildId } from "./contracts/index.js";
9
+ import { readReportHistory } from "./reports.js";
10
+ export const MARIVO_EXPORT_HTML_TOOL_NAME = 'marivo_export_html';
11
+ export function parseHtmlOutputPath(value) {
12
+ if (typeof value !== 'string' ||
13
+ !value ||
14
+ value.length > 4096 ||
15
+ value.includes('\\') ||
16
+ value.includes('\0') ||
17
+ path.isAbsolute(value) ||
18
+ !value.endsWith('.html'))
19
+ throw new Error('invalid-html-output-path');
20
+ const segments = value.split('/');
21
+ if (segments.some((part) => !part || part === '.' || part === '..') ||
22
+ segments[0].toLowerCase() === '.dsh-data-analysis')
23
+ throw new Error('invalid-html-output-path');
24
+ return value;
25
+ }
26
+ /** Workspace-local hard-link publication is atomic and never replaces an existing target. */
27
+ async function writeHtml(root, relative, bytes, assertOwner) {
28
+ const parents = [root];
29
+ const identities = [];
30
+ const inspect = async (filename) => {
31
+ const stat = await lstat(filename);
32
+ if (!stat.isDirectory() || stat.isSymbolicLink() || (await realpath(filename)) !== filename)
33
+ throw new Error('html-output-directory-mismatch');
34
+ return stat;
35
+ };
36
+ const check = async () => {
37
+ await assertOwner();
38
+ for (const [index, directory] of parents.entries()) {
39
+ const stat = await inspect(directory);
40
+ if (stat.dev !== identities[index].dev || stat.ino !== identities[index].ino)
41
+ throw new Error('html-output-directory-changed');
42
+ }
43
+ };
44
+ identities.push(await inspect(root));
45
+ for (const part of relative.split('/').slice(0, -1)) {
46
+ await check();
47
+ const directory = path.join(parents.at(-1), part);
48
+ try {
49
+ await mkdir(directory, { mode: 0o700 });
50
+ }
51
+ catch (error) {
52
+ if (error.code !== 'EEXIST')
53
+ throw error;
54
+ }
55
+ identities.push(await inspect(directory));
56
+ parents.push(directory);
57
+ }
58
+ await check();
59
+ const target = path.join(root, relative);
60
+ // Stage at the highest ancestor on the destination filesystem, outside movable
61
+ // output subdirectories where possible, without introducing cross-device links.
62
+ const stagingParent = parents[identities.findIndex((identity) => identity.dev === identities.at(-1).dev)];
63
+ const temporary = path.join(stagingParent, `.html-export-${randomUUID()}.tmp`);
64
+ const handle = await open(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
65
+ const owned = await handle.stat();
66
+ let linked = false;
67
+ const removeOwned = async (filename) => {
68
+ try {
69
+ const stat = await lstat(filename);
70
+ if (stat.dev === owned.dev && stat.ino === owned.ino)
71
+ await unlink(filename);
72
+ }
73
+ catch (error) {
74
+ if (error.code !== 'ENOENT')
75
+ throw error;
76
+ }
77
+ };
78
+ try {
79
+ await check();
80
+ await handle.writeFile(bytes);
81
+ await handle.sync();
82
+ await check();
83
+ await link(temporary, target);
84
+ linked = true;
85
+ await check();
86
+ const stat = await lstat(target);
87
+ if (stat.dev !== owned.dev || stat.ino !== owned.ino || stat.size !== bytes.length)
88
+ throw new Error('html-output-changed');
89
+ await removeOwned(temporary);
90
+ await check();
91
+ return target;
92
+ }
93
+ catch (error) {
94
+ // Paths may have moved or been replaced. The open handle still owns the exact inode,
95
+ // including any published hard link: erase failed output without following new paths.
96
+ const failures = [error];
97
+ try {
98
+ await handle.truncate(0);
99
+ await handle.sync();
100
+ }
101
+ catch (cleanupError) {
102
+ failures.push(cleanupError);
103
+ }
104
+ if (linked) {
105
+ try {
106
+ await removeOwned(target);
107
+ }
108
+ catch (cleanupError) {
109
+ failures.push(cleanupError);
110
+ }
111
+ }
112
+ if (failures.length > 1)
113
+ throw new AggregateError(failures, 'html-export-cleanup-failed');
114
+ throw error;
115
+ }
116
+ finally {
117
+ try {
118
+ await removeOwned(temporary);
119
+ }
120
+ finally {
121
+ await handle.close();
122
+ }
123
+ }
124
+ }
125
+ export function createMarivoExportHtmlTool(source, session) {
126
+ return defineTool({
127
+ name: MARIVO_EXPORT_HTML_TOOL_NAME,
128
+ description: 'Export a saved complete report as self-contained offline HTML only when requested. Uses the current Session Workspace, without analysis, Python, publication, new Builds or unsaved reader state. Specify build_id for an exact version; otherwise freezes current at call start. output_path must be a new Workspace-relative .html file; existing files are never overwritten. Returns file metadata, not HTML content. After success, call native present({files:[{path: result.path, description: "Offline HTML report"}]}) to deliver the file before your final response.',
129
+ parameters: {
130
+ report_id: {
131
+ type: 'string',
132
+ required: true,
133
+ description: 'Saved Report ID from its receipt.',
134
+ },
135
+ build_id: { type: 'string', description: 'Exact saved Build ID; omit to export current.' },
136
+ output_path: {
137
+ type: 'string',
138
+ required: true,
139
+ description: 'New Workspace-relative .html path, e.g. reports/analysis.html. Parent directories may be created.',
140
+ },
141
+ },
142
+ output: {
143
+ schema: {
144
+ type: 'object',
145
+ additionalProperties: false,
146
+ properties: {
147
+ workspace_id: { type: 'string', required: true },
148
+ report_id: { type: 'string', required: true },
149
+ build_id: { type: 'string', required: true },
150
+ path: { type: 'string', required: true },
151
+ mime_type: { type: 'string', required: true },
152
+ bytes: { type: 'number', required: true },
153
+ sha256: { type: 'string', required: true },
154
+ },
155
+ },
156
+ render: (_args, value) => [{ type: 'text', text: JSON.stringify(value) }],
157
+ presentationMeta: (_args, value) => value,
158
+ },
159
+ presentCall(args) {
160
+ return {
161
+ card: 'generic',
162
+ kind: 'edit',
163
+ title: 'Export report HTML',
164
+ locations: [{ path: args.output_path }],
165
+ };
166
+ },
167
+ timeoutMs: 120_000,
168
+ async execute(args, exec) {
169
+ const reportId = parsePresentationBuildId(args.report_id, '/report_id');
170
+ const buildId = args.build_id === undefined
171
+ ? undefined
172
+ : parsePresentationBuildId(args.build_id, '/build_id');
173
+ const relative = parseHtmlOutputPath(args.output_path);
174
+ const owner = exec.agent;
175
+ if (!owner || owner.session !== session)
176
+ throw new Error('presentation-session-mismatch');
177
+ exec.signal.throwIfAborted();
178
+ const workspace = await source();
179
+ const root = path.resolve(workspace.path);
180
+ const check = async () => {
181
+ exec.signal.throwIfAborted();
182
+ if (owner.session !== session)
183
+ throw new Error('presentation-session-mismatch');
184
+ const current = await source();
185
+ if (current.id !== workspace.id || path.resolve(current.path) !== root)
186
+ throw new Error('presentation-workspace-changed');
187
+ exec.signal.throwIfAborted();
188
+ };
189
+ await check();
190
+ const history = await readReportHistory(root, workspace.id, reportId, exec.signal);
191
+ const receipt = buildId === undefined
192
+ ? history.versions[0].receipt
193
+ : history.versions.find((entry) => entry.receipt.buildId === buildId)?.receipt;
194
+ if (!receipt)
195
+ throw new Error('presentation-build-unavailable');
196
+ await check();
197
+ const asset = await readReceiptAsset(root, workspace.id, receipt, 'index.html', exec.signal);
198
+ await check();
199
+ const filename = await writeHtml(root, relative, asset.bytes, check);
200
+ return {
201
+ workspace_id: workspace.id,
202
+ report_id: reportId,
203
+ build_id: receipt.buildId,
204
+ path: filename,
205
+ mime_type: 'text/html',
206
+ bytes: asset.bytes.length,
207
+ sha256: asset.sha256,
208
+ };
209
+ },
210
+ });
211
+ }
212
+ export function registerMarivoExportHtmlTool(ctx, source, session) {
213
+ return registerMarivoTool(ctx, createMarivoExportHtmlTool(source, session));
214
+ }
@@ -0,0 +1,82 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { constants } from 'node:fs';
3
+ import { lstat, open, realpath } from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { presentationAssetRelativePath } from "./contracts/asset-path.js";
6
+ import { PRESENTATION_BUDGETS, parsePresentationBuildId, } from "./contracts/index.js";
7
+ export function presentationReportPath(root, reportId) {
8
+ parsePresentationBuildId(reportId, '/reportId');
9
+ return path.join(root, '.dsh-data-analysis', 'presentations', reportId);
10
+ }
11
+ export function presentationAssetPath(root, reportId, buildId, asset) {
12
+ return path.join(root, presentationAssetRelativePath(reportId, buildId, asset));
13
+ }
14
+ export function presentationSha256(bytes) {
15
+ return createHash('sha256').update(bytes).digest('hex');
16
+ }
17
+ /** Bounded raw-byte reader with no symbolic links and stable directory/file identity. */
18
+ export async function readPresentationAsset(workspaceRoot, reportId, buildId, asset, signal) {
19
+ const filename = presentationAssetPath(workspaceRoot, reportId, buildId, asset);
20
+ return readReportFile(workspaceRoot, filename, asset === 'presentation.json'
21
+ ? PRESENTATION_BUDGETS.documentBytes
22
+ : PRESENTATION_BUDGETS.htmlBytes, signal);
23
+ }
24
+ export async function readReportFile(workspaceRoot, filename, maximum, signal) {
25
+ signal?.throwIfAborted();
26
+ const root = await realpath(workspaceRoot);
27
+ if (root !== path.resolve(workspaceRoot) || !filename.startsWith(`${root}${path.sep}`))
28
+ throw new Error('asset-path-mismatch');
29
+ const parents = [root];
30
+ for (const segment of path.relative(root, path.dirname(filename)).split(path.sep)) {
31
+ if (!segment || segment === '..' || segment === '.')
32
+ throw new Error('asset-path-mismatch');
33
+ parents.push(path.join(parents.at(-1), segment));
34
+ }
35
+ const beforeParents = await Promise.all(parents.map((parent) => lstat(parent)));
36
+ if (beforeParents.some((stat) => !stat.isDirectory() || stat.isSymbolicLink()))
37
+ throw new Error('asset-path-mismatch');
38
+ if ((await realpath(filename)) !== filename)
39
+ throw new Error('asset-path-mismatch');
40
+ const file = await open(filename, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
41
+ try {
42
+ const before = await file.stat();
43
+ if (!before.isFile())
44
+ throw new Error('asset-not-file');
45
+ if (before.size > maximum)
46
+ throw new Error('asset-too-large');
47
+ // Allocate for the observed file, retaining one extra byte to detect growth.
48
+ const bytes = Buffer.alloc(before.size + 1);
49
+ let length = 0;
50
+ while (length < bytes.length) {
51
+ signal?.throwIfAborted();
52
+ const read = await file.read(bytes, length, bytes.length - length, null);
53
+ if (read.bytesRead === 0)
54
+ break;
55
+ length += read.bytesRead;
56
+ }
57
+ if (length > maximum)
58
+ throw new Error('asset-too-large');
59
+ const after = await file.stat();
60
+ const current = await lstat(filename);
61
+ const afterParents = await Promise.all(parents.map((parent) => lstat(parent)));
62
+ if (afterParents.some((stat, index) => !stat.isDirectory() ||
63
+ stat.isSymbolicLink() ||
64
+ stat.dev !== beforeParents[index].dev ||
65
+ stat.ino !== beforeParents[index].ino) ||
66
+ (await realpath(workspaceRoot)) !== root ||
67
+ (await realpath(filename)) !== filename)
68
+ throw new Error('asset-path-mismatch');
69
+ if (current.dev !== after.dev ||
70
+ current.ino !== after.ino ||
71
+ before.size !== after.size ||
72
+ before.mtimeMs !== after.mtimeMs ||
73
+ before.ctimeMs !== after.ctimeMs ||
74
+ length !== after.size)
75
+ throw new Error('asset-changed');
76
+ signal?.throwIfAborted();
77
+ return bytes.subarray(0, length);
78
+ }
79
+ finally {
80
+ await file.close();
81
+ }
82
+ }
@@ -0,0 +1,6 @@
1
+ export * from "./contracts/index.js";
2
+ export * from "./delivery.js";
3
+ export * from "./projection/index.js";
4
+ export * from "./receipt.js";
5
+ export * from "./rpc.js";
6
+ export * from "./tool.js";
@@ -0,0 +1,46 @@
1
+ import { PRESENTATION_BUDGETS, PresentationContractError, parsePresentationDraft, parseTypedDataset, } from "./contracts/index.js";
2
+ import { readWorkspaceJson } from "./projection/files.js";
3
+ /** Static preflight only: never starts a Runtime or publishes presentation assets. */
4
+ export async function lintPresentationDraft(workspaceRoot, draftPath) {
5
+ const diagnostics = [];
6
+ let checkedComputedDatasets = 0;
7
+ const record = (error) => {
8
+ if (!(error instanceof PresentationContractError))
9
+ throw error;
10
+ diagnostics.push({
11
+ code: error.code,
12
+ path: error.path,
13
+ message: error.message,
14
+ hint: error.hint,
15
+ });
16
+ };
17
+ try {
18
+ const draft = parsePresentationDraft(await readWorkspaceJson(workspaceRoot, draftPath, PRESENTATION_BUDGETS.draftBytes, '/draft_path'));
19
+ for (const [index, dataset] of draft.datasets.entries()) {
20
+ if (dataset.kind !== 'computed')
21
+ continue;
22
+ try {
23
+ const data = await readWorkspaceJson(workspaceRoot, dataset.path, PRESENTATION_BUDGETS.datasetBytes, `/datasets/${index}/path`);
24
+ parseTypedDataset(data, `/datasets/${index}/data`);
25
+ checkedComputedDatasets++;
26
+ }
27
+ catch (error) {
28
+ record(error);
29
+ }
30
+ }
31
+ }
32
+ catch (error) {
33
+ record(error);
34
+ }
35
+ return {
36
+ ok: diagnostics.length === 0,
37
+ scope: 'draft-structure-and-computed-data',
38
+ checkedComputedDatasets,
39
+ diagnostics,
40
+ deferredChecks: [
41
+ 'artifact-availability-and-data',
42
+ 'codeRefs',
43
+ 'projected-document-and-rendering',
44
+ ],
45
+ };
46
+ }
@@ -0,0 +1,85 @@
1
+ import { constants } from 'node:fs';
2
+ import { lstat, open, realpath } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { PresentationContractError } from "../contracts/index.js";
5
+ function boundary(location) {
6
+ throw new PresentationContractError('file_boundary', location, 'Expected an unchanged regular file inside the bound Workspace without symbolic links.');
7
+ }
8
+ /** Read raw bytes under a strict budget before JSON parsing, including concurrent growth. */
9
+ export async function readWorkspaceJson(workspaceRoot, relativePath, maximum, location, signal) {
10
+ signal?.throwIfAborted();
11
+ if (path.isAbsolute(relativePath) ||
12
+ relativePath.includes('\\') ||
13
+ relativePath.includes('\0') ||
14
+ relativePath.split('/').some((part) => !part || part === '.' || part === '..'))
15
+ boundary(location);
16
+ try {
17
+ const root = await realpath(workspaceRoot);
18
+ if (root !== path.resolve(workspaceRoot))
19
+ boundary(location);
20
+ const segments = relativePath.split('/');
21
+ const filename = path.join(root, ...segments);
22
+ const parents = [
23
+ root,
24
+ ...segments.slice(0, -1).map((_, index) => path.join(root, ...segments.slice(0, index + 1))),
25
+ ];
26
+ const beforeParents = await Promise.all(parents.map((parent) => lstat(parent)));
27
+ if (beforeParents.some((stat) => !stat.isDirectory() || stat.isSymbolicLink()))
28
+ boundary(location);
29
+ if ((await realpath(filename)) !== filename)
30
+ boundary(location);
31
+ if (!(await lstat(filename)).isFile())
32
+ boundary(location);
33
+ const file = await open(filename, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
34
+ try {
35
+ const before = await file.stat();
36
+ if (!before.isFile())
37
+ boundary(location);
38
+ if (before.size > maximum)
39
+ throw new PresentationContractError('budget', location, 'Input file exceeds the presentation byte budget.');
40
+ const bytes = Buffer.alloc(maximum + 1);
41
+ let length = 0;
42
+ while (length < bytes.length) {
43
+ signal?.throwIfAborted();
44
+ const { bytesRead } = await file.read(bytes, length, bytes.length - length, null);
45
+ if (bytesRead === 0)
46
+ break;
47
+ length += bytesRead;
48
+ }
49
+ if (length > maximum)
50
+ throw new PresentationContractError('budget', location, 'Input file exceeds the presentation byte budget.');
51
+ const after = await file.stat();
52
+ const current = await lstat(filename);
53
+ const afterParents = await Promise.all(parents.map((parent) => lstat(parent)));
54
+ if (afterParents.some((stat, index) => !stat.isDirectory() ||
55
+ stat.isSymbolicLink() ||
56
+ stat.dev !== beforeParents[index].dev ||
57
+ stat.ino !== beforeParents[index].ino) ||
58
+ (await realpath(workspaceRoot)) !== root ||
59
+ (await realpath(filename)) !== filename ||
60
+ current.dev !== after.dev ||
61
+ current.ino !== after.ino ||
62
+ before.size !== after.size ||
63
+ before.mtimeMs !== after.mtimeMs ||
64
+ before.ctimeMs !== after.ctimeMs ||
65
+ length !== after.size)
66
+ boundary(location);
67
+ signal?.throwIfAborted();
68
+ try {
69
+ return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes.subarray(0, length)));
70
+ }
71
+ catch {
72
+ throw new PresentationContractError('invalid_json', location, 'Expected valid UTF-8 JSON.');
73
+ }
74
+ }
75
+ finally {
76
+ await file.close();
77
+ }
78
+ }
79
+ catch (error) {
80
+ signal?.throwIfAborted();
81
+ if (error instanceof PresentationContractError)
82
+ throw error;
83
+ boundary(location);
84
+ }
85
+ }