@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,186 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import path from 'node:path';
3
+ import { finishCleanup, PendingTasks } from "../lifecycle.js";
4
+ import { registerPluginRpc } from "../rpc.js";
5
+ import { readReceiptAsset } from "./asset.js";
6
+ import { applyPresentationEdits } from "./contracts/editing.js";
7
+ import { PresentationContractError, parsePresentationBuildId, parsePresentationReceipt, } from "./contracts/index.js";
8
+ import { MARIVO_PRESENTATION_RPC_CHANNEL } from "./receipt.js";
9
+ import { listReports, publishPresentation, readReceiptDocument, readReportHistory, resolvePresentation, } from "./reports.js";
10
+ export class MarivoPresentationFileService {
11
+ #resolve;
12
+ #resolveId;
13
+ #abort = new AbortController();
14
+ #tasks = new PendingTasks();
15
+ #closing;
16
+ constructor(resolve, resolveId) {
17
+ this.#resolve = resolve;
18
+ this.#resolveId = resolveId;
19
+ }
20
+ async #workspace(input) {
21
+ try {
22
+ if ('workspaceId' in input) {
23
+ const workspace = await this.#resolveId?.(input.workspaceId);
24
+ if (workspace && workspace.id !== input.workspaceId)
25
+ throw new Error('workspace-unavailable');
26
+ return workspace;
27
+ }
28
+ return await this.#resolve(input.sessionId);
29
+ }
30
+ catch {
31
+ throw new Error('workspace-unavailable');
32
+ }
33
+ }
34
+ #scope(input) {
35
+ const scope = 'workspaceId' in input ? 'workspaceId' : 'sessionId';
36
+ if (typeof input[scope] !== 'string' ||
37
+ !input[scope].trim() ||
38
+ input[scope].length > 512 ||
39
+ ('workspaceId' in input && 'sessionId' in input))
40
+ throw new Error('invalid-request');
41
+ return scope;
42
+ }
43
+ catalog(endpoint, payload, caller = new AbortController().signal) {
44
+ return this.#tasks.track(this.#catalog(endpoint, payload, caller));
45
+ }
46
+ async #catalog(endpoint, payload, caller = new AbortController().signal) {
47
+ const signal = AbortSignal.any([caller, this.#abort.signal]);
48
+ signal.throwIfAborted();
49
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload))
50
+ throw new Error('invalid-request');
51
+ const input = payload;
52
+ const scope = this.#scope(input);
53
+ const expected = endpoint === 'reports/list' ? scope : ['reportId', scope].sort().join(',');
54
+ if (Object.keys(input).sort().join(',') !== expected)
55
+ throw new Error('invalid-request');
56
+ const workspace = await this.#workspace(input);
57
+ if (!workspace)
58
+ throw new Error('workspace-unavailable');
59
+ const root = path.resolve(workspace.path);
60
+ const result = endpoint === 'reports/list'
61
+ ? await listReports(root, workspace.id, signal)
62
+ : await readReportHistory(root, workspace.id, parsePresentationBuildId(input.reportId), signal);
63
+ const current = await this.#workspace(input);
64
+ if (!current || current.id !== workspace.id || path.resolve(current.path) !== root)
65
+ throw new Error('workspace-changed');
66
+ signal.throwIfAborted();
67
+ return result;
68
+ }
69
+ close() {
70
+ this.#abort.abort();
71
+ this.#closing ??= finishCleanup([() => this.#tasks.drain()]);
72
+ return this.#closing;
73
+ }
74
+ report(endpoint, payload, caller = new AbortController().signal) {
75
+ return this.#tasks.track(this.#report(endpoint, payload, caller));
76
+ }
77
+ async #report(endpoint, payload, caller = new AbortController().signal) {
78
+ const signal = AbortSignal.any([caller, this.#abort.signal]);
79
+ signal.throwIfAborted();
80
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload))
81
+ throw new Error('invalid-request');
82
+ const input = payload;
83
+ const scope = this.#scope(input);
84
+ const expected = (endpoint === 'reports/save'
85
+ ? ['edits', 'expectedBuildId', 'reportId', scope]
86
+ : ['reportId', scope])
87
+ .sort()
88
+ .join(',');
89
+ if (Object.keys(input).sort().join(',') !== expected)
90
+ throw new Error('invalid-request');
91
+ const reportId = parsePresentationBuildId(input.reportId, '/reportId');
92
+ const workspace = await this.#workspace(input);
93
+ if (!workspace)
94
+ throw new Error('workspace-unavailable');
95
+ const root = path.resolve(workspace.path);
96
+ const check = async () => {
97
+ signal.throwIfAborted();
98
+ const current = await this.#workspace(input);
99
+ if (!current || current.id !== workspace.id || path.resolve(current.path) !== root)
100
+ throw new Error('workspace-changed');
101
+ };
102
+ await check();
103
+ const receipt = await resolvePresentation(root, workspace.id, reportId, signal);
104
+ await check();
105
+ if (endpoint === 'reports/resolve')
106
+ return receipt;
107
+ const expectedBuildId = parsePresentationBuildId(input.expectedBuildId);
108
+ if (receipt.buildId !== expectedBuildId)
109
+ throw new Error('report-save-conflict');
110
+ const base = await readReceiptDocument(root, receipt, signal);
111
+ let edited;
112
+ try {
113
+ edited = applyPresentationEdits(base, input.edits);
114
+ }
115
+ catch {
116
+ throw new Error('invalid-report-edits');
117
+ }
118
+ await check();
119
+ return publishPresentation(root, { ...edited, buildId: randomUUID(), generatedAt: new Date().toISOString() }, expectedBuildId, check, signal, { kind: 'reader', sessionId: typeof input.sessionId === 'string' ? input.sessionId : null });
120
+ }
121
+ read(payload, caller = new AbortController().signal) {
122
+ return this.#tasks.track(this.#read(payload, caller));
123
+ }
124
+ async #read(payload, caller = new AbortController().signal) {
125
+ const signal = AbortSignal.any([caller, this.#abort.signal]);
126
+ signal.throwIfAborted();
127
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload))
128
+ throw new Error('invalid-request');
129
+ const input = payload;
130
+ if (Object.keys(input).sort().join(',') !==
131
+ ['asset', 'receipt', this.#scope(input)].sort().join(',') ||
132
+ (input.asset !== 'presentation.json' && input.asset !== 'index.html'))
133
+ throw new Error('invalid-request');
134
+ const receipt = structuredClone(parsePresentationReceipt(input.receipt));
135
+ const asset = input.asset;
136
+ const workspace = await this.#workspace(input);
137
+ if (!workspace || workspace.id !== receipt.workspaceId)
138
+ throw new Error('workspace-unavailable');
139
+ const root = path.resolve(workspace.path);
140
+ const { bytes, sha256 } = await readReceiptAsset(root, workspace.id, receipt, asset, signal);
141
+ const current = await this.#workspace(input);
142
+ if (!current || current.id !== workspace.id || path.resolve(current.path) !== root)
143
+ throw new Error('workspace-changed');
144
+ signal.throwIfAborted();
145
+ return {
146
+ workspaceId: workspace.id,
147
+ reportId: receipt.reportId,
148
+ buildId: receipt.buildId,
149
+ asset,
150
+ mimeType: asset === 'presentation.json' ? 'application/json' : 'text/html',
151
+ sha256,
152
+ bytes: bytes.length,
153
+ bodyBase64: bytes.toString('base64'),
154
+ };
155
+ }
156
+ }
157
+ export function registerMarivoPresentationRpc(connection, service) {
158
+ const unregister = registerPluginRpc(connection, MARIVO_PRESENTATION_RPC_CHANNEL, ['reports/resolve', 'reports/save', 'reports/list', 'reports/history', 'files/read'], async (endpoint, payload, signal) => {
159
+ try {
160
+ if (endpoint === 'reports/resolve' || endpoint === 'reports/save')
161
+ return { ok: true, value: await service.report(endpoint, payload, signal) };
162
+ if (endpoint === 'reports/list' || endpoint === 'reports/history')
163
+ return { ok: true, value: await service.catalog(endpoint, payload, signal) };
164
+ if (endpoint === 'files/read')
165
+ return { ok: true, value: await service.read(payload, signal) };
166
+ throw new Error('unknown-endpoint');
167
+ }
168
+ catch (error) {
169
+ const message = signal.aborted
170
+ ? 'cancelled'
171
+ : error instanceof PresentationContractError
172
+ ? error.code
173
+ : error?.code === 'ENOENT'
174
+ ? 'asset-missing'
175
+ : error instanceof Error &&
176
+ /^(invalid-request|workspace-unavailable|workspace-changed|asset-path-mismatch|asset-not-file|asset-too-large|asset-changed|asset-digest-mismatch|asset-owner-mismatch|unknown-endpoint|invalid-report-current|invalid-report-history|report-history-full|report-catalog-too-large|invalid-report-edits|report-save-conflict|presentation-directory-changed)$/.test(error.message)
177
+ ? error.message
178
+ : error instanceof Error && /lock.*timed out|timed out.*lock/i.test(error.message)
179
+ ? 'report-save-busy'
180
+ : 'presentation-operation-failed';
181
+ return { ok: false, error: { code: 'internal', message, details: {} } };
182
+ }
183
+ });
184
+ let closing;
185
+ return () => (closing ??= finishCleanup([unregister, () => service.close()]));
186
+ }
@@ -0,0 +1,118 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { defineTool } from '@deepseek-ai/dsh-tools';
3
+ import { registerMarivoTool } from "../tool-lifecycle.js";
4
+ import { parsePresentationBuildId } from "./contracts/index.js";
5
+ import { MARIVO_PRESENT_TOOL_NAME, MARIVO_PRESENTATION_DELIVERY_KIND, parsePresentationDelivery, presentationReceiptText, } from "./receipt.js";
6
+ import { publishPresentation, resolvePresentation } from "./reports.js";
7
+ /** One call projects a draft, builds the document, commits them, then emits the receipt. */
8
+ export function createMarivoPresentTool(source, session, lifecycleSignal) {
9
+ return defineTool({
10
+ name: MARIVO_PRESENT_TOOL_NAME,
11
+ description: 'Present a complete Workspace-relative analysis draft as one saved document, automatically opened in the right-side report tab. HTML is generated only when explicitly exporting or publishing the report. Omit report_id and expected_build_id to create a Report; provide both to replace an existing Report with a new immutable Build based on the version you have read. A report-save-conflict requires reading the current report and reconciling the draft; never retry by only changing expected_build_id. Returns the exact published receipt, file locations, for the report reader. Only saved Artifact and computed snapshots are read; sources are declarations, not verification of calculations.',
12
+ parameters: {
13
+ draft_path: {
14
+ type: 'string',
15
+ required: true,
16
+ description: 'Workspace-relative path of the complete presentation draft JSON. Updates replace the whole report content; omitted content is not preserved.',
17
+ },
18
+ report_id: {
19
+ type: 'string',
20
+ description: 'Existing Report ID in the current Workspace to update. Must be provided together with expected_build_id; omit both to create a new Report.',
21
+ },
22
+ expected_build_id: {
23
+ type: 'string',
24
+ description: 'Build ID of the report version you have read and based this complete draft on. Must be provided together with report_id. A stale version fails with report-save-conflict; read and reconcile the current report before retrying.',
25
+ },
26
+ },
27
+ output: {
28
+ schema: {
29
+ type: 'object',
30
+ additionalProperties: false,
31
+ properties: { deliveryJson: { type: 'string', required: true } },
32
+ },
33
+ render: (_args, value) => [
34
+ {
35
+ type: 'text',
36
+ text: presentationReceiptText(parsePresentationDelivery(JSON.parse(value.deliveryJson)).receipt),
37
+ },
38
+ ],
39
+ presentationMeta: (_args, value) => JSON.parse(value.deliveryJson),
40
+ },
41
+ timeoutMs: 120_000,
42
+ async execute(args, exec) {
43
+ const signal = lifecycleSignal ? AbortSignal.any([exec.signal, lifecycleSignal]) : exec.signal;
44
+ if (typeof args.draft_path !== 'string' ||
45
+ args.draft_path.length === 0 ||
46
+ args.draft_path.length > 4096)
47
+ throw new Error('invalid-draft-path');
48
+ const hasReportId = args.report_id !== undefined;
49
+ const hasExpectedBuildId = args.expected_build_id !== undefined;
50
+ if (hasReportId !== hasExpectedBuildId)
51
+ throw new Error('invalid-report-update-target');
52
+ const reportId = hasReportId
53
+ ? parsePresentationBuildId(args.report_id, '/report_id')
54
+ : randomUUID();
55
+ const expectedBuildId = hasExpectedBuildId
56
+ ? parsePresentationBuildId(args.expected_build_id, '/expected_build_id')
57
+ : null;
58
+ const owner = exec.agent;
59
+ if (!owner || owner.session !== session)
60
+ throw new Error('presentation-session-mismatch');
61
+ const rootCallId = String(exec.rootCallId ?? exec.callId);
62
+ const call = [...session.snapshotEvents()]
63
+ .reverse()
64
+ .find((event) => event.type === 'tool/call' && String(event.data.callId) === rootCallId);
65
+ if (call?.type !== 'tool/call' || !Number.isSafeInteger(call.data.turn) || call.data.turn < 0)
66
+ throw new Error('presentation-turn-unavailable');
67
+ signal.throwIfAborted();
68
+ const binding = await source();
69
+ const identity = {
70
+ workspaceId: binding.workspaceId,
71
+ projectRoot: binding.projection.binding.projectRoot,
72
+ fingerprint: binding.projection.binding.fingerprint,
73
+ };
74
+ const check = async () => {
75
+ signal.throwIfAborted();
76
+ if (owner.session !== session)
77
+ throw new Error('presentation-session-mismatch');
78
+ const current = await source();
79
+ if (current.projection.status !== 'ready' ||
80
+ current.workspaceId !== identity.workspaceId ||
81
+ current.projection.binding.projectRoot !== identity.projectRoot ||
82
+ current.projection.binding.fingerprint !== identity.fingerprint ||
83
+ current.projection.binding !== binding.projection.binding)
84
+ throw new Error('presentation-workspace-changed');
85
+ signal.throwIfAborted();
86
+ };
87
+ await check();
88
+ if (expectedBuildId !== null) {
89
+ const current = await resolvePresentation(identity.projectRoot, identity.workspaceId, reportId, signal);
90
+ await check();
91
+ if (current.buildId !== expectedBuildId)
92
+ throw new Error('report-save-conflict');
93
+ }
94
+ const draft = await binding.projection.readDraft(args.draft_path, signal);
95
+ await check();
96
+ const document = await binding.projection.project(draft, {
97
+ workspaceId: binding.workspaceId,
98
+ reportId,
99
+ buildId: randomUUID(),
100
+ signal,
101
+ });
102
+ await check();
103
+ const receipt = await publishPresentation(identity.projectRoot, document, expectedBuildId, check, signal, { kind: 'agent', sessionId: String(session.id) });
104
+ return {
105
+ deliveryJson: JSON.stringify(parsePresentationDelivery({
106
+ kind: MARIVO_PRESENTATION_DELIVERY_KIND,
107
+ schemaVersion: 2,
108
+ dshSessionId: String(session.id),
109
+ turn: call.data.turn,
110
+ receipt,
111
+ })),
112
+ };
113
+ },
114
+ });
115
+ }
116
+ export function registerMarivoPresentTool(ctx, source, session) {
117
+ return registerMarivoTool(ctx, createMarivoPresentTool(source, session));
118
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,232 @@
1
+ import { createHash, 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 { resolveDshHome } from '@deepseek-ai/dsh-home-paths';
6
+ const CODE_BYTES = 131072;
7
+ const RECORD_BYTES = 262144;
8
+ const EXECUTION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
9
+ const DIGEST = /^[0-9a-f]{64}$/;
10
+ function invalid() {
11
+ throw new Error('Python execution source is unavailable or invalid');
12
+ }
13
+ function same(left, right) {
14
+ return left.dev === right.dev && left.ino === right.ino;
15
+ }
16
+ async function directory(filename) {
17
+ const stat = await lstat(filename);
18
+ if (!stat.isDirectory() || stat.isSymbolicLink() || (await realpath(filename)) !== filename)
19
+ invalid();
20
+ return stat;
21
+ }
22
+ async function directories(workspaceRoot, create, signal) {
23
+ signal?.throwIfAborted();
24
+ const root = await realpath(workspaceRoot);
25
+ if (root !== path.resolve(workspaceRoot))
26
+ invalid();
27
+ const workspaceIdentity = await directory(root);
28
+ const hostRoot = path.resolve(resolveDshHome());
29
+ const paths = [
30
+ hostRoot,
31
+ path.join(hostRoot, 'dsh-data-analysis'),
32
+ path.join(hostRoot, 'dsh-data-analysis', 'python-executions'),
33
+ path.join(hostRoot, 'dsh-data-analysis', 'python-executions', sha256(Buffer.from(root))),
34
+ ];
35
+ const identities = [];
36
+ const check = async (checkSignal = true) => {
37
+ if (checkSignal)
38
+ signal?.throwIfAborted();
39
+ if (!same(await directory(root), workspaceIdentity) ||
40
+ path.resolve(resolveDshHome()) !== hostRoot)
41
+ invalid();
42
+ for (const [index, expected] of identities.entries())
43
+ if (!same(await directory(paths[index]), expected))
44
+ invalid();
45
+ if (checkSignal)
46
+ signal?.throwIfAborted();
47
+ };
48
+ for (const [index, filename] of paths.entries()) {
49
+ await check();
50
+ if (create && index > 0) {
51
+ try {
52
+ await mkdir(filename, { mode: 0o700 });
53
+ }
54
+ catch (error) {
55
+ if (error.code !== 'EEXIST')
56
+ throw error;
57
+ }
58
+ }
59
+ identities.push(await directory(filename));
60
+ }
61
+ await check();
62
+ return { root, parent: paths[3], check };
63
+ }
64
+ function sourceText(text) {
65
+ return (typeof text === 'string' &&
66
+ !!text.trim() &&
67
+ !text.includes('\0') &&
68
+ !/[\uD800-\uDFFF]/u.test(text) &&
69
+ Buffer.byteLength(text) <= CODE_BYTES);
70
+ }
71
+ function timestamp(value) {
72
+ return (typeof value === 'string' &&
73
+ Number.isFinite(Date.parse(value)) &&
74
+ new Date(value).toISOString() === value);
75
+ }
76
+ function sha256(bytes) {
77
+ return createHash('sha256').update(bytes).digest('hex');
78
+ }
79
+ /** Publish a complete record once. Its final name never exposes a partial write or overwrites. */
80
+ export async function savePythonExecution(workspaceRoot, input, signal) {
81
+ try {
82
+ signal?.throwIfAborted();
83
+ const { text, startedAt, finishedAt } = input;
84
+ if (!sourceText(text) ||
85
+ !timestamp(startedAt) ||
86
+ !timestamp(finishedAt) ||
87
+ finishedAt < startedAt)
88
+ invalid();
89
+ const workspace = await directories(workspaceRoot, true, signal);
90
+ const executionId = randomUUID();
91
+ const bytes = Buffer.from(JSON.stringify({
92
+ schemaVersion: 1,
93
+ executionId,
94
+ projectRoot: workspace.root,
95
+ language: 'python',
96
+ text,
97
+ startedAt,
98
+ finishedAt,
99
+ exitCode: 0,
100
+ }));
101
+ if (bytes.length > RECORD_BYTES)
102
+ invalid();
103
+ const ref = { executionId, sha256: sha256(bytes) };
104
+ const temporary = path.join(workspace.parent, `.${executionId}.pending`);
105
+ const final = path.join(workspace.parent, `${executionId}.json`);
106
+ await workspace.check();
107
+ const file = await open(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
108
+ const identity = await file.stat();
109
+ let published = false;
110
+ try {
111
+ await file.writeFile(bytes, { signal });
112
+ await file.sync();
113
+ await workspace.check();
114
+ if (!same(await lstat(temporary), identity))
115
+ invalid();
116
+ // Hard-link publication is atomic and fails if a final path already exists.
117
+ await link(temporary, final);
118
+ published = true;
119
+ await workspace.check();
120
+ if (!same(await lstat(final), identity))
121
+ invalid();
122
+ const verified = await readPythonExecution(workspace.root, ref, signal);
123
+ if (verified.text !== text)
124
+ invalid();
125
+ await workspace.check();
126
+ return ref;
127
+ }
128
+ catch (error) {
129
+ if (published) {
130
+ try {
131
+ await workspace.check(false);
132
+ if (same(await lstat(final), identity))
133
+ await unlink(final);
134
+ }
135
+ catch {
136
+ /* Do not remove a replaced path or hide the original error. */
137
+ }
138
+ }
139
+ throw error;
140
+ }
141
+ finally {
142
+ await file.close();
143
+ try {
144
+ await workspace.check(false);
145
+ if (same(await lstat(temporary), identity))
146
+ await unlink(temporary);
147
+ }
148
+ catch {
149
+ /* Cleanup cannot override successful execution or follow a replaced parent. */
150
+ }
151
+ }
152
+ }
153
+ catch {
154
+ signal?.throwIfAborted();
155
+ invalid();
156
+ }
157
+ }
158
+ /** Resolve Host-stored execution identities for the bound Workspace. Never executes code. */
159
+ export async function readPythonExecution(workspaceRoot, ref, signal) {
160
+ try {
161
+ signal?.throwIfAborted();
162
+ if (!ref || !EXECUTION_ID.test(ref.executionId) || !DIGEST.test(ref.sha256))
163
+ invalid();
164
+ const { executionId, sha256: expectedDigest } = ref;
165
+ const workspace = await directories(workspaceRoot, false, signal);
166
+ const filename = path.join(workspace.parent, `${executionId}.json`);
167
+ const expected = await lstat(filename);
168
+ if (!expected.isFile() || expected.isSymbolicLink() || (await realpath(filename)) !== filename)
169
+ invalid();
170
+ const file = await open(filename, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
171
+ try {
172
+ const before = await file.stat();
173
+ if (!before.isFile() || !same(before, expected) || before.size > RECORD_BYTES)
174
+ invalid();
175
+ const bytes = Buffer.alloc(RECORD_BYTES + 1);
176
+ let length = 0;
177
+ while (length < bytes.length) {
178
+ signal?.throwIfAborted();
179
+ const { bytesRead } = await file.read(bytes, length, bytes.length - length, null);
180
+ if (!bytesRead)
181
+ break;
182
+ length += bytesRead;
183
+ }
184
+ const after = await file.stat();
185
+ const current = await lstat(filename);
186
+ await workspace.check();
187
+ if (length > RECORD_BYTES ||
188
+ length !== after.size ||
189
+ !same(current, after) ||
190
+ !current.isFile() ||
191
+ before.size !== after.size ||
192
+ before.mtimeMs !== after.mtimeMs ||
193
+ before.ctimeMs !== after.ctimeMs ||
194
+ (await realpath(filename)) !== filename)
195
+ invalid();
196
+ const raw = bytes.subarray(0, length);
197
+ if (sha256(raw) !== expectedDigest)
198
+ invalid();
199
+ const record = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(raw));
200
+ if (!record ||
201
+ typeof record !== 'object' ||
202
+ Array.isArray(record) ||
203
+ Object.keys(record).sort().join(',') !==
204
+ 'executionId,exitCode,finishedAt,language,projectRoot,schemaVersion,startedAt,text' ||
205
+ record.schemaVersion !== 1 ||
206
+ record.executionId !== executionId ||
207
+ record.projectRoot !== workspace.root ||
208
+ record.language !== 'python' ||
209
+ record.exitCode !== 0 ||
210
+ !sourceText(record.text) ||
211
+ !timestamp(record.startedAt) ||
212
+ !timestamp(record.finishedAt) ||
213
+ record.finishedAt < record.startedAt)
214
+ invalid();
215
+ signal?.throwIfAborted();
216
+ return {
217
+ language: 'python',
218
+ text: record.text,
219
+ provenance: 'execution',
220
+ executionId,
221
+ sha256: expectedDigest,
222
+ };
223
+ }
224
+ finally {
225
+ await file.close();
226
+ }
227
+ }
228
+ catch {
229
+ signal?.throwIfAborted();
230
+ invalid();
231
+ }
232
+ }
@@ -0,0 +1,111 @@
1
+ import { defineTool } from '@deepseek-ai/dsh-tools';
2
+ import { z } from 'zod';
3
+ import { PRESENTATION_BUDGETS, parsePresentationBuildId } from "../presentation/contracts/index.js";
4
+ import { registerPluginRpc } from "../rpc.js";
5
+ import { registerMarivoTool } from "../tool-lifecycle.js";
6
+ export const PUBLISHING_CHANNEL = '/dsh-report-publishing';
7
+ const identity = z.string().transform((value) => parsePresentationBuildId(value));
8
+ const field = z.enum(['accessKeyId', 'secretAccessKey', 'sessionToken']);
9
+ export function registerPublishingCredentials(connection, service, workspaceExists) {
10
+ return registerPluginRpc(connection, PUBLISHING_CHANNEL, ['describe', 'set', 'unset', 'publish'], async (endpoint, payload, signal) => {
11
+ try {
12
+ const base = { workspaceId: z.string().min(1).max(512) };
13
+ const { workspaceId } = z.object(base).parse(payload);
14
+ if (!workspaceExists(workspaceId))
15
+ throw new Error('workspace-unavailable');
16
+ signal.throwIfAborted();
17
+ const mutation = { ...base, configId: z.string().uuid() };
18
+ if (endpoint !== 'describe')
19
+ service.assertConfig(z.object(mutation).parse(payload).configId);
20
+ if (endpoint === 'publish') {
21
+ const input = z
22
+ .object({
23
+ ...mutation,
24
+ reportId: identity,
25
+ buildId: identity,
26
+ viewHtml: z.string().max(PRESENTATION_BUDGETS.htmlBytes).optional(),
27
+ })
28
+ .strict()
29
+ .parse(payload);
30
+ return {
31
+ ok: true,
32
+ value: await service.publish({ workspaceId }, input.reportId, input.buildId, AbortSignal.any([signal, AbortSignal.timeout(110_000)]), input.viewHtml),
33
+ };
34
+ }
35
+ if (endpoint === 'set') {
36
+ const input = z
37
+ .object({ ...mutation, field, value: z.string().min(1).max(65536) })
38
+ .strict()
39
+ .parse(payload);
40
+ await service.change(input.field, input.value, signal);
41
+ }
42
+ else if (endpoint === 'unset') {
43
+ const input = z
44
+ .object({ ...mutation, field })
45
+ .strict()
46
+ .parse(payload);
47
+ await service.change(input.field, undefined, signal);
48
+ }
49
+ else
50
+ z.object(base).strict().parse(payload);
51
+ return { ok: true, value: await service.describe() };
52
+ }
53
+ catch (error) {
54
+ const message = publishingError(error);
55
+ return { ok: false, error: { code: 'internal', message, details: {} } };
56
+ }
57
+ });
58
+ }
59
+ export function registerReportPublishTool(ctx, service, sessionId) {
60
+ return registerMarivoTool(ctx, defineTool({
61
+ name: 'marivo_publish_report',
62
+ description: 'Publish a saved report Build as self-contained HTML to the configured object storage only when the user requests publication. Returns the immutable URL. Does not run analysis or include unsaved edits or temporary filters. Credentials are configured under 数据源与凭证 → 报告发布凭证. An unconfirmed upload may already exist; retry the same Build safely. Never send credentials or storage settings as tool arguments.',
63
+ parameters: {
64
+ report_id: {
65
+ type: 'string',
66
+ required: true,
67
+ description: 'Saved Report ID from the presentation receipt.',
68
+ },
69
+ build_id: {
70
+ type: 'string',
71
+ required: true,
72
+ description: 'Exact saved Build ID to publish.',
73
+ },
74
+ },
75
+ output: {
76
+ schema: {
77
+ type: 'object',
78
+ properties: { publicationJson: { type: 'string', required: true } },
79
+ additionalProperties: false,
80
+ },
81
+ render: (_args, value) => [{ type: 'text', text: value.publicationJson }],
82
+ },
83
+ timeoutMs: 120_000,
84
+ async execute(args, exec) {
85
+ if (!exec.agent || exec.agent.session.id !== sessionId)
86
+ throw new Error('presentation-session-mismatch');
87
+ const input = z.object({ report_id: identity, build_id: identity }).strict().parse(args);
88
+ try {
89
+ return {
90
+ publicationJson: JSON.stringify(await service.publish({ sessionId }, input.report_id, input.build_id, AbortSignal.any([exec.signal, AbortSignal.timeout(110_000)]))),
91
+ };
92
+ }
93
+ catch (error) {
94
+ throw new Error(publishingError(error));
95
+ }
96
+ },
97
+ }));
98
+ }
99
+ function publishingError(error) {
100
+ return error instanceof Error &&
101
+ [
102
+ 'report-publishing-config-changed',
103
+ 'report-publishing-disabled',
104
+ 'report-publishing-build-unavailable',
105
+ 'report-publishing-credentials-missing',
106
+ 'report-publishing-upload-unconfirmed',
107
+ 'report-publishing-view-invalid',
108
+ ].includes(error.message)
109
+ ? error.message
110
+ : 'report-publishing-operation-failed';
111
+ }