@chengxianglibra/dsh-data-analysis 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/README.en.md +135 -0
  2. package/README.md +105 -98
  3. package/cordis.patch.yml +1 -1
  4. package/lib/bin/environment.js +8 -6
  5. package/lib/client/credentials/changes.js +74 -0
  6. package/lib/client/credentials/create-datasource.js +126 -22
  7. package/lib/client/credentials/credential-draft.js +42 -0
  8. package/lib/client/credentials/defaults.js +11 -0
  9. package/lib/client/credentials/descriptions.js +46 -0
  10. package/lib/client/credentials/install.js +209 -81
  11. package/lib/client/credentials/model.js +323 -51
  12. package/lib/client/credentials/report-publishing.js +96 -0
  13. package/lib/client/credentials/styles.js +13 -9
  14. package/lib/client/i18n/context.js +22 -0
  15. package/lib/client/i18n/copy.js +60 -0
  16. package/lib/client/i18n/credentials.js +475 -0
  17. package/lib/client/i18n/host.js +28 -0
  18. package/lib/client/i18n/navigation.js +41 -0
  19. package/lib/client/i18n/presentation.js +577 -0
  20. package/lib/client/i18n/python.js +36 -0
  21. package/lib/client/i18n/semantic.js +441 -0
  22. package/lib/client/i18n/settings.js +30 -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/settings/card.js +90 -0
  36. package/lib/client/settings/install.js +10 -0
  37. package/lib/client/workspace-header-action.js +14 -1
  38. package/lib/client/workspace-shortcuts.js +26 -0
  39. package/lib/client.js +388 -242
  40. package/lib/compatibility.js +1 -1
  41. package/lib/datasource/authoring.js +43 -4
  42. package/lib/datasource/bridge-programs.js +23 -20
  43. package/lib/datasource/bridge.js +46 -3
  44. package/lib/datasource/changes-contract.js +41 -0
  45. package/lib/datasource/changes.js +49 -0
  46. package/lib/datasource/configure.js +39 -0
  47. package/lib/datasource/defaults.js +25 -0
  48. package/lib/datasource/index.js +2 -1
  49. package/lib/datasource/python-options.js +15 -0
  50. package/lib/datasource/python.js +119 -25
  51. package/lib/datasource/rpc.js +75 -13
  52. package/lib/datasource/service.js +286 -11
  53. package/lib/datasource/test.js +3 -2
  54. package/lib/disclosure/activation.js +19 -7
  55. package/lib/disclosure/execution-guidance.js +49 -0
  56. package/lib/disclosure/help.js +4 -1
  57. package/lib/environment/runtime.js +35 -34
  58. package/lib/environment/workspace.js +18 -1
  59. package/lib/lifecycle.js +33 -0
  60. package/lib/plugin-agents.js +154 -0
  61. package/lib/plugin.js +98 -172
  62. package/lib/presentation/asset.js +34 -0
  63. package/lib/presentation/assets/portable.js +14 -14
  64. package/lib/presentation/assets/static.js +344 -73
  65. package/lib/presentation/build/index.js +3 -2
  66. package/lib/presentation/commit.js +19 -12
  67. package/lib/presentation/contracts/asset-path.js +9 -0
  68. package/lib/presentation/contracts/catalog.js +62 -0
  69. package/lib/presentation/contracts/charts.js +0 -23
  70. package/lib/presentation/contracts/index.js +17 -5
  71. package/lib/presentation/contracts/types.js +1 -1
  72. package/lib/presentation/delivery.js +45 -30
  73. package/lib/presentation/export-html.js +214 -0
  74. package/lib/presentation/files.js +4 -5
  75. package/lib/presentation/projection/index.js +2 -1
  76. package/lib/presentation/projection/program.js +2 -9
  77. package/lib/presentation/projection/sql-code.js +13 -13
  78. package/lib/presentation/reports.js +114 -17
  79. package/lib/presentation/rpc.js +96 -58
  80. package/lib/presentation/tool.js +6 -10
  81. package/lib/report-publishing/adapters.js +111 -0
  82. package/lib/report-publishing/config.js +69 -0
  83. package/lib/report-publishing/service.js +180 -0
  84. package/lib/rpc.js +49 -0
  85. package/lib/semantic-browser/contracts.js +17 -0
  86. package/lib/semantic-browser/program.js +10 -0
  87. package/lib/semantic-browser/service.js +40 -6
  88. package/lib/semantic-reference/bridge.js +10 -1
  89. package/lib/semantic-reference/contracts.js +20 -6
  90. package/lib/semantic-reference/environment.js +56 -0
  91. package/lib/semantic-reference/labels.js +16 -0
  92. package/lib/semantic-reference/rpc.js +39 -18
  93. package/lib/semantic-reference/search.js +5 -4
  94. package/lib/semantic-reference/usage.js +12 -3
  95. package/lib/settings.js +21 -0
  96. package/lib/tool-lifecycle.js +22 -0
  97. package/lib/types/client/credentials/changes.d.ts +9 -0
  98. package/lib/types/client/credentials/create-datasource.d.ts +3 -1
  99. package/lib/types/client/credentials/credential-draft.d.ts +14 -0
  100. package/lib/types/client/credentials/defaults.d.ts +2 -0
  101. package/lib/types/client/credentials/descriptions.d.ts +1 -0
  102. package/lib/types/client/credentials/install.d.ts +19 -1
  103. package/lib/types/client/credentials/model.d.ts +31 -11
  104. package/lib/types/client/credentials/report-publishing.d.ts +5 -0
  105. package/lib/types/client/credentials/styles.d.ts +1 -1
  106. package/lib/types/client/i18n/context.d.ts +17 -0
  107. package/lib/types/client/i18n/copy.d.ts +821 -0
  108. package/lib/types/client/i18n/credentials.d.ts +475 -0
  109. package/lib/types/client/i18n/host.d.ts +17 -0
  110. package/lib/types/client/i18n/navigation.d.ts +41 -0
  111. package/lib/types/client/i18n/presentation.d.ts +577 -0
  112. package/lib/types/client/i18n/python.d.ts +19 -0
  113. package/lib/types/client/i18n/semantic.d.ts +441 -0
  114. package/lib/types/client/i18n/settings.d.ts +16 -0
  115. package/lib/types/client/input-context.d.ts +19 -0
  116. package/lib/types/client/presentation/ask-dsh.d.ts +4 -8
  117. package/lib/types/client/presentation/catalog-model.d.ts +24 -0
  118. package/lib/types/client/presentation/catalog.d.ts +20 -0
  119. package/lib/types/client/presentation/chart-tooltip.d.ts +2 -2
  120. package/lib/types/client/presentation/context-reference.d.ts +12 -0
  121. package/lib/types/client/presentation/copy-context.d.ts +2 -2
  122. package/lib/types/client/presentation/delivery-model.d.ts +27 -6
  123. package/lib/types/client/presentation/delivery.d.ts +8 -7
  124. package/lib/types/client/presentation/download.d.ts +2 -0
  125. package/lib/types/client/presentation/export-menu.d.ts +26 -0
  126. package/lib/types/client/presentation/export-view.d.ts +63 -0
  127. package/lib/types/client/presentation/host-entry.d.ts +8 -2
  128. package/lib/types/client/presentation/icons.d.ts +1 -1
  129. package/lib/types/client/presentation/model.d.ts +14 -15
  130. package/lib/types/client/presentation/reader.d.ts +15 -2
  131. package/lib/types/client/presentation/reference-source.d.ts +9 -0
  132. package/lib/types/client/presentation/source-code-model.d.ts +2 -1
  133. package/lib/types/client/presentation/source-code.d.ts +0 -5
  134. package/lib/types/client/presentation/source-dialog.d.ts +1 -2
  135. package/lib/types/client/presentation/sources.d.ts +5 -5
  136. package/lib/types/client/presentation/styles.d.ts +1 -1
  137. package/lib/types/client/presentation/table.d.ts +5 -1
  138. package/lib/types/client/python-tool/card.d.ts +2 -0
  139. package/lib/types/client/python-tool/execution-error.d.ts +3 -0
  140. package/lib/types/client/python-tool/install.d.ts +2 -0
  141. package/lib/types/client/python-tool/model.d.ts +20 -0
  142. package/lib/types/client/right-tabs/install.d.ts +18 -0
  143. package/lib/types/client/right-tabs/live-delivery.d.ts +8 -0
  144. package/lib/types/client/right-tabs/navigation.d.ts +17 -0
  145. package/lib/types/client/right-tabs/page.d.ts +36 -0
  146. package/lib/types/client/right-tabs/styles.d.ts +2 -0
  147. package/lib/types/client/rpc.d.ts +3 -0
  148. package/lib/types/client/semantic-reference.d.ts +4 -0
  149. package/lib/types/client/settings/card.d.ts +9 -0
  150. package/lib/types/client/settings/install.d.ts +2 -0
  151. package/lib/types/client/workspace-header-action.d.ts +1 -1
  152. package/lib/types/client/workspace-shortcuts.d.ts +15 -0
  153. package/lib/types/client.d.ts +5 -2
  154. package/lib/types/compatibility.d.ts +1 -1
  155. package/lib/types/datasource/authoring.d.ts +10 -0
  156. package/lib/types/datasource/bridge.d.ts +23 -2
  157. package/lib/types/datasource/changes-contract.d.ts +18 -0
  158. package/lib/types/datasource/changes.d.ts +15 -0
  159. package/lib/types/datasource/configure.d.ts +7 -0
  160. package/lib/types/datasource/defaults.d.ts +7 -0
  161. package/lib/types/datasource/index.d.ts +6 -1
  162. package/lib/types/datasource/python-options.d.ts +8 -0
  163. package/lib/types/datasource/python.d.ts +17 -2
  164. package/lib/types/datasource/rpc.d.ts +2 -1
  165. package/lib/types/datasource/service.d.ts +48 -5
  166. package/lib/types/datasource/test.d.ts +1 -1
  167. package/lib/types/disclosure/activation.d.ts +2 -1
  168. package/lib/types/disclosure/execution-guidance.d.ts +6 -0
  169. package/lib/types/disclosure/help.d.ts +1 -1
  170. package/lib/types/environment/types.d.ts +2 -1
  171. package/lib/types/environment/workspace.d.ts +1 -0
  172. package/lib/types/lifecycle.d.ts +8 -0
  173. package/lib/types/plugin-agents.d.ts +29 -0
  174. package/lib/types/plugin.d.ts +12 -28
  175. package/lib/types/presentation/asset.d.ts +6 -0
  176. package/lib/types/presentation/build/index.d.ts +7 -2
  177. package/lib/types/presentation/commit.d.ts +1 -1
  178. package/lib/types/presentation/contracts/asset-path.d.ts +2 -0
  179. package/lib/types/presentation/contracts/catalog.d.ts +27 -0
  180. package/lib/types/presentation/contracts/charts.d.ts +0 -2
  181. package/lib/types/presentation/contracts/types.d.ts +7 -4
  182. package/lib/types/presentation/delivery.d.ts +1 -1
  183. package/lib/types/presentation/export-html.d.ts +13 -0
  184. package/lib/types/presentation/reports.d.ts +6 -1
  185. package/lib/types/presentation/rpc.d.ts +3 -2
  186. package/lib/types/presentation/tool.d.ts +2 -2
  187. package/lib/types/report-publishing/adapters.d.ts +6 -0
  188. package/lib/types/report-publishing/config.d.ts +22 -0
  189. package/lib/types/report-publishing/service.d.ts +68 -0
  190. package/lib/types/rpc.d.ts +3 -0
  191. package/lib/types/semantic-browser/contracts.d.ts +7 -0
  192. package/lib/types/semantic-browser/service.d.ts +7 -1
  193. package/lib/types/semantic-reference/bridge.d.ts +1 -0
  194. package/lib/types/semantic-reference/contracts.d.ts +5 -2
  195. package/lib/types/semantic-reference/environment.d.ts +22 -0
  196. package/lib/types/semantic-reference/labels.d.ts +16 -0
  197. package/lib/types/semantic-reference/rpc.d.ts +1 -1
  198. package/lib/types/semantic-reference/search.d.ts +1 -1
  199. package/lib/types/settings.d.ts +14 -0
  200. package/lib/types/tool-lifecycle.d.ts +4 -0
  201. package/lib/types/workspace-identity.d.ts +6 -0
  202. package/lib/workspace-identity.js +9 -0
  203. package/package.json +93 -61
  204. package/python/presentation-kit/dist/dsh_data_analysis_presentation_kit-1.1.0-py3-none-any.whl +0 -0
  205. package/skills/dsh-data-analysis-files/SKILL.md +44 -0
  206. package/skills/dsh-data-analysis-files/references/delivery.md +15 -0
  207. package/skills/dsh-data-analysis-files/references/examples.md +93 -0
  208. package/skills/dsh-data-analysis-presentation/SKILL.md +15 -15
  209. package/skills/dsh-data-analysis-presentation/references/examples/artifact.draft.json +13 -4
  210. package/skills/dsh-data-analysis-presentation/references/examples/charts.draft.json +3 -2
  211. package/skills/dsh-data-analysis-presentation/references/examples/comparison.draft.json +3 -2
  212. package/skills/dsh-data-analysis-presentation/references/examples/computed-sources.draft.json +12 -5
  213. package/skills/dsh-data-analysis-presentation/references/examples/computed.draft.json +3 -2
  214. package/skills/dsh-data-analysis-presentation/references/examples/interaction.draft.json +3 -2
  215. package/skills/dsh-data-analysis-presentation/references/examples/source-only.draft.json +12 -4
  216. package/skills/dsh-data-analysis-presentation/references/examples.md +3 -1
  217. package/skills/dsh-data-analysis-presentation/references/export-html.md +7 -0
  218. package/skills/dsh-data-analysis-presentation/references/interaction.md +1 -1
  219. package/skills/dsh-data-analysis-presentation/references/narrative.md +1 -1
  220. package/skills/dsh-data-analysis-presentation/references/publishing.md +3 -0
  221. package/skills/dsh-data-analysis-presentation/references/schema.md +29 -2
  222. package/lib/types/client/presentation/install.d.ts +0 -15
