@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,962 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { credentialRef } from '@deepseek-ai/dsh-credentials';
3
+ import { finishCleanup } from "../lifecycle.js";
4
+ import { marivoCredentialStorageRef } from "./shell-env.js";
5
+ export const CREDENTIAL_CHANNEL = '/dsh-data-analysis-credentials';
6
+ const RETENTION = 30 * 60_000;
7
+ const CAPACITY = 512;
8
+ function sessionIdentity(exec) {
9
+ const session = exec.agent.session;
10
+ return JSON.stringify([
11
+ session.id,
12
+ Reflect.get(session.header, 'workspaceId'),
13
+ session.header.cwd,
14
+ ]);
15
+ }
16
+ export class CredentialServiceError extends Error {
17
+ code;
18
+ constructor(code) {
19
+ super(code);
20
+ this.code = code;
21
+ this.name = 'CredentialServiceError';
22
+ }
23
+ }
24
+ export function credentialError(error) {
25
+ return error instanceof CredentialServiceError ? error.code : 'credential-operation-failed';
26
+ }
27
+ function assert(condition, code) {
28
+ if (!condition)
29
+ throw new CredentialServiceError(code);
30
+ }
31
+ /** Owns only plugin operations and execution-scoped waits. No value cache or datasource registry. */
32
+ export class MarivoCredentialService {
33
+ generation = randomUUID();
34
+ #store;
35
+ #contexts = new Map();
36
+ #activeContexts = new Set();
37
+ #configurations = new Map();
38
+ #requests = new Map();
39
+ #operations = new Map();
40
+ #controllers = new Map();
41
+ #closing;
42
+ #tasks = new Set();
43
+ #versions = new Map();
44
+ #activeRefs = new Map();
45
+ #history = new Map();
46
+ #lifetime = new AbortController();
47
+ #agents = new WeakMap();
48
+ #listeners = new Set();
49
+ #revision = 0;
50
+ #tail = Promise.resolve();
51
+ interaction;
52
+ now;
53
+ constructor(store, interaction = 'web', now = Date.now) {
54
+ this.#store = store;
55
+ this.interaction = interaction;
56
+ this.now = now;
57
+ }
58
+ executionSignal(caller, agent) {
59
+ let lifecycle;
60
+ if (agent) {
61
+ lifecycle = this.#agents.get(agent);
62
+ if (!lifecycle) {
63
+ lifecycle = new AbortController();
64
+ this.#agents.set(agent, lifecycle);
65
+ }
66
+ }
67
+ return AbortSignal.any([
68
+ caller,
69
+ this.#lifetime.signal,
70
+ ...(lifecycle ? [lifecycle.signal] : []),
71
+ ]);
72
+ }
73
+ #changed() {
74
+ this.#revision++;
75
+ for (const listener of this.#listeners)
76
+ listener();
77
+ }
78
+ #prune() {
79
+ const now = this.now();
80
+ for (const [id, op] of this.#operations)
81
+ if (op.endedAt !== undefined && now - op.endedAt >= RETENTION)
82
+ this.#operations.delete(id);
83
+ for (const [id, request] of this.#configurations)
84
+ if (request.view.endedAt !== undefined && now - request.view.endedAt >= RETENTION)
85
+ this.#configurations.delete(id);
86
+ for (const [id, request] of this.#requests)
87
+ if (request.view.endedAt !== undefined && now - request.view.endedAt >= RETENTION)
88
+ this.#requests.delete(id);
89
+ for (const [id, context] of this.#contexts)
90
+ if (now - context.touched >= RETENTION &&
91
+ ![...this.#requests.values()].some((r) => r.context === context && r.view.endedAt === undefined) &&
92
+ ![...this.#operations.values()].some((op) => op.scope === id && op.status === 'running'))
93
+ this.#contexts.delete(id);
94
+ for (const [key, history] of this.#history)
95
+ if (now - history.at >= RETENTION)
96
+ this.#history.delete(key);
97
+ const retainedRefs = new Set([
98
+ ...[...this.#contexts.values()].flatMap((context) => context.description.refs),
99
+ ...[...this.#history.values()].flatMap((history) => history.refs),
100
+ ...this.#activeRefs.keys(),
101
+ ]);
102
+ for (const ref of this.#versions.keys())
103
+ if (!retainedRefs.has(ref))
104
+ this.#versions.delete(ref);
105
+ }
106
+ async #locked(fn, signal) {
107
+ const previous = this.#tail;
108
+ let release;
109
+ this.#tail = new Promise((resolve) => {
110
+ release = resolve;
111
+ });
112
+ await previous;
113
+ try {
114
+ signal.throwIfAborted();
115
+ return await fn();
116
+ }
117
+ finally {
118
+ release();
119
+ }
120
+ }
121
+ #version(context) {
122
+ return JSON.stringify(context.description.refs.map((ref) => [ref, this.#versions.get(ref) ?? 0]));
123
+ }
124
+ #historyKey(context) {
125
+ return JSON.stringify([
126
+ context.bridge.binding.fingerprint,
127
+ context.description.name,
128
+ context.description.definition,
129
+ ]);
130
+ }
131
+ invalidateStorageRef(storageRef) {
132
+ const refs = new Set([...this.#contexts.values()].flatMap((context) => context.description.refs));
133
+ for (const ref of this.#activeRefs.keys())
134
+ refs.add(ref);
135
+ for (const history of this.#history.values())
136
+ for (const ref of history.refs)
137
+ refs.add(ref);
138
+ this.invalidate([...refs].filter((ref) => marivoCredentialStorageRef(ref) === storageRef));
139
+ }
140
+ invalidate(refs) {
141
+ for (const ref of refs)
142
+ this.#versions.set(ref, (this.#versions.get(ref) ?? 0) + 1);
143
+ this.#changed();
144
+ }
145
+ async #current(context, signal) {
146
+ assert(!context.invalidated, 'context-changed');
147
+ signal.throwIfAborted();
148
+ const bridge = await context.resolve();
149
+ assert(bridge.binding.fingerprint === context.bridge.binding.fingerprint, 'context-changed');
150
+ const described = await bridge.describe(context.description.name, signal);
151
+ assert(described.definition === context.description.definition, 'context-changed');
152
+ assert(!context.invalidated, 'context-changed');
153
+ signal.throwIfAborted();
154
+ context.touched = this.now();
155
+ }
156
+ async #view(context) {
157
+ const credentials = Object.create(null);
158
+ for (const ref of context.description.refs) {
159
+ try {
160
+ credentials[ref] = await this.#store.describe(credentialRef(marivoCredentialStorageRef(ref)));
161
+ }
162
+ catch {
163
+ throw new CredentialServiceError('credential-state-unavailable');
164
+ }
165
+ }
166
+ const historyKey = this.#historyKey(context);
167
+ const previous = [...this.#history.entries()].reverse().find(([key]) => {
168
+ const [fingerprint, name] = JSON.parse(key);
169
+ return fingerprint === context.bridge.binding.fingerprint && name === context.description.name;
170
+ });
171
+ const history = this.#history.get(historyKey) ?? previous?.[1];
172
+ return {
173
+ token: context.token,
174
+ workspaceId: context.workspaceId,
175
+ name: context.description.name,
176
+ backend: context.description.backend ?? '',
177
+ properties: context.description.properties ?? {},
178
+ refs: context.description.refs,
179
+ fields: context.description.fields,
180
+ version: this.#version(context),
181
+ credentials,
182
+ ...(history
183
+ ? {
184
+ lastTest: {
185
+ at: history.at,
186
+ result: history.result,
187
+ stale: !this.#history.has(historyKey) || history.version !== this.#version(context),
188
+ },
189
+ }
190
+ : {}),
191
+ };
192
+ }
193
+ async #bind(workspaceId, resolve, name, signal) {
194
+ this.#prune();
195
+ assert(!this.#lifetime.signal.aborted, 'disposed');
196
+ const bridge = await resolve();
197
+ const description = await bridge.describe(name, signal);
198
+ assert(description.name === name, 'invalid-datasource-context');
199
+ for (const ref of description.refs)
200
+ marivoCredentialStorageRef(ref);
201
+ assert(typeof description.definition === 'string' && !!description.fields, 'invalid-datasource-context');
202
+ const context = {
203
+ token: randomUUID(),
204
+ workspaceId,
205
+ bridge,
206
+ description,
207
+ resolve,
208
+ touched: this.now(),
209
+ };
210
+ await this.#current(context, signal);
211
+ return context;
212
+ }
213
+ async overview(workspaceId, resolve, caller) {
214
+ const signal = AbortSignal.any([caller, this.#lifetime.signal]);
215
+ const bridge = await resolve();
216
+ const items = await bridge.inventory(signal);
217
+ assert(items.length <= 128, 'datasource-limit-exceeded');
218
+ const views = [];
219
+ this.#prune();
220
+ assert(!signal.aborted, 'disposed');
221
+ for (const item of items) {
222
+ for (const ref of item.refs)
223
+ marivoCredentialStorageRef(ref);
224
+ const context = {
225
+ token: randomUUID(),
226
+ workspaceId,
227
+ bridge,
228
+ description: item,
229
+ resolve,
230
+ touched: this.now(),
231
+ };
232
+ const old = [...this.#contexts.values()].find((c) => !c.invalidated &&
233
+ c.workspaceId === workspaceId &&
234
+ c.description.name === context.description.name &&
235
+ c.description.definition === context.description.definition &&
236
+ c.bridge.binding.fingerprint === context.bridge.binding.fingerprint);
237
+ if (old) {
238
+ this.#contexts.delete(old.token);
239
+ context.token = old.token;
240
+ }
241
+ assert(this.#contexts.size < CAPACITY, 'capacity-exceeded');
242
+ this.#contexts.set(context.token, context);
243
+ views.push(await this.#view(context));
244
+ }
245
+ const current = await resolve();
246
+ assert(current.binding.fingerprint === bridge.binding.fingerprint, 'context-changed');
247
+ signal.throwIfAborted();
248
+ return views;
249
+ }
250
+ async createDatasource(generation, fingerprint, input, resolve, caller) {
251
+ const signal = AbortSignal.any([caller, this.#lifetime.signal]);
252
+ return this.#locked(async () => {
253
+ assert(generation === this.generation, 'context-changed');
254
+ const bridge = await resolve();
255
+ assert(bridge.binding.fingerprint === fingerprint, 'context-changed');
256
+ assert(bridge.create, 'datasource-authoring-unavailable');
257
+ signal.throwIfAborted();
258
+ const result = await bridge.create(input, signal);
259
+ assert(!result.error && result.name, result.error ?? 'datasource-definition-invalid');
260
+ this.#changed();
261
+ return { name: result.name };
262
+ }, signal);
263
+ }
264
+ async updateDatasource(generation, fingerprint, input, resolve, caller) {
265
+ const signal = AbortSignal.any([caller, this.#lifetime.signal]);
266
+ return this.#locked(async () => {
267
+ assert(generation === this.generation, 'context-changed');
268
+ const bridge = await resolve();
269
+ assert(bridge.binding.fingerprint === fingerprint, 'context-changed');
270
+ assert(bridge.update, 'datasource-authoring-unavailable');
271
+ assert(![...this.#operations.values()].some((op) => op.status === 'running' &&
272
+ this.#contexts.get(op.scope)?.bridge.binding.fingerprint === fingerprint &&
273
+ this.#contexts.get(op.scope)?.description.name === input.name), 'operation-busy');
274
+ const result = await bridge.update(input, signal);
275
+ assert(!result.error && result.name, result.error ?? 'datasource-definition-invalid');
276
+ this.#changed();
277
+ return { name: result.name };
278
+ }, signal);
279
+ }
280
+ async configure(exec, resolve, input) {
281
+ assert(exec.agent, 'agent-required');
282
+ assert((input.mode === 'create' && input.name === undefined) ||
283
+ (input.mode === 'edit' &&
284
+ typeof input.name === 'string' &&
285
+ !!input.name.trim() &&
286
+ input.name.length <= 256), 'invalid-request');
287
+ assert(typeof input.reason === 'string' && !!input.reason.trim() && input.reason.length <= 1000, 'invalid-request');
288
+ const agent = exec.agent;
289
+ if (this.interaction === 'none' ||
290
+ agent.session.header.origin === 'subagent' ||
291
+ (agent.session.header.delegationDepth ?? 0) > 0)
292
+ return { status: 'needs-configuration', ...(input.name ? { name: input.name } : {}) };
293
+ const controller = new AbortController();
294
+ const signal = AbortSignal.any([this.executionSignal(exec.signal, agent), controller.signal]);
295
+ const identity = sessionIdentity(exec);
296
+ const bridge = await resolve();
297
+ signal.throwIfAborted();
298
+ assert(identity === sessionIdentity(exec), 'context-changed');
299
+ this.#prune();
300
+ assert(this.#configurations.size < CAPACITY / 2, 'capacity-exceeded');
301
+ return new Promise((finish) => {
302
+ const id = randomUUID();
303
+ const request = {
304
+ exec,
305
+ settled: false,
306
+ controller,
307
+ resolve,
308
+ fingerprint: bridge.binding.fingerprint,
309
+ sessionIdentity: identity,
310
+ signal,
311
+ finish,
312
+ view: {
313
+ id,
314
+ sessionId: agent.session.id,
315
+ workspaceId: String(Reflect.get(agent.session.header, 'workspaceId') ?? ''),
316
+ configuration: { ...input },
317
+ status: 'awaiting-input',
318
+ },
319
+ stop: () => signal.removeEventListener('abort', abort),
320
+ };
321
+ const abort = () => this.#endConfiguration(request, { status: 'call-ended' });
322
+ this.#configurations.set(id, request);
323
+ signal.addEventListener('abort', abort, { once: true });
324
+ if (signal.aborted)
325
+ abort();
326
+ this.#changed();
327
+ });
328
+ }
329
+ #endConfiguration(request, result) {
330
+ if (request.settled)
331
+ return;
332
+ request.settled = true;
333
+ request.view.endedAt = this.now();
334
+ request.view.status =
335
+ result.status === 'ok'
336
+ ? 'succeeded'
337
+ : result.status === 'failed'
338
+ ? 'handed-off'
339
+ : result.status === 'context-changed'
340
+ ? 'context-changed'
341
+ : result.status === 'cancelled'
342
+ ? 'cancelled'
343
+ : 'call-ended';
344
+ request.stop();
345
+ request.finish(result);
346
+ this.#changed();
347
+ }
348
+ async #configurationCurrent(request, signal) {
349
+ assert(request.view.endedAt === undefined && !request.signal.aborted, 'call-ended');
350
+ signal.throwIfAborted();
351
+ const bridge = await request.resolve();
352
+ assert(bridge.binding.fingerprint === request.fingerprint &&
353
+ sessionIdentity(request.exec) === request.sessionIdentity, 'context-changed');
354
+ return bridge;
355
+ }
356
+ async configurationRequest(id, workspaceId, resolve, caller) {
357
+ const request = this.#configurations.get(id);
358
+ assert(request, 'call-ended');
359
+ const bridge = await this.#configurationCurrent(request, caller);
360
+ assert((!request.view.workspaceId || request.view.workspaceId === workspaceId) &&
361
+ (await resolve()).binding.fingerprint === bridge.binding.fingerprint, 'context-changed');
362
+ assert(request.view.status !== 'executing', 'operation-busy');
363
+ return request;
364
+ }
365
+ async selectConfiguration(id, workspaceId, name, resolve, caller) {
366
+ const request = await this.configurationRequest(id, workspaceId, resolve, caller);
367
+ assert(request.view.configuration.mode !== 'edit' || request.view.configuration.name === name, 'datasource-identity-fixed');
368
+ const signal = AbortSignal.any([caller, request.signal]);
369
+ const context = await this.#bind(workspaceId, request.resolve, name, signal);
370
+ const view = await this.#view(context);
371
+ await this.#configurationCurrent(request, signal);
372
+ assert(request.view.status !== 'executing', 'operation-busy');
373
+ assert(this.#contexts.size < CAPACITY, 'capacity-exceeded');
374
+ this.#contexts.set(context.token, context);
375
+ request.view.context = view;
376
+ request.view.status = 'awaiting-input';
377
+ delete request.view.failure;
378
+ // Once a datasource is chosen, use the existing credential/test operation lifecycle.
379
+ const attached = {
380
+ view: request.view,
381
+ context,
382
+ exec: request.exec,
383
+ signal: request.signal,
384
+ stop: () => { },
385
+ finish: ({ result }) => {
386
+ this.#endConfiguration(request, {
387
+ status: result.ok ? 'ok' : 'failed',
388
+ name: result.name,
389
+ latency_ms: result.latency_ms,
390
+ ...(!result.ok ? { failure: result.failure, repair: result.repair } : {}),
391
+ });
392
+ },
393
+ reject: () => {
394
+ this.#endConfiguration(request, { status: 'context-changed' });
395
+ },
396
+ };
397
+ this.#requests.set(id, attached);
398
+ this.#changed();
399
+ return structuredClone(request.view);
400
+ }
401
+ #watchRefs(refs) {
402
+ const unique = new Set(refs);
403
+ for (const ref of unique)
404
+ this.#activeRefs.set(ref, (this.#activeRefs.get(ref) ?? 0) + 1);
405
+ return () => {
406
+ for (const ref of unique) {
407
+ const count = this.#activeRefs.get(ref) - 1;
408
+ if (count === 0)
409
+ this.#activeRefs.delete(ref);
410
+ else
411
+ this.#activeRefs.set(ref, count);
412
+ }
413
+ };
414
+ }
415
+ async #snapshot(contexts, signal) {
416
+ const stop = this.#watchRefs(contexts.flatMap((context) => context.description.refs));
417
+ const values = Object.create(null);
418
+ try {
419
+ return await this.#locked(async () => {
420
+ for (const context of contexts)
421
+ await this.#current(context, signal);
422
+ const versions = contexts.map((context) => this.#version(context));
423
+ for (const ref of new Set(contexts.flatMap((context) => context.description.refs))) {
424
+ let resolved;
425
+ try {
426
+ resolved = await this.#store.resolve(credentialRef(marivoCredentialStorageRef(ref)));
427
+ }
428
+ catch {
429
+ throw new CredentialServiceError('credential-unavailable');
430
+ }
431
+ signal.throwIfAborted();
432
+ assert(resolved?.value, 'credential-missing');
433
+ values[ref] = resolved.value;
434
+ }
435
+ for (const [index, context] of contexts.entries()) {
436
+ await this.#current(context, signal);
437
+ assert(versions[index] === this.#version(context), 'credentials-changed');
438
+ }
439
+ return values;
440
+ }, signal);
441
+ }
442
+ catch (error) {
443
+ for (const ref of Object.keys(values))
444
+ delete values[ref];
445
+ throw error;
446
+ }
447
+ finally {
448
+ stop();
449
+ }
450
+ }
451
+ async #test(context, signal) {
452
+ const stop = this.#watchRefs(context.description.refs);
453
+ let values;
454
+ try {
455
+ const version = this.#version(context);
456
+ values = await this.#snapshot([context], signal);
457
+ const result = await context.bridge.test(context.description, values, signal);
458
+ await this.#current(context, signal);
459
+ assert(version === this.#version(context), 'credentials-changed');
460
+ const key = this.#historyKey(context);
461
+ this.#history.delete(key);
462
+ if (this.#history.size >= CAPACITY)
463
+ this.#history.delete(this.#history.keys().next().value);
464
+ this.#history.set(key, {
465
+ at: this.now(),
466
+ version,
467
+ refs: [...context.description.refs],
468
+ result,
469
+ });
470
+ return result;
471
+ }
472
+ finally {
473
+ if (values)
474
+ for (const ref of Object.keys(values))
475
+ delete values[ref];
476
+ stop();
477
+ }
478
+ }
479
+ async #awaitCredentials(exec, context, view, signal) {
480
+ const agent = exec.agent;
481
+ const missing = view.refs.filter((ref) => !view.credentials[ref]?.configured);
482
+ if (missing.length === 0)
483
+ return undefined;
484
+ if (this.interaction === 'none' ||
485
+ agent.session.header.origin === 'subagent' ||
486
+ (agent.session.header.delegationDepth ?? 0) > 0)
487
+ return { status: 'needs-credentials', name: context.description.name, refs: missing };
488
+ this.#prune();
489
+ assert(this.#requests.size < CAPACITY / 2, 'capacity-exceeded');
490
+ assert(this.#contexts.size < CAPACITY, 'capacity-exceeded');
491
+ this.#contexts.set(context.token, context);
492
+ return new Promise((finish, reject) => {
493
+ const id = randomUUID();
494
+ const request = {
495
+ context,
496
+ exec,
497
+ signal,
498
+ finish,
499
+ reject,
500
+ stop: () => signal.removeEventListener('abort', abort),
501
+ view: { id, sessionId: agent.session.id, context: view, status: 'awaiting-input' },
502
+ };
503
+ const abort = () => {
504
+ if (request.view.endedAt !== undefined)
505
+ return;
506
+ request.view.status = 'call-ended';
507
+ request.view.endedAt = this.now();
508
+ request.stop();
509
+ reject(new CredentialServiceError('call-ended'));
510
+ this.#changed();
511
+ };
512
+ this.#requests.set(id, request);
513
+ signal.addEventListener('abort', abort, { once: true });
514
+ if (signal.aborted)
515
+ abort();
516
+ this.#changed();
517
+ });
518
+ }
519
+ async prepare(_kind, exec, resolve, name) {
520
+ assert(exec.agent, 'agent-required');
521
+ const signal = this.executionSignal(exec.signal, exec.agent);
522
+ const context = await this.#bind('', resolve, name, signal);
523
+ this.#activeContexts.add(context);
524
+ const stop = this.#watchRefs(context.description.refs);
525
+ try {
526
+ const tested = await this.#awaitCredentials(exec, context, await this.#view(context), signal);
527
+ return tested
528
+ ? 'result' in tested
529
+ ? tested.result
530
+ : tested
531
+ : await this.#test(context, signal);
532
+ }
533
+ finally {
534
+ stop();
535
+ this.#activeContexts.delete(context);
536
+ }
537
+ }
538
+ async prepareExecution(exec, resolve, names) {
539
+ assert(exec.agent, 'agent-required');
540
+ assert(names.length <= 16 &&
541
+ new Set(names).size === names.length &&
542
+ names.every((name) => typeof name === 'string' && !!name.trim() && name.length <= 256), 'invalid-datasources');
543
+ const signal = this.executionSignal(exec.signal, exec.agent);
544
+ const session = exec.agent.session;
545
+ const currentWorkspace = () => JSON.stringify([Reflect.get(session.header, 'workspaceId'), session.header.cwd]);
546
+ const workspace = currentWorkspace();
547
+ const contexts = [];
548
+ const stops = [];
549
+ const versions = new Map();
550
+ let values = {};
551
+ let released = false;
552
+ const release = () => {
553
+ if (released)
554
+ return;
555
+ released = true;
556
+ for (const ref of Object.keys(values))
557
+ delete values[ref];
558
+ for (const stop of stops)
559
+ stop();
560
+ for (const context of contexts)
561
+ this.#activeContexts.delete(context);
562
+ };
563
+ try {
564
+ for (const name of names) {
565
+ const context = await this.#bind('', resolve, name, signal);
566
+ assert(!contexts.length ||
567
+ context.bridge.binding.fingerprint === contexts[0].bridge.binding.fingerprint, 'context-changed');
568
+ contexts.push(context);
569
+ this.#activeContexts.add(context);
570
+ stops.push(this.#watchRefs(context.description.refs));
571
+ const view = await this.#view(context);
572
+ if (view.refs.every((ref) => view.credentials[ref]?.configured))
573
+ versions.set(context, view.version);
574
+ }
575
+ for (const context of contexts) {
576
+ // Another datasource form may have supplied shared refs while this call waited.
577
+ const view = await this.#view(context);
578
+ if (!versions.has(context) && view.refs.every((ref) => view.credentials[ref]?.configured))
579
+ versions.set(context, view.version);
580
+ const prepared = await this.#awaitCredentials(exec, context, view, signal);
581
+ if (prepared && (!('result' in prepared) || !prepared.result.ok)) {
582
+ release();
583
+ return 'result' in prepared ? prepared.result : prepared;
584
+ }
585
+ if (prepared) {
586
+ assert(!versions.has(context) || versions.get(context) === prepared.version, 'credentials-changed');
587
+ versions.set(context, prepared.version);
588
+ }
589
+ }
590
+ const assertCurrent = () => {
591
+ assert(!released, 'execution-ended');
592
+ signal.throwIfAborted();
593
+ assert(exec.agent?.session === session && currentWorkspace() === workspace, 'context-changed');
594
+ for (const context of contexts) {
595
+ assert(!context.invalidated, 'context-changed');
596
+ assert(versions.get(context) === this.#version(context), 'credentials-changed');
597
+ }
598
+ };
599
+ assertCurrent();
600
+ values = await this.#snapshot(contexts, signal);
601
+ assertCurrent();
602
+ const bridge = contexts[0]?.bridge ?? (await resolve());
603
+ if (!contexts.length) {
604
+ const fingerprint = bridge.binding.fingerprint;
605
+ const current = await resolve();
606
+ assert(current.binding.fingerprint === fingerprint, 'context-changed');
607
+ }
608
+ assertCurrent();
609
+ return {
610
+ status: 'ready',
611
+ bridge,
612
+ values,
613
+ grants: Object.fromEntries(contexts.map((c) => [c.description.name, c.description])),
614
+ signal,
615
+ assertCurrent,
616
+ release,
617
+ };
618
+ }
619
+ catch (error) {
620
+ release();
621
+ throw error;
622
+ }
623
+ }
624
+ watch(sessionId, cursor) {
625
+ this.#prune();
626
+ void cursor;
627
+ return {
628
+ generation: this.generation,
629
+ cursor: `${this.generation}:${this.#revision}`,
630
+ configurationRequests: [...this.#configurations.values()]
631
+ .filter((r) => r.view.sessionId === sessionId)
632
+ .map((r) => structuredClone(r.view)),
633
+ requests: [...this.#requests.values()]
634
+ .filter((r) => r.view.sessionId === sessionId && !this.#configurations.has(r.view.id))
635
+ .map((r) => structuredClone(r.view)),
636
+ };
637
+ }
638
+ async waitWatch(sessionId, cursor, signal) {
639
+ if (cursor === this.watch(sessionId).cursor &&
640
+ !signal.aborted &&
641
+ !this.#lifetime.signal.aborted) {
642
+ await new Promise((resolve) => {
643
+ const end = () => {
644
+ clearTimeout(timer);
645
+ this.#listeners.delete(end);
646
+ signal.removeEventListener('abort', end);
647
+ resolve();
648
+ };
649
+ const timer = setTimeout(end, 25_000);
650
+ this.#listeners.add(end);
651
+ signal.addEventListener('abort', end, { once: true });
652
+ if (signal.aborted)
653
+ end();
654
+ });
655
+ }
656
+ signal.throwIfAborted();
657
+ for (const request of this.#configurations.values()) {
658
+ if (request.view.sessionId !== sessionId || request.view.endedAt !== undefined)
659
+ continue;
660
+ try {
661
+ await this.#configurationCurrent(request, signal);
662
+ }
663
+ catch {
664
+ if (!signal.aborted)
665
+ this.#endConfiguration(request, { status: 'context-changed' });
666
+ }
667
+ }
668
+ for (const request of this.#requests.values()) {
669
+ if (request.view.sessionId !== sessionId || request.view.endedAt !== undefined)
670
+ continue;
671
+ const view = await this.#view(request.context);
672
+ if (JSON.stringify(view) !== JSON.stringify(request.view.context)) {
673
+ request.view.context = view;
674
+ this.#changed();
675
+ }
676
+ }
677
+ return this.watch(sessionId);
678
+ }
679
+ operation(generation, id, scope) {
680
+ this.#prune();
681
+ if (generation !== this.generation)
682
+ return undefined;
683
+ const operation = this.#operations.get(id);
684
+ assert(!operation || operation.scope === scope, 'operation-scope-mismatch');
685
+ return operation ? structuredClone(operation) : undefined;
686
+ }
687
+ start(input) {
688
+ this.#prune();
689
+ assert(input.generation === this.generation && !this.#lifetime.signal.aborted, 'context-changed');
690
+ const existing = this.#operations.get(input.id);
691
+ if (existing) {
692
+ assert(existing.scope === input.scope && existing.action === input.action, 'operation-scope-mismatch');
693
+ return structuredClone(existing);
694
+ }
695
+ assert(this.#operations.size < CAPACITY, 'capacity-exceeded');
696
+ const context = this.#contexts.get(input.scope);
697
+ assert(context, 'context-changed');
698
+ assert(!context.invalidated, 'context-changed');
699
+ assert(input.action === 'delete-datasource' || input.deleteCredentials === undefined, 'invalid-action');
700
+ const request = input.requestId ? this.#requests.get(input.requestId) : undefined;
701
+ assert(!input.requestId ||
702
+ (request &&
703
+ request.context.token === context.token &&
704
+ request.view.endedAt === undefined &&
705
+ !request.signal.aborted), 'call-ended');
706
+ assert((input.action === 'submit' || input.action === 'diagnose') === !!request, 'invalid-action');
707
+ assert(![...this.#operations.values()].some((op) => op.status === 'running' &&
708
+ (op.scope === input.scope ||
709
+ (this.#contexts.get(op.scope)?.bridge.binding.fingerprint ===
710
+ context.bridge.binding.fingerprint &&
711
+ this.#contexts.get(op.scope)?.description.name === context.description.name))), 'operation-busy');
712
+ if (request)
713
+ assert(request.view.status !== 'executing', 'operation-busy');
714
+ assert(input.version === this.#version(context), 'credentials-changed');
715
+ const changes = input.changes ?? {};
716
+ assert(Object.keys(changes).every((ref) => context.description.refs.includes(ref) &&
717
+ typeof changes[ref] === 'string' &&
718
+ changes[ref].length > 0 &&
719
+ changes[ref].length <= 65536), 'invalid-changes');
720
+ assert(!Object.keys(changes).length ||
721
+ input.action === 'save' ||
722
+ input.action === 'update' ||
723
+ input.action === 'submit', 'invalid-changes');
724
+ assert(input.action !== 'delete' ||
725
+ (input.reference && context.description.refs.includes(input.reference)), 'invalid-reference');
726
+ const op = {
727
+ id: input.id,
728
+ scope: input.scope,
729
+ action: input.action,
730
+ status: 'running',
731
+ phase: 'saving',
732
+ saved: [],
733
+ errors: [],
734
+ ...(input.action === 'delete-datasource'
735
+ ? { deleteCredentials: input.deleteCredentials === true }
736
+ : {}),
737
+ };
738
+ this.#operations.set(op.id, op);
739
+ const controller = new AbortController();
740
+ this.#controllers.set(op.id, controller);
741
+ const signal = AbortSignal.any([
742
+ controller.signal,
743
+ this.#lifetime.signal,
744
+ ...(request ? [request.signal] : []),
745
+ ]);
746
+ if (request)
747
+ request.view.status = 'executing';
748
+ const task = this.#run(context, request, op, changes, input.reference, input.version, signal).finally(() => {
749
+ this.#controllers.delete(op.id);
750
+ this.#tasks.delete(task);
751
+ });
752
+ this.#tasks.add(task);
753
+ this.#changed();
754
+ return structuredClone(op);
755
+ }
756
+ async #run(context, request, op, changes, reference, version, signal) {
757
+ try {
758
+ await this.#current(context, signal);
759
+ const configuration = request && this.#configurations.get(request.view.id);
760
+ if (configuration)
761
+ await this.#configurationCurrent(configuration, signal);
762
+ if (op.action === 'delete-datasource') {
763
+ await this.#locked(async () => {
764
+ await this.#current(context, signal);
765
+ assert(version === this.#version(context), 'credentials-changed');
766
+ assert(context.bridge.remove, 'datasource-remove-unavailable');
767
+ op.phase = 'removing';
768
+ // Revoke pending uses even if the subprocess result is later lost.
769
+ for (const bound of new Set([
770
+ ...this.#contexts.values(),
771
+ ...this.#activeContexts,
772
+ ...[...this.#requests.values()].map((pending) => pending.context),
773
+ ])) {
774
+ if (bound.bridge.binding.fingerprint !== context.bridge.binding.fingerprint ||
775
+ bound.description.name !== context.description.name)
776
+ continue;
777
+ bound.invalidated = true;
778
+ }
779
+ for (const pending of this.#requests.values()) {
780
+ if (!pending.context.invalidated || pending.view.endedAt !== undefined)
781
+ continue;
782
+ pending.view.status = 'context-changed';
783
+ pending.view.endedAt = this.now();
784
+ pending.stop();
785
+ pending.reject(new CredentialServiceError('context-changed'));
786
+ }
787
+ for (const pending of this.#configurations.values()) {
788
+ if (pending.fingerprint === context.bridge.binding.fingerprint &&
789
+ (pending.view.context?.name === context.description.name ||
790
+ (pending.view.configuration.mode === 'edit' &&
791
+ pending.view.configuration.name === context.description.name)))
792
+ this.#endConfiguration(pending, { status: 'context-changed' });
793
+ }
794
+ this.#changed();
795
+ const result = await context.bridge.remove(context.description, signal);
796
+ assert(!result.error && result.name === context.description.name, result.error ?? 'datasource-remove-failed');
797
+ op.datasourceRemoved = true;
798
+ for (const key of this.#history.keys()) {
799
+ const [fingerprint, name] = JSON.parse(key);
800
+ if (fingerprint === context.bridge.binding.fingerprint &&
801
+ name === context.description.name)
802
+ this.#history.delete(key);
803
+ }
804
+ if (op.deleteCredentials) {
805
+ op.deletedCredentials = [];
806
+ op.credentialDeleteFailures = [];
807
+ for (const ref of new Set(context.description.refs)) {
808
+ signal.throwIfAborted();
809
+ this.invalidate([ref]);
810
+ try {
811
+ await this.#store.unset(credentialRef(marivoCredentialStorageRef(ref)));
812
+ op.deletedCredentials.push(ref);
813
+ }
814
+ catch {
815
+ op.credentialDeleteFailures.push(ref);
816
+ }
817
+ }
818
+ assert(op.credentialDeleteFailures.length === 0, 'credential-delete-failed');
819
+ }
820
+ }, signal);
821
+ }
822
+ else if (op.action === 'diagnose') {
823
+ assert(request?.view.failure, 'no-test-failure');
824
+ op.result = request.view.failure;
825
+ request.view.status = 'handed-off';
826
+ request.view.endedAt = this.now();
827
+ request.stop();
828
+ request.finish({ result: op.result, version: this.#version(context) });
829
+ }
830
+ else {
831
+ await this.#locked(async () => {
832
+ await this.#current(context, signal);
833
+ assert(version === this.#version(context), 'credentials-changed');
834
+ const refs = op.action === 'delete' ? [reference] : Object.keys(changes);
835
+ for (const ref of refs) {
836
+ signal.throwIfAborted();
837
+ // Revocation precedes writes; a snapshot already committed may finish.
838
+ this.invalidate([ref]);
839
+ try {
840
+ const storageRef = credentialRef(marivoCredentialStorageRef(ref));
841
+ if (op.action === 'delete')
842
+ await this.#store.unset(storageRef);
843
+ else
844
+ await this.#store.set(storageRef, changes[ref]);
845
+ op.saved.push(ref);
846
+ }
847
+ catch {
848
+ op.errors.push(ref);
849
+ }
850
+ }
851
+ }, signal);
852
+ assert(op.errors.length === 0, 'credential-save-failed');
853
+ for (const ref of Object.keys(changes))
854
+ delete changes[ref];
855
+ if (op.action !== 'delete' && op.action !== 'save') {
856
+ op.phase = 'validating';
857
+ this.#changed();
858
+ const testedVersion = this.#version(context);
859
+ op.result = await this.#test(context, signal);
860
+ if (request && request.view.endedAt === undefined) {
861
+ request.view.context = await this.#view(context);
862
+ signal.throwIfAborted();
863
+ await this.#current(context, signal);
864
+ if (configuration)
865
+ await this.#configurationCurrent(configuration, signal);
866
+ assert(testedVersion === this.#version(context), 'credentials-changed');
867
+ if (op.result.ok) {
868
+ request.view.status = 'succeeded';
869
+ request.view.endedAt = this.now();
870
+ request.stop();
871
+ request.finish({ result: op.result, version: testedVersion });
872
+ }
873
+ else {
874
+ request.view.status = 'awaiting-decision';
875
+ request.view.failure = op.result;
876
+ }
877
+ }
878
+ }
879
+ }
880
+ op.status = 'succeeded';
881
+ }
882
+ catch (error) {
883
+ op.status = signal.aborted ? 'cancelled' : 'failed';
884
+ op.errors.push(signal.aborted ? 'cancelled' : credentialError(error));
885
+ if (request && request.view.endedAt === undefined) {
886
+ if (credentialError(error) === 'context-changed') {
887
+ request.view.status = 'context-changed';
888
+ request.view.endedAt = this.now();
889
+ request.stop();
890
+ request.reject(new CredentialServiceError('context-changed'));
891
+ }
892
+ else {
893
+ request.view.status = 'awaiting-input';
894
+ try {
895
+ request.view.context = await this.#view(context);
896
+ }
897
+ catch {
898
+ /* keep last safe facts */
899
+ }
900
+ }
901
+ }
902
+ }
903
+ finally {
904
+ for (const ref of Object.keys(changes))
905
+ delete changes[ref];
906
+ op.phase = 'settled';
907
+ op.endedAt = this.now();
908
+ this.#changed();
909
+ }
910
+ }
911
+ cancelOperation(generation, id, scope) {
912
+ const operation = this.operation(generation, id, scope);
913
+ assert(operation, 'operation-unrecoverable');
914
+ this.#controllers.get(id)?.abort();
915
+ }
916
+ cancelRequest(id) {
917
+ const configuration = this.#configurations.get(id);
918
+ if (configuration) {
919
+ assert(configuration.view.endedAt === undefined, 'call-ended');
920
+ for (const op of this.#operations.values())
921
+ if (op.scope === configuration.view.context?.token && op.status === 'running')
922
+ this.#controllers.get(op.id)?.abort();
923
+ this.#endConfiguration(configuration, { status: 'cancelled' });
924
+ configuration.controller.abort();
925
+ return;
926
+ }
927
+ const request = this.#requests.get(id);
928
+ assert(request && request.view.endedAt === undefined && !request.signal.aborted, 'call-ended');
929
+ request.exec.agent.cancel({ kind: 'user' }, { keepInbox: true });
930
+ }
931
+ disposeAgent(agent) {
932
+ this.#agents.get(agent)?.abort();
933
+ for (const request of this.#requests.values())
934
+ if (request.exec.agent === agent && request.view.endedAt === undefined) {
935
+ request.view.status = 'call-ended';
936
+ request.view.endedAt = this.now();
937
+ request.stop();
938
+ request.reject(new CredentialServiceError('call-ended'));
939
+ }
940
+ this.#changed();
941
+ }
942
+ track(task) {
943
+ const tracked = task
944
+ .then(() => { }, () => { })
945
+ .finally(() => this.#tasks.delete(tracked));
946
+ this.#tasks.add(tracked);
947
+ return task;
948
+ }
949
+ close() {
950
+ if (this.#closing)
951
+ return this.#closing;
952
+ this.#lifetime.abort();
953
+ this.#closing = finishCleanup([
954
+ () => this.#changed(),
955
+ async () => {
956
+ while (this.#tasks.size)
957
+ await Promise.allSettled([...this.#tasks]);
958
+ },
959
+ ]);
960
+ return this.#closing;
961
+ }
962
+ }