@@ -12,7 +12,7 @@ def sql_code_snapshot(session, artifact):
12
12
  succeeded_run = mv.SucceededRun
13
13
  get_run = session.get_run
14
14
  except Exception:
15
- return {"snippets": [], "notices": ["当前 Marivo 未提供读取持久化 SQL 所需的公开 Run 能力。"]}
15
+ return {"snippets": [], "notices": ["marivo.presentation.sql-capability-unavailable"]}
16
16
 
17
17
  pending = [(artifact.ref, artifact)]
18
18
  scheduled, runs, seen_queries = {artifact.ref}, {}, set()
@@ -22,30 +22,30 @@ def sql_code_snapshot(session, artifact):
22
22
  current = known_artifact if known_artifact is not None else session.artifact(expected_ref)
23
23
  meta, contract = current.meta, current.contract()
24
24
  if current.ref != expected_ref or contract.ref != expected_ref or meta.session_id != session.id or Path(meta.project_root).resolve() != Path.cwd().resolve():
25
- notice("部分上游 Artifact 的身份或 Workspace 不一致,已拒绝读取其 SQL。")
25
+ notice("marivo.presentation.sql-artifact-mismatch")
26
26
  continue
27
27
  producer = meta.produced_by_job
28
28
  if producer is None:
29
- notice("部分 Artifact 未保存生产 Run 标识,无法读取其生成 SQL。")
29
+ notice("marivo.presentation.sql-producer-missing")
30
30
  continue
31
31
  if not valid_string(producer, 512):
32
- notice("部分 Artifact 的生产 Run 标识无效,已拒绝读取其 SQL。")
32
+ notice("marivo.presentation.sql-producer-invalid")
33
33
  continue
34
34
  run = runs.get(producer)
35
35
  if run is None:
36
36
  run = get_run(producer)
37
37
  runs[producer] = run
38
38
  if not isinstance(run, succeeded_run) or run.run_id != producer or run.output_mode != "produced" or run.output_artifact_ref != expected_ref:
39
- notice("部分生产 Run 的身份、完成状态或产出不匹配,已拒绝读取其 SQL。")
39
+ notice("marivo.presentation.sql-run-mismatch")
40
40
  continue
41
41
  for query in run.queries:
42
42
  if query_count >= 32:
43
- notice("SQL 快照最多读取 32 条查询记录,其余记录未纳入。")
43
+ notice("marivo.presentation.sql-query-limit")
44
44
  break
45
45
  query_count += 1
46
46
  query_id = query.query_id
47
47
  if not valid_string(query_id, 512):
48
- notice("部分 SQL 记录的查询标识无效,未纳入代码快照。")
48
+ notice("marivo.presentation.sql-query-invalid")
49
49
  continue
50
50
  query_key = (producer, query_id)
51
51
  if query_key in seen_queries:
@@ -53,27 +53,27 @@ def sql_code_snapshot(session, artifact):
53
53
  seen_queries.add(query_key)
54
54
  sql = query.sql
55
55
  if not valid_string(sql, 32768) or not sql.strip():
56
- notice("部分 SQL 为空、包含不支持的字符或超过 32768 字符,未截断也未纳入快照。")
56
+ notice("marivo.presentation.sql-sql-invalid")
57
57
  continue
58
58
  snippets.append({"language": "sql", "text": sql, "provenance": "execution", "runId": producer, "queryId": query_id, "artifactRef": expected_ref})
59
59
  for input_index, input_ref in enumerate(run.input_artifact_refs):
60
60
  if input_index >= 65:
61
- notice("SQL 追溯最多读取 64 个上游 Artifact,其余来源未纳入。")
61
+ notice("marivo.presentation.sql-upstream-limit")
62
62
  break
63
63
  if not valid_string(input_ref, 512):
64
- notice("部分上游 Artifact 标识无效,已停止该分支的 SQL 追溯。")
64
+ notice("marivo.presentation.sql-upstream-invalid")
65
65
  continue
66
66
  if input_ref in scheduled:
67
67
  continue
68
68
  if len(scheduled) >= 65:
69
- notice("SQL 追溯最多读取 64 个上游 Artifact,其余来源未纳入。")
69
+ notice("marivo.presentation.sql-upstream-limit")
70
70
  break
71
71
  scheduled.add(input_ref)
72
72
  pending.append((input_ref, None))
73
73
  except Exception:
74
74
  # A missing record or capability affects code only. Never return exception text.
75
- notice("部分持久化生产记录无法通过当前 Workspace 的公开 API 读取,SQL 快照可能不完整。")
75
+ notice("marivo.presentation.sql-read-unavailable")
76
76
  if not snippets and not notices:
77
- notice("该 Artifact 及已读取的上游生产记录未保存 SQL。")
77
+ notice("marivo.presentation.sql-not-saved")
78
78
  return {"snippets": snippets, "notices": notices}
79
79
  `.trim();
@@ -1,10 +1,11 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { constants } from 'node:fs';
3
- import { lstat, open, realpath, rename, rm } from 'node:fs/promises';
3
+ import { lstat, open, opendir, realpath, rename, rm } from 'node:fs/promises';
4
4
  import path from 'node:path';
5
5
  import { withFileLock } from '@deepseek-ai/dsh-atomic-write';
6
6
  import { buildPresentation } from "./build/index.js";
7
7
  import { commitPresentation } from "./commit.js";
8
+ import { parseReportHistory, REPORT_HISTORY_BYTES, REPORT_HISTORY_LIMIT, } from "./contracts/catalog.js";
8
9
  import { parsePresentationDocument, parsePresentationReceipt, } from "./contracts/index.js";
9
10
  import { presentationAssetPath, presentationReportPath, presentationSha256, readPresentationAsset, readReportFile, } from "./files.js";
10
11
  export async function readReceiptDocument(root, receipt, signal) {
@@ -24,26 +25,101 @@ export async function readReceiptDocument(root, receipt, signal) {
24
25
  throw new Error('asset-owner-mismatch');
25
26
  return document;
26
27
  }
27
- export async function resolvePresentation(root, workspaceId, reportId, signal) {
28
- const bytes = await readReportFile(root, path.join(presentationReportPath(root, reportId), 'current.json'), 20 * 1024, signal);
28
+ /** Current and its publication history become visible through one atomic rename. */
29
+ export async function readReportHistory(root, workspaceId, reportId, signal) {
30
+ const bytes = await readReportFile(root, path.join(presentationReportPath(root, reportId), 'current.json'), REPORT_HISTORY_BYTES, signal);
29
31
  const current = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
30
- if (!current ||
31
- Object.keys(current).sort().join(',') !== 'receipt,reportId,schemaVersion,workspaceId' ||
32
- current.schemaVersion !== 2 ||
33
- current.workspaceId !== workspaceId ||
34
- current.reportId !== reportId)
32
+ if (!current || current.workspaceId !== workspaceId || current.reportId !== reportId)
35
33
  throw new Error('invalid-report-current');
36
34
  const receipt = parsePresentationReceipt(current.receipt);
37
35
  if (receipt.workspaceId !== workspaceId || receipt.reportId !== reportId)
38
36
  throw new Error('asset-owner-mismatch');
37
+ if (current.schemaVersion === 2 &&
38
+ Object.keys(current).sort().join(',') === 'receipt,reportId,schemaVersion,workspaceId') {
39
+ return {
40
+ workspaceId,
41
+ reportId,
42
+ currentBuildId: receipt.buildId,
43
+ legacyHistoryUnavailable: true,
44
+ versions: [{ receipt, publishedAt: null, source: null }],
45
+ };
46
+ }
47
+ if (current.schemaVersion !== 3 ||
48
+ Object.keys(current).sort().join(',') !==
49
+ 'legacyHistoryUnavailable,receipt,reportId,schemaVersion,versions,workspaceId')
50
+ throw new Error('invalid-report-current');
51
+ const history = parseReportHistory({
52
+ workspaceId,
53
+ reportId,
54
+ currentBuildId: receipt.buildId,
55
+ legacyHistoryUnavailable: current.legacyHistoryUnavailable,
56
+ versions: current.versions,
57
+ });
58
+ if (JSON.stringify(history.versions[0].receipt) !== JSON.stringify(receipt))
59
+ throw new Error('invalid-report-history');
60
+ return history;
61
+ }
62
+ export async function resolvePresentation(root, workspaceId, reportId, signal) {
63
+ const history = await readReportHistory(root, workspaceId, reportId, signal);
64
+ const receipt = history.versions[0].receipt;
39
65
  await readReceiptDocument(root, receipt, signal);
40
66
  return receipt;
41
67
  }
68
+ /** Enumerate only publication pointers, never completed-but-unpublished build directories. */
69
+ export async function listReports(root, workspaceId, signal) {
70
+ const directory = path.dirname(presentationReportPath(root, 'catalog'));
71
+ const parents = [root, path.dirname(directory), directory];
72
+ const identities = [];
73
+ try {
74
+ for (const name of parents) {
75
+ const stat = await lstat(name);
76
+ if (!stat.isDirectory() || stat.isSymbolicLink() || (await realpath(name)) !== name)
77
+ throw new Error('asset-path-mismatch');
78
+ identities.push(stat);
79
+ }
80
+ }
81
+ catch (error) {
82
+ if (error.code === 'ENOENT')
83
+ return { workspaceId, reports: [], unavailable: 0 };
84
+ throw error;
85
+ }
86
+ const reports = [];
87
+ let unavailable = 0, count = 0;
88
+ for await (const entry of await opendir(directory)) {
89
+ signal?.throwIfAborted();
90
+ if (++count > 4096)
91
+ throw new Error('report-catalog-too-large');
92
+ if (entry.name.startsWith('.'))
93
+ continue;
94
+ try {
95
+ if (!entry.isDirectory() || entry.isSymbolicLink())
96
+ throw new Error('asset-not-file');
97
+ const history = await readReportHistory(root, workspaceId, entry.name, signal);
98
+ reports.push(history.versions[0]);
99
+ }
100
+ catch (error) {
101
+ signal?.throwIfAborted();
102
+ if (error.code !== 'ENOENT')
103
+ unavailable++;
104
+ }
105
+ }
106
+ for (const [index, name] of parents.entries()) {
107
+ const stat = await lstat(name);
108
+ if (stat.dev !== identities[index].dev ||
109
+ stat.ino !== identities[index].ino ||
110
+ stat.isSymbolicLink() ||
111
+ (await realpath(name)) !== name)
112
+ throw new Error('asset-path-mismatch');
113
+ }
114
+ reports.sort((a, b) => (b.publishedAt ?? '').localeCompare(a.publishedAt ?? '') ||
115
+ a.receipt.reportId.localeCompare(b.receipt.reportId));
116
+ return { workspaceId, reports, unavailable };
117
+ }
42
118
  /** The mutable pointer is the publication boundary; completed builds are never overwritten. */
43
- export async function publishPresentation(root, document, expectedBuildId, assertOwner, signal) {
119
+ export async function publishPresentation(root, document, expectedBuildId, assertOwner, signal, source = null) {
44
120
  await assertOwner();
45
121
  signal?.throwIfAborted();
46
- const built = await buildPresentation(document);
122
+ const built = await buildPresentation(document, { html: false });
47
123
  await assertOwner();
48
124
  const receipt = await commitPresentation(root, built, assertOwner, signal);
49
125
  const directory = presentationReportPath(root, document.reportId);
@@ -66,9 +142,12 @@ export async function publishPresentation(root, document, expectedBuildId, asser
66
142
  await check();
67
143
  await withFileLock(currentPath, async () => {
68
144
  await check();
145
+ let history;
69
146
  let current;
70
147
  try {
71
- current = await resolvePresentation(root, document.workspaceId, document.reportId, signal);
148
+ history = await readReportHistory(root, document.workspaceId, document.reportId, signal);
149
+ current = history.versions[0].receipt;
150
+ await readReceiptDocument(root, current, signal);
72
151
  }
73
152
  catch (error) {
74
153
  if (expectedBuildId !== null || error.code !== 'ENOENT')
@@ -85,17 +164,35 @@ export async function publishPresentation(root, document, expectedBuildId, asser
85
164
  }
86
165
  if ((current?.buildId ?? null) !== expectedBuildId)
87
166
  throw new Error('report-save-conflict');
167
+ const versions = [
168
+ { receipt, publishedAt: new Date().toISOString(), source },
169
+ ...(history?.versions ?? []),
170
+ ];
171
+ if (versions.length > REPORT_HISTORY_LIMIT)
172
+ throw new Error('report-history-full');
173
+ parseReportHistory({
174
+ workspaceId: document.workspaceId,
175
+ reportId: document.reportId,
176
+ currentBuildId: receipt.buildId,
177
+ legacyHistoryUnavailable: history?.legacyHistoryUnavailable ?? false,
178
+ versions,
179
+ });
180
+ const pointer = JSON.stringify({
181
+ schemaVersion: 3,
182
+ workspaceId: document.workspaceId,
183
+ reportId: document.reportId,
184
+ receipt,
185
+ versions,
186
+ legacyHistoryUnavailable: history?.legacyHistoryUnavailable ?? false,
187
+ });
188
+ if (Buffer.byteLength(pointer) > REPORT_HISTORY_BYTES)
189
+ throw new Error('report-history-full');
88
190
  const temporary = path.join(directory, `.current-${randomUUID()}.tmp`);
89
191
  let published = false;
90
192
  try {
91
193
  const file = await open(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
92
194
  try {
93
- await file.writeFile(JSON.stringify({
94
- schemaVersion: 2,
95
- workspaceId: document.workspaceId,
96
- reportId: document.reportId,
97
- receipt,
98
- }), { signal });
195
+ await file.writeFile(pointer, { signal });
99
196
  await file.sync();
100
197
  }
101
198
  finally {
@@ -1,48 +1,101 @@
1
1
  import { randomUUID } from 'node:crypto';
2
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";
3
6
  import { applyPresentationEdits } from "./contracts/editing.js";
4
- import { parsePresentationBuildId, parsePresentationDocument, parsePresentationReceipt, } from "./contracts/index.js";
5
- import { presentationAssetPath, presentationSha256, readPresentationAsset } from "./files.js";
7
+ import { PresentationContractError, parsePresentationBuildId, parsePresentationReceipt, } from "./contracts/index.js";
6
8
  import { MARIVO_PRESENTATION_RPC_CHANNEL } from "./receipt.js";
7
- import { publishPresentation, readReceiptDocument, resolvePresentation } from "./reports.js";
9
+ import { listReports, publishPresentation, readReceiptDocument, readReportHistory, resolvePresentation, } from "./reports.js";
8
10
  export class MarivoPresentationFileService {
9
11
  #resolve;
12
+ #resolveId;
10
13
  #abort = new AbortController();
11
- constructor(resolve) {
14
+ #tasks = new PendingTasks();
15
+ #closing;
16
+ constructor(resolve, resolveId) {
12
17
  this.#resolve = resolve;
18
+ this.#resolveId = resolveId;
13
19
  }
14
- async #workspace(sessionId) {
20
+ async #workspace(input) {
15
21
  try {
16
- return await this.#resolve(sessionId);
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);
17
29
  }
18
30
  catch {
19
31
  throw new Error('workspace-unavailable');
20
32
  }
21
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
+ }
22
69
  close() {
23
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));
24
76
  }
25
- async report(endpoint, payload, caller = new AbortController().signal) {
77
+ async #report(endpoint, payload, caller = new AbortController().signal) {
26
78
  const signal = AbortSignal.any([caller, this.#abort.signal]);
79
+ signal.throwIfAborted();
27
80
  if (!payload || typeof payload !== 'object' || Array.isArray(payload))
28
81
  throw new Error('invalid-request');
29
82
  const input = payload;
30
- const expected = endpoint === 'reports/save'
31
- ? 'edits,expectedBuildId,reportId,sessionId'
32
- : 'reportId,sessionId';
33
- if (Object.keys(input).sort().join(',') !== expected ||
34
- typeof input.sessionId !== 'string' ||
35
- !input.sessionId.trim() ||
36
- input.sessionId.length > 512)
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)
37
90
  throw new Error('invalid-request');
38
91
  const reportId = parsePresentationBuildId(input.reportId, '/reportId');
39
- const workspace = await this.#workspace(input.sessionId);
92
+ const workspace = await this.#workspace(input);
40
93
  if (!workspace)
41
94
  throw new Error('workspace-unavailable');
42
95
  const root = path.resolve(workspace.path);
43
96
  const check = async () => {
44
97
  signal.throwIfAborted();
45
- const current = await this.#workspace(input.sessionId);
98
+ const current = await this.#workspace(input);
46
99
  if (!current || current.id !== workspace.id || path.resolve(current.path) !== root)
47
100
  throw new Error('workspace-changed');
48
101
  };
@@ -63,46 +116,29 @@ export class MarivoPresentationFileService {
63
116
  throw new Error('invalid-report-edits');
64
117
  }
65
118
  await check();
66
- return publishPresentation(root, { ...edited, buildId: randomUUID(), generatedAt: new Date().toISOString() }, expectedBuildId, check, signal);
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));
67
123
  }
68
- async read(payload, caller = new AbortController().signal) {
124
+ async #read(payload, caller = new AbortController().signal) {
69
125
  const signal = AbortSignal.any([caller, this.#abort.signal]);
70
126
  signal.throwIfAborted();
71
127
  if (!payload || typeof payload !== 'object' || Array.isArray(payload))
72
128
  throw new Error('invalid-request');
73
129
  const input = payload;
74
- if (Object.keys(input).sort().join(',') !== 'asset,receipt,sessionId' ||
75
- typeof input.sessionId !== 'string' ||
76
- !input.sessionId.trim() ||
77
- input.sessionId.length > 512 ||
130
+ if (Object.keys(input).sort().join(',') !==
131
+ ['asset', 'receipt', this.#scope(input)].sort().join(',') ||
78
132
  (input.asset !== 'presentation.json' && input.asset !== 'index.html'))
79
133
  throw new Error('invalid-request');
80
134
  const receipt = structuredClone(parsePresentationReceipt(input.receipt));
81
135
  const asset = input.asset;
82
- const workspace = await this.#workspace(input.sessionId);
136
+ const workspace = await this.#workspace(input);
83
137
  if (!workspace || workspace.id !== receipt.workspaceId)
84
138
  throw new Error('workspace-unavailable');
85
139
  const root = path.resolve(workspace.path);
86
- for (const file of Object.values(receipt.files))
87
- if (file.path !== presentationAssetPath(root, receipt.reportId, receipt.buildId, file.asset))
88
- throw new Error('asset-path-mismatch');
89
- const documentBytes = await readPresentationAsset(root, receipt.reportId, receipt.buildId, 'presentation.json', signal);
90
- if (documentBytes.length !== receipt.files.document.bytes ||
91
- presentationSha256(documentBytes) !== receipt.files.document.sha256)
92
- throw new Error('asset-digest-mismatch');
93
- const document = parsePresentationDocument(JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(documentBytes)));
94
- if (document.workspaceId !== workspace.id ||
95
- document.reportId !== receipt.reportId ||
96
- document.buildId !== receipt.buildId ||
97
- document.title !== receipt.title)
98
- throw new Error('asset-owner-mismatch');
99
- const bytes = asset === 'presentation.json'
100
- ? documentBytes
101
- : await readPresentationAsset(root, receipt.reportId, receipt.buildId, asset, signal);
102
- const file = asset === 'presentation.json' ? receipt.files.document : receipt.files.html;
103
- if (bytes.length !== file.bytes || presentationSha256(bytes) !== file.sha256)
104
- throw new Error('asset-digest-mismatch');
105
- const current = await this.#workspace(input.sessionId);
140
+ const { bytes, sha256 } = await readReceiptAsset(root, workspace.id, receipt, asset, signal);
141
+ const current = await this.#workspace(input);
106
142
  if (!current || current.id !== workspace.id || path.resolve(current.path) !== root)
107
143
  throw new Error('workspace-changed');
108
144
  signal.throwIfAborted();
@@ -112,17 +148,19 @@ export class MarivoPresentationFileService {
112
148
  buildId: receipt.buildId,
113
149
  asset,
114
150
  mimeType: asset === 'presentation.json' ? 'application/json' : 'text/html',
115
- sha256: file.sha256,
151
+ sha256,
116
152
  bytes: bytes.length,
117
153
  bodyBase64: bytes.toString('base64'),
118
154
  };
119
155
  }
120
156
  }
121
157
  export function registerMarivoPresentationRpc(connection, service) {
122
- const unregister = connection.rpc.handle(MARIVO_PRESENTATION_RPC_CHANNEL, async (endpoint, payload, signal) => {
158
+ const unregister = registerPluginRpc(connection, MARIVO_PRESENTATION_RPC_CHANNEL, ['reports/resolve', 'reports/save', 'reports/list', 'reports/history', 'files/read'], async (endpoint, payload, signal) => {
123
159
  try {
124
160
  if (endpoint === 'reports/resolve' || endpoint === 'reports/save')
125
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) };
126
164
  if (endpoint === 'files/read')
127
165
  return { ok: true, value: await service.read(payload, signal) };
128
166
  throw new Error('unknown-endpoint');
@@ -130,19 +168,19 @@ export function registerMarivoPresentationRpc(connection, service) {
130
168
  catch (error) {
131
169
  const message = signal.aborted
132
170
  ? 'cancelled'
133
- : error?.code === 'ENOENT'
134
- ? 'asset-missing'
135
- : error instanceof Error &&
136
- /^(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-edits|report-save-conflict|presentation-directory-changed)$/.test(error.message)
137
- ? error.message
138
- : error instanceof Error && /lock.*timed out|timed out.*lock/i.test(error.message)
139
- ? 'report-save-busy'
140
- : 'presentation-operation-failed';
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';
141
181
  return { ok: false, error: { code: 'internal', message, details: {} } };
142
182
  }
143
- }, { authority: 'trusted-host' });
144
- return async () => {
145
- service.close();
146
- await unregister();
147
- };
183
+ });
184
+ let closing;
185
+ return () => (closing ??= finishCleanup([unregister, () => service.close()]));
148
186
  }
@@ -1,13 +1,14 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { defineTool } from '@deepseek-ai/dsh-tools';
3
+ import { registerMarivoTool } from "../tool-lifecycle.js";
3
4
  import { parsePresentationBuildId } from "./contracts/index.js";
4
5
  import { MARIVO_PRESENT_TOOL_NAME, MARIVO_PRESENTATION_DELIVERY_KIND, parsePresentationDelivery, presentationReceiptText, } from "./receipt.js";
5
6
  import { publishPresentation, resolvePresentation } from "./reports.js";
6
- /** One call projects a draft, builds both assets, commits them, then emits the receipt. */
7
+ /** One call projects a draft, builds the document, commits them, then emits the receipt. */
7
8
  export function createMarivoPresentTool(source, session, lifecycleSignal) {
8
9
  return defineTool({
9
10
  name: MARIVO_PRESENT_TOOL_NAME,
10
- description: 'Present a complete Workspace-relative analysis draft as one saved document and self-contained offline HTML. 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, and a reader card. Only saved Artifact and computed snapshots are read; sources are declarations, not verification of calculations.',
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.',
11
12
  parameters: {
12
13
  draft_path: {
13
14
  type: 'string',
@@ -58,7 +59,7 @@ export function createMarivoPresentTool(source, session, lifecycleSignal) {
58
59
  if (!owner || owner.session !== session)
59
60
  throw new Error('presentation-session-mismatch');
60
61
  const rootCallId = String(exec.rootCallId ?? exec.callId);
61
- const call = [...session.events]
62
+ const call = [...session.snapshotEvents()]
62
63
  .reverse()
63
64
  .find((event) => event.type === 'tool/call' && String(event.data.callId) === rootCallId);
64
65
  if (call?.type !== 'tool/call' || !Number.isSafeInteger(call.data.turn) || call.data.turn < 0)
@@ -99,7 +100,7 @@ export function createMarivoPresentTool(source, session, lifecycleSignal) {
99
100
  signal,
100
101
  });
101
102
  await check();
102
- const receipt = await publishPresentation(identity.projectRoot, document, expectedBuildId, check, signal);
103
+ const receipt = await publishPresentation(identity.projectRoot, document, expectedBuildId, check, signal, { kind: 'agent', sessionId: String(session.id) });
103
104
  return {
104
105
  deliveryJson: JSON.stringify(parsePresentationDelivery({
105
106
  kind: MARIVO_PRESENTATION_DELIVERY_KIND,
@@ -113,10 +114,5 @@ export function createMarivoPresentTool(source, session, lifecycleSignal) {
113
114
  });
114
115
  }
115
116
  export function registerMarivoPresentTool(ctx, source, session) {
116
- const controller = new AbortController();
117
- const unregister = ctx.tools.register(createMarivoPresentTool(source, session, controller.signal));
118
- return () => {
119
- controller.abort();
120
- unregister();
121
- };
117
+ return registerMarivoTool(ctx, createMarivoPresentTool(source, session));
122
118
  }
@@ -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
+ }