@zaalipro/dsh-workflows 0.1.0-rc.3

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 (229) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +224 -0
  3. package/README.i18n.yaml +4 -0
  4. package/README.md +109 -0
  5. package/README.zh.md +109 -0
  6. package/cordis.patch.yml +10 -0
  7. package/docs/architecture.i18n.yaml +4 -0
  8. package/docs/architecture.md +177 -0
  9. package/docs/architecture.zh.md +177 -0
  10. package/docs/testing.i18n.yaml +4 -0
  11. package/docs/testing.md +125 -0
  12. package/docs/testing.zh.md +125 -0
  13. package/docs/user-guide.i18n.yaml +4 -0
  14. package/docs/user-guide.md +351 -0
  15. package/docs/user-guide.zh.md +351 -0
  16. package/lib/client-types/WorkflowMemberInspector.d.ts +28 -0
  17. package/lib/client-types/WorkflowMemberInspector.d.ts.map +1 -0
  18. package/lib/client-types/WorkflowMemberInspector.js +181 -0
  19. package/lib/client-types/WorkflowMemberInspector.js.map +1 -0
  20. package/lib/client-types/WorkflowRunPanel.d.ts +43 -0
  21. package/lib/client-types/WorkflowRunPanel.d.ts.map +1 -0
  22. package/lib/client-types/WorkflowRunPanel.js +130 -0
  23. package/lib/client-types/WorkflowRunPanel.js.map +1 -0
  24. package/lib/client-types/WorkflowsDashboard.d.ts +49 -0
  25. package/lib/client-types/WorkflowsDashboard.d.ts.map +1 -0
  26. package/lib/client-types/WorkflowsDashboard.js +942 -0
  27. package/lib/client-types/WorkflowsDashboard.js.map +1 -0
  28. package/lib/client-types/adapter.d.ts +60 -0
  29. package/lib/client-types/adapter.d.ts.map +1 -0
  30. package/lib/client-types/adapter.js +117 -0
  31. package/lib/client-types/adapter.js.map +1 -0
  32. package/lib/client-types/chat-renderer.d.ts +10 -0
  33. package/lib/client-types/chat-renderer.d.ts.map +1 -0
  34. package/lib/client-types/chat-renderer.js +11 -0
  35. package/lib/client-types/chat-renderer.js.map +1 -0
  36. package/lib/client-types/contract.d.ts +210 -0
  37. package/lib/client-types/contract.d.ts.map +1 -0
  38. package/lib/client-types/contract.js +37 -0
  39. package/lib/client-types/contract.js.map +1 -0
  40. package/lib/client-types/controller.d.ts +83 -0
  41. package/lib/client-types/controller.d.ts.map +1 -0
  42. package/lib/client-types/controller.js +705 -0
  43. package/lib/client-types/controller.js.map +1 -0
  44. package/lib/client-types/index.d.ts +19 -0
  45. package/lib/client-types/index.d.ts.map +1 -0
  46. package/lib/client-types/index.js +730 -0
  47. package/lib/client-types/index.js.map +1 -0
  48. package/lib/client-types/locales.d.ts +257 -0
  49. package/lib/client-types/locales.d.ts.map +1 -0
  50. package/lib/client-types/locales.js +270 -0
  51. package/lib/client-types/locales.js.map +1 -0
  52. package/lib/client-types/slot-components.d.ts +38 -0
  53. package/lib/client-types/slot-components.d.ts.map +1 -0
  54. package/lib/client-types/slot-components.js +22 -0
  55. package/lib/client-types/slot-components.js.map +1 -0
  56. package/lib/client-types/store.d.ts +69 -0
  57. package/lib/client-types/store.d.ts.map +1 -0
  58. package/lib/client-types/store.js +135 -0
  59. package/lib/client-types/store.js.map +1 -0
  60. package/lib/client-types/workflow-definition.d.ts +52 -0
  61. package/lib/client-types/workflow-definition.d.ts.map +1 -0
  62. package/lib/client-types/workflow-definition.js +135 -0
  63. package/lib/client-types/workflow-definition.js.map +1 -0
  64. package/lib/client.js +11064 -0
  65. package/lib/client.js.map +1 -0
  66. package/lib/compat-engine/index.js +1926 -0
  67. package/lib/compat-engine/index.js.map +1 -0
  68. package/lib/compat-engine/worker.cjs +1500 -0
  69. package/lib/compat-engine/worker.cjs.map +1 -0
  70. package/lib/typert.host.d.ts +3 -0
  71. package/lib/typert.host.js +1709 -0
  72. package/lib/typert.remote-client.d.ts +57 -0
  73. package/lib/typert.remote-client.js +1662 -0
  74. package/lib/types/commands/aliases.d.ts +4 -0
  75. package/lib/types/commands/aliases.d.ts.map +1 -0
  76. package/lib/types/commands/aliases.js +25 -0
  77. package/lib/types/commands/aliases.js.map +1 -0
  78. package/lib/types/commands/index.d.ts +66 -0
  79. package/lib/types/commands/index.d.ts.map +1 -0
  80. package/lib/types/commands/index.js +550 -0
  81. package/lib/types/commands/index.js.map +1 -0
  82. package/lib/types/commands/parser.d.ts +17 -0
  83. package/lib/types/commands/parser.d.ts.map +1 -0
  84. package/lib/types/commands/parser.js +45 -0
  85. package/lib/types/commands/parser.js.map +1 -0
  86. package/lib/types/config.d.ts +71 -0
  87. package/lib/types/config.d.ts.map +1 -0
  88. package/lib/types/config.js +109 -0
  89. package/lib/types/config.js.map +1 -0
  90. package/lib/types/index.d.ts +41 -0
  91. package/lib/types/index.d.ts.map +1 -0
  92. package/lib/types/index.js +483 -0
  93. package/lib/types/index.js.map +1 -0
  94. package/lib/types/invariant.d.ts +21 -0
  95. package/lib/types/invariant.d.ts.map +1 -0
  96. package/lib/types/invariant.js +85 -0
  97. package/lib/types/invariant.js.map +1 -0
  98. package/lib/types/registry/definition.d.ts +24 -0
  99. package/lib/types/registry/definition.d.ts.map +1 -0
  100. package/lib/types/registry/definition.js +189 -0
  101. package/lib/types/registry/definition.js.map +1 -0
  102. package/lib/types/registry/index.d.ts +100 -0
  103. package/lib/types/registry/index.d.ts.map +1 -0
  104. package/lib/types/registry/index.js +899 -0
  105. package/lib/types/registry/index.js.map +1 -0
  106. package/lib/types/registry/names.d.ts +19 -0
  107. package/lib/types/registry/names.d.ts.map +1 -0
  108. package/lib/types/registry/names.js +69 -0
  109. package/lib/types/registry/names.js.map +1 -0
  110. package/lib/types/registry/remote.d.ts +15 -0
  111. package/lib/types/registry/remote.d.ts.map +1 -0
  112. package/lib/types/registry/remote.js +128 -0
  113. package/lib/types/registry/remote.js.map +1 -0
  114. package/lib/types/registry/roots.d.ts +44 -0
  115. package/lib/types/registry/roots.d.ts.map +1 -0
  116. package/lib/types/registry/roots.js +118 -0
  117. package/lib/types/registry/roots.js.map +1 -0
  118. package/lib/types/registry/types.d.ts +62 -0
  119. package/lib/types/registry/types.d.ts.map +1 -0
  120. package/lib/types/registry/types.js +2 -0
  121. package/lib/types/registry/types.js.map +1 -0
  122. package/lib/types/registry/watchers.d.ts +70 -0
  123. package/lib/types/registry/watchers.d.ts.map +1 -0
  124. package/lib/types/registry/watchers.js +152 -0
  125. package/lib/types/registry/watchers.js.map +1 -0
  126. package/lib/types/remote-events.d.ts +42 -0
  127. package/lib/types/remote-events.d.ts.map +1 -0
  128. package/lib/types/remote-events.js +21 -0
  129. package/lib/types/remote-events.js.map +1 -0
  130. package/lib/types/run-recorder.d.ts +44 -0
  131. package/lib/types/run-recorder.d.ts.map +1 -0
  132. package/lib/types/run-recorder.js +356 -0
  133. package/lib/types/run-recorder.js.map +1 -0
  134. package/lib/types/supervisor/canned-validate.d.ts +25 -0
  135. package/lib/types/supervisor/canned-validate.d.ts.map +1 -0
  136. package/lib/types/supervisor/canned-validate.js +122 -0
  137. package/lib/types/supervisor/canned-validate.js.map +1 -0
  138. package/lib/types/supervisor/completion-notice.d.ts +49 -0
  139. package/lib/types/supervisor/completion-notice.d.ts.map +1 -0
  140. package/lib/types/supervisor/completion-notice.js +281 -0
  141. package/lib/types/supervisor/completion-notice.js.map +1 -0
  142. package/lib/types/supervisor/cursors.d.ts +68 -0
  143. package/lib/types/supervisor/cursors.d.ts.map +1 -0
  144. package/lib/types/supervisor/cursors.js +158 -0
  145. package/lib/types/supervisor/cursors.js.map +1 -0
  146. package/lib/types/supervisor/engine-compat.d.ts +48 -0
  147. package/lib/types/supervisor/engine-compat.d.ts.map +1 -0
  148. package/lib/types/supervisor/engine-compat.js +116 -0
  149. package/lib/types/supervisor/engine-compat.js.map +1 -0
  150. package/lib/types/supervisor/index.d.ts +183 -0
  151. package/lib/types/supervisor/index.d.ts.map +1 -0
  152. package/lib/types/supervisor/index.js +2040 -0
  153. package/lib/types/supervisor/index.js.map +1 -0
  154. package/lib/types/supervisor/parallel-compat.d.ts +36 -0
  155. package/lib/types/supervisor/parallel-compat.d.ts.map +1 -0
  156. package/lib/types/supervisor/parallel-compat.js +180 -0
  157. package/lib/types/supervisor/parallel-compat.js.map +1 -0
  158. package/lib/types/supervisor/remote.d.ts +25 -0
  159. package/lib/types/supervisor/remote.d.ts.map +1 -0
  160. package/lib/types/supervisor/remote.js +478 -0
  161. package/lib/types/supervisor/remote.js.map +1 -0
  162. package/lib/types/supervisor/storage/bounded-file.d.ts +41 -0
  163. package/lib/types/supervisor/storage/bounded-file.d.ts.map +1 -0
  164. package/lib/types/supervisor/storage/bounded-file.js +186 -0
  165. package/lib/types/supervisor/storage/bounded-file.js.map +1 -0
  166. package/lib/types/supervisor/storage/details-codec.d.ts +14 -0
  167. package/lib/types/supervisor/storage/details-codec.d.ts.map +1 -0
  168. package/lib/types/supervisor/storage/details-codec.js +359 -0
  169. package/lib/types/supervisor/storage/details-codec.js.map +1 -0
  170. package/lib/types/supervisor/storage/index.d.ts +26 -0
  171. package/lib/types/supervisor/storage/index.d.ts.map +1 -0
  172. package/lib/types/supervisor/storage/index.js +100 -0
  173. package/lib/types/supervisor/storage/index.js.map +1 -0
  174. package/lib/types/supervisor/storage/lease.d.ts +34 -0
  175. package/lib/types/supervisor/storage/lease.d.ts.map +1 -0
  176. package/lib/types/supervisor/storage/lease.js +289 -0
  177. package/lib/types/supervisor/storage/lease.js.map +1 -0
  178. package/lib/types/supervisor/storage/manifest-codec.d.ts +10 -0
  179. package/lib/types/supervisor/storage/manifest-codec.d.ts.map +1 -0
  180. package/lib/types/supervisor/storage/manifest-codec.js +335 -0
  181. package/lib/types/supervisor/storage/manifest-codec.js.map +1 -0
  182. package/lib/types/supervisor/storage/manifest-store.d.ts +99 -0
  183. package/lib/types/supervisor/storage/manifest-store.d.ts.map +1 -0
  184. package/lib/types/supervisor/storage/manifest-store.js +1606 -0
  185. package/lib/types/supervisor/storage/manifest-store.js.map +1 -0
  186. package/lib/types/supervisor/storage/manifest-types.d.ts +269 -0
  187. package/lib/types/supervisor/storage/manifest-types.d.ts.map +1 -0
  188. package/lib/types/supervisor/storage/manifest-types.js +2 -0
  189. package/lib/types/supervisor/storage/manifest-types.js.map +1 -0
  190. package/lib/types/supervisor/storage/private-root.d.ts +143 -0
  191. package/lib/types/supervisor/storage/private-root.d.ts.map +1 -0
  192. package/lib/types/supervisor/storage/private-root.js +860 -0
  193. package/lib/types/supervisor/storage/private-root.js.map +1 -0
  194. package/lib/types/supervisor/storage/recovery.d.ts +14 -0
  195. package/lib/types/supervisor/storage/recovery.d.ts.map +1 -0
  196. package/lib/types/supervisor/storage/recovery.js +126 -0
  197. package/lib/types/supervisor/storage/recovery.js.map +1 -0
  198. package/lib/types/supervisor/storage/run-files.d.ts +67 -0
  199. package/lib/types/supervisor/storage/run-files.d.ts.map +1 -0
  200. package/lib/types/supervisor/storage/run-files.js +346 -0
  201. package/lib/types/supervisor/storage/run-files.js.map +1 -0
  202. package/lib/types/supervisor/types.d.ts +295 -0
  203. package/lib/types/supervisor/types.d.ts.map +1 -0
  204. package/lib/types/supervisor/types.js +3 -0
  205. package/lib/types/supervisor/types.js.map +1 -0
  206. package/lib/types/supervisor/value-view.d.ts +19 -0
  207. package/lib/types/supervisor/value-view.d.ts.map +1 -0
  208. package/lib/types/supervisor/value-view.js +183 -0
  209. package/lib/types/supervisor/value-view.js.map +1 -0
  210. package/lib/types/tool/index.d.ts +86 -0
  211. package/lib/types/tool/index.d.ts.map +1 -0
  212. package/lib/types/tool/index.js +810 -0
  213. package/lib/types/tool/index.js.map +1 -0
  214. package/lib/types/tool/schema.d.ts +158 -0
  215. package/lib/types/tool/schema.d.ts.map +1 -0
  216. package/lib/types/tool/schema.js +137 -0
  217. package/lib/types/tool/schema.js.map +1 -0
  218. package/lib/types/types.d.ts +9 -0
  219. package/lib/types/types.d.ts.map +1 -0
  220. package/lib/types/types.js +3 -0
  221. package/lib/types/types.js.map +1 -0
  222. package/lib/types/user-questions.d.ts +9 -0
  223. package/lib/types/user-questions.d.ts.map +1 -0
  224. package/lib/types/user-questions.js +91 -0
  225. package/lib/types/user-questions.js.map +1 -0
  226. package/package.json +273 -0
  227. package/skills/create-workflow/SKILL.md +166 -0
  228. package/vendor/workflow-engine/LICENSE +21 -0
  229. package/vendor/workflow-engine/README.md +1 -0
@@ -0,0 +1,942 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useRef, useState, } from 'react';
3
+ import clsx from 'clsx';
4
+ import { WorkflowRunsRemoteError } from './contract.js';
5
+ import { WorkflowMemberInspector } from './WorkflowMemberInspector.js';
6
+ import { dashboardLabelsFromLocale, INTERRUPTED_SETTLEMENT, workflowLocales, } from './locales.js';
7
+ import css from './WorkflowsDashboard.module.css';
8
+ const TERMINAL = new Set(['completed', 'failed', 'cancelled', 'interrupted']);
9
+ const ACTION_ORDER = ['pause', 'resume', 'stop', 'save'];
10
+ const SHORTCUTS = {
11
+ p: 'pause', r: 'resume', x: 'stop', s: 'save',
12
+ };
13
+ export const GENERIC_LOAD_ERROR = 'Unable to load workflow data. Retry.';
14
+ export const GENERIC_CONTROL_ERROR = 'Unable to update workflow. Retry.';
15
+ export const STALE_CONTROL_ERROR = 'workflow run changed; refresh it before applying a control';
16
+ function emptySource(sessionId = '') {
17
+ return {
18
+ sessionId,
19
+ phase: 'idle',
20
+ status: 'idle',
21
+ runs: [],
22
+ total: 0,
23
+ sessionRevision: 0,
24
+ revision: 0,
25
+ };
26
+ }
27
+ function isActive(status) { return !TERMINAL.has(status); }
28
+ function statusLabel(status, labels) {
29
+ return labels.status[status];
30
+ }
31
+ function formatDuration(ms) {
32
+ const seconds = Math.max(0, Math.floor(ms / 1_000));
33
+ if (seconds < 60)
34
+ return `${seconds}s`;
35
+ const minutes = Math.floor(seconds / 60);
36
+ if (minutes < 60)
37
+ return `${minutes}m`;
38
+ const hours = Math.floor(minutes / 60);
39
+ if (hours < 24)
40
+ return `${hours}h`;
41
+ return `${Math.floor(hours / 24)}d`;
42
+ }
43
+ /** Stable active-oldest/history-newest ordering required by the dashboard. */
44
+ export function orderWorkflowRuns(rows) {
45
+ return [...rows].sort((left, right) => {
46
+ const leftActive = isActive(left.status);
47
+ const rightActive = isActive(right.status);
48
+ if (leftActive !== rightActive)
49
+ return leftActive ? -1 : 1;
50
+ if (leftActive)
51
+ return left.startedAt - right.startedAt || left.displayName.localeCompare(right.displayName);
52
+ const leftEnd = left.settledAt ?? left.startedAt;
53
+ const rightEnd = right.settledAt ?? right.startedAt;
54
+ return rightEnd - leftEnd || right.startedAt - left.startedAt;
55
+ });
56
+ }
57
+ function isAbort(error) {
58
+ return (error instanceof Error && error.name === 'AbortError')
59
+ || (typeof DOMException !== 'undefined' && error instanceof DOMException && error.name === 'AbortError');
60
+ }
61
+ function pageError(error) {
62
+ if (error instanceof WorkflowRunsRemoteError && [
63
+ 'invalid-page-limit', 'invalid-artifact-limit', 'invalid-cursor', 'stale-cursor',
64
+ ].includes(error.code))
65
+ return error.message;
66
+ return GENERIC_LOAD_ERROR;
67
+ }
68
+ function utf8Bytes(value) { return new TextEncoder().encode(value).byteLength; }
69
+ function settledMembers(run) {
70
+ return run.memberCounts.completed + run.memberCounts.failed + run.memberCounts.cancelled;
71
+ }
72
+ /** Prefer Remote phases; otherwise recover titles from members or the live phase. */
73
+ export function declaredWorkflowPhases(execution, selectedRun, members) {
74
+ if (execution?.phases !== undefined && execution.phases.length > 0)
75
+ return execution.phases;
76
+ const seen = new Set();
77
+ const fromMembers = [];
78
+ for (const member of members) {
79
+ if (typeof member.phase !== 'string' || member.phase === '' || seen.has(member.phase))
80
+ continue;
81
+ seen.add(member.phase);
82
+ fromMembers.push({ title: member.phase });
83
+ }
84
+ if (fromMembers.length > 0)
85
+ return fromMembers;
86
+ const live = execution?.run.phase ?? selectedRun.phase;
87
+ if (typeof live === 'string' && live.length > 0)
88
+ return [{ title: live }];
89
+ return execution?.phases ?? [];
90
+ }
91
+ function memberOutcomeLabel(member, labels) {
92
+ if (member.childSessionId !== undefined && member.outcome === 'not-produced')
93
+ return labels.outcomeChild;
94
+ return labels.outcome[member.outcome];
95
+ }
96
+ function memberSummary(run, labels) {
97
+ return labels.agentsCompact(settledMembers(run), run.memberCounts.total);
98
+ }
99
+ function budgetSummary(run) {
100
+ return `${run.budget.spent}/${run.budget.total} agents`;
101
+ }
102
+ function terminalResult(run) {
103
+ if (run.terminal === undefined)
104
+ return 'Result pending';
105
+ if (run.terminal.preview !== undefined)
106
+ return `Result: ${run.terminal.preview}`;
107
+ switch (run.terminal.resultState) {
108
+ case 'available': return 'Result retained';
109
+ case 'not-produced': return 'No result produced';
110
+ case 'evicted': return 'Result evicted';
111
+ }
112
+ }
113
+ function groupMembers(members) {
114
+ const groups = new Map();
115
+ for (const member of members) {
116
+ const key = member.phase === undefined ? 'missing' : `value:${member.phase.length}:${member.phase}`;
117
+ const group = groups.get(key);
118
+ if (group === undefined)
119
+ groups.set(key, { phase: member.phase, members: [member] });
120
+ else
121
+ group.members.push(member);
122
+ }
123
+ return [...groups].map(([key, group]) => ({ key, ...group }));
124
+ }
125
+ function appendItems(previous, next) {
126
+ if (previous.revision !== next.revision)
127
+ return undefined;
128
+ return { ...next, items: [...previous.items, ...next.items] };
129
+ }
130
+ /**
131
+ * Join only a same-revision chunk beginning at the exact prior UTF-8 byte end.
132
+ * Returning undefined forces the UI to preserve the good prefix and retry.
133
+ */
134
+ export function appendArtifactChunk(previous, next) {
135
+ const expectedOffset = previous.offsetBytes + previous.returnedBytes;
136
+ if (next.revision !== previous.revision
137
+ || next.artifact.name !== previous.artifact.name
138
+ || next.totalBytes !== previous.totalBytes
139
+ || next.offsetBytes !== expectedOffset)
140
+ return undefined;
141
+ return {
142
+ ...next,
143
+ text: previous.text + next.text,
144
+ offsetBytes: previous.offsetBytes,
145
+ returnedBytes: previous.returnedBytes + next.returnedBytes,
146
+ };
147
+ }
148
+ function editableTarget(target) {
149
+ return target instanceof HTMLElement
150
+ && target.closest('input, textarea, select, [contenteditable="true"]') !== null;
151
+ }
152
+ function focusable(root) {
153
+ return [...root.querySelectorAll('a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])')].filter(node => {
154
+ if (node.hidden || node.closest('[inert], [aria-hidden="true"]') !== null)
155
+ return false;
156
+ if (typeof window === 'undefined' || typeof window.getComputedStyle !== 'function')
157
+ return true;
158
+ for (let current = node; current !== null && current !== root; current = current.parentElement) {
159
+ const style = window.getComputedStyle(current);
160
+ if (style.display === 'none' || style.visibility === 'hidden')
161
+ return false;
162
+ }
163
+ return true;
164
+ });
165
+ }
166
+ function runFromDetails(error) {
167
+ const candidate = error.details?.run;
168
+ if (typeof candidate !== 'object' || candidate === null)
169
+ return undefined;
170
+ return candidate;
171
+ }
172
+ function ErrorRetry({ message, onRetry, disabled = false }) {
173
+ return (_jsxs("div", { className: css.error, role: "alert", children: [_jsx("p", { children: message }), _jsx("button", { type: "button", disabled: disabled, onClick: onRetry, children: "Retry" })] }));
174
+ }
175
+ function ResultView({ result }) {
176
+ const outcome = result.value;
177
+ if (outcome.state === 'pending')
178
+ return _jsxs("section", { children: [_jsx("h3", { children: "Pending result" }), _jsx("p", { children: "The workflow is still running." })] });
179
+ if (outcome.state === 'not-produced')
180
+ return _jsxs("section", { children: [_jsx("h3", { children: "No final result produced" }), _jsx("p", { children: "The workflow settled without a result." })] });
181
+ if (outcome.state === 'evicted')
182
+ return _jsxs("section", { children: [_jsx("h3", { children: "Final result evicted" }), _jsx("p", { children: "The result was removed by retention." })] });
183
+ if (outcome.state !== 'available')
184
+ return _jsxs("section", { children: [_jsx("h3", { children: "Pending result" }), _jsx("p", { children: "The workflow is still running." })] });
185
+ const available = outcome;
186
+ if (available.content.kind === 'preview') {
187
+ return (_jsxs("section", { children: [_jsx("h3", { children: "Truncated final result" }), _jsxs("p", { children: [utf8Bytes(available.content.text), " bytes retained of ", available.totalBytes, " bytes total."] }), _jsx("pre", { "aria-label": "Truncated final result preview", children: available.content.text })] }));
188
+ }
189
+ const value = available.content.value;
190
+ if (typeof value === 'string') {
191
+ return _jsxs("section", { children: [_jsx("h3", { children: "Final result" }), _jsx("div", { children: value }), result.error !== undefined && _jsx("p", { children: result.error })] });
192
+ }
193
+ let text = '[unavailable]';
194
+ try {
195
+ text = JSON.stringify(value, null, 2);
196
+ }
197
+ catch { /* keep bounded fallback */ }
198
+ return _jsxs("section", { children: [_jsx("h3", { children: "Final result" }), _jsx("pre", { children: text }), result.error !== undefined && _jsx("p", { children: result.error })] });
199
+ }
200
+ function PaneHeading({ title, onBack, backLabel }) {
201
+ return (_jsxs("header", { className: css.inspectorHeading, children: [_jsx("button", { type: "button", className: css.drilldownBack, onClick: onBack, children: backLabel }), _jsx("h2", { tabIndex: -1, children: title })] }));
202
+ }
203
+ /** Full-screen, lazy, revision-aware workflow dashboard. */
204
+ export function WorkflowsDashboard({ operations: suppliedOperations, controller, source: suppliedSource, sessionId, open: openProp = true, invoker, onClose, store, storeActions, labels: labelOverrides, }) {
205
+ const candidateOperations = (suppliedOperations ?? controller);
206
+ if (candidateOperations === undefined)
207
+ throw new Error('workflow dashboard operations are unavailable');
208
+ const operations = candidateOperations;
209
+ const labels = labelOverrides ?? dashboardLabelsFromLocale(workflowLocales.en);
210
+ const open = store === undefined ? openProp : store.open;
211
+ const rootRef = useRef(null);
212
+ const openerRef = useRef(null);
213
+ const onCloseRef = useRef(onClose);
214
+ onCloseRef.current = onClose;
215
+ const [source, setSource] = useState(() => (suppliedSource ?? (sessionId === undefined ? emptySource() : operations.get?.(sessionId) ?? emptySource(sessionId))));
216
+ const [localRunId, setLocalRunId] = useState();
217
+ const [localMobileView, setLocalMobileView] = useState('runs');
218
+ const [localTab, setLocalTab] = useState('members');
219
+ const [localMemberId, setLocalMemberId] = useState();
220
+ const [localArtifact, setLocalArtifact] = useState();
221
+ const [now, setNow] = useState(() => Date.now());
222
+ const [narrow, setNarrow] = useState(false);
223
+ const selectedRunId = store?.selectedRunId ?? localRunId;
224
+ const mobileView = store?.mobileView ?? localMobileView;
225
+ const tab = store?.inspectorTab ?? localTab;
226
+ const selectedMemberId = store?.selectedMemberId ?? localMemberId;
227
+ const selectedArtifact = store?.selectedArtifactName ?? localArtifact;
228
+ const [detail, setDetail] = useState({ phase: 'idle' });
229
+ const [members, setMembers] = useState({ phase: 'idle' });
230
+ const [memberDetail, setMemberDetail] = useState({ phase: 'idle' });
231
+ const [logs, setLogs] = useState({ phase: 'idle' });
232
+ const [result, setResult] = useState({ phase: 'idle' });
233
+ const [artifacts, setArtifacts] = useState({ phase: 'idle' });
234
+ const [artifactChunk, setArtifactChunk] = useState({ phase: 'idle' });
235
+ const [pendingControl, setPendingControl] = useState();
236
+ const [controlFeedback, setControlFeedback] = useState();
237
+ const [runPaging, setRunPaging] = useState(false);
238
+ const [runPageError, setRunPageError] = useState();
239
+ const [definitions, setDefinitions] = useState([]);
240
+ const [definitionsPhase, setDefinitionsPhase] = useState('idle');
241
+ const [startingName, setStartingName] = useState();
242
+ const [launchFeedback, setLaunchFeedback] = useState();
243
+ const readGeneration = useRef(0);
244
+ const reads = useRef(new Set());
245
+ const selectedRunRef = useRef(undefined);
246
+ const pendingControlRef = useRef(undefined);
247
+ const controlAbortRef = useRef(undefined);
248
+ const executeControlRef = useRef(() => undefined);
249
+ const membersRef = useRef(members);
250
+ membersRef.current = members;
251
+ const logsRef = useRef(logs);
252
+ logsRef.current = logs;
253
+ const artifactsRef = useRef(artifacts);
254
+ artifactsRef.current = artifacts;
255
+ const chunkRef = useRef(artifactChunk);
256
+ chunkRef.current = artifactChunk;
257
+ useEffect(() => {
258
+ if (suppliedSource !== undefined)
259
+ setSource(suppliedSource);
260
+ }, [suppliedSource]);
261
+ useEffect(() => {
262
+ if (suppliedSource !== undefined || sessionId === undefined || operations.subscribe === undefined)
263
+ return;
264
+ return operations.subscribe(sessionId, setSource);
265
+ }, [operations, sessionId, suppliedSource]);
266
+ useEffect(() => {
267
+ operations.observe(open ? sessionId : undefined);
268
+ return () => { operations.observe(undefined); };
269
+ }, [open, operations, sessionId]);
270
+ useEffect(() => {
271
+ if (!open || sessionId === undefined || typeof operations.listDefinitions !== 'function') {
272
+ if (typeof operations.listDefinitions !== 'function') {
273
+ setDefinitions([]);
274
+ setDefinitionsPhase('idle');
275
+ }
276
+ return;
277
+ }
278
+ const abort = new AbortController();
279
+ setDefinitionsPhase('loading');
280
+ void operations.listDefinitions(sessionId, abort.signal).then(items => {
281
+ if (abort.signal.aborted)
282
+ return;
283
+ setDefinitions(Array.isArray(items) ? items : []);
284
+ setDefinitionsPhase('ready');
285
+ }, error => {
286
+ if (abort.signal.aborted || isAbort(error))
287
+ return;
288
+ setDefinitions([]);
289
+ setDefinitionsPhase('error');
290
+ });
291
+ return () => { abort.abort(); };
292
+ }, [open, operations, sessionId]);
293
+ const rows = useMemo(() => orderWorkflowRuns(source.runs), [source.runs]);
294
+ const activeRows = useMemo(() => rows.filter(run => isActive(run.status)), [rows]);
295
+ const historyRows = useMemo(() => rows.filter(run => !isActive(run.status)), [rows]);
296
+ const selectedRun = (selectedRunId === undefined
297
+ ? rows[0]
298
+ : rows.find(run => run.runId === selectedRunId)) ?? rows[0];
299
+ selectedRunRef.current = selectedRun;
300
+ const selectedKey = selectedRun?.runId;
301
+ const visibleRunIds = useMemo(() => rows.map(run => run.runId), [rows]);
302
+ function selectRun(runId) {
303
+ if (typeof storeActions?.selectRun === 'function')
304
+ storeActions.selectRun(runId);
305
+ else {
306
+ setLocalRunId(runId);
307
+ setLocalMobileView('execution');
308
+ }
309
+ }
310
+ async function startDefinition(name) {
311
+ if (sessionId === undefined || typeof operations.launchDefinition !== 'function' || startingName !== undefined)
312
+ return;
313
+ setStartingName(name);
314
+ setLaunchFeedback(undefined);
315
+ try {
316
+ await operations.launchDefinition(sessionId, name);
317
+ setLaunchFeedback({ kind: 'notice', message: labels.started(name) });
318
+ }
319
+ catch (error) {
320
+ const reason = error instanceof Error && error.message.length > 0 ? error.message : labels.launchFailed;
321
+ setLaunchFeedback({ kind: 'error', message: reason });
322
+ setStartingName(undefined);
323
+ return;
324
+ }
325
+ try {
326
+ const snap = await operations.refresh(sessionId);
327
+ setSource(snap);
328
+ const newest = [...snap.runs]
329
+ .filter(run => run.name === name)
330
+ .sort((left, right) => right.startedAt - left.startedAt)[0];
331
+ if (newest !== undefined)
332
+ selectRun(newest.runId);
333
+ }
334
+ catch (error) {
335
+ // The launch already succeeded. Refresh failures are represented by the
336
+ // controller source and its single Retry surface; reporting the same
337
+ // failure as launch feedback duplicates the alert and leaves a stale
338
+ // error behind after source retry succeeds.
339
+ if (isAbort(error))
340
+ return;
341
+ }
342
+ finally {
343
+ setStartingName(undefined);
344
+ }
345
+ }
346
+ function selectMember(memberId) {
347
+ if (typeof storeActions?.selectMember === 'function')
348
+ storeActions.selectMember(memberId);
349
+ else {
350
+ setLocalMemberId(memberId);
351
+ setLocalTab('members');
352
+ setLocalMobileView('inspector');
353
+ }
354
+ }
355
+ function selectArtifact(name) {
356
+ if (typeof storeActions?.selectArtifact === 'function')
357
+ storeActions.selectArtifact(name);
358
+ else {
359
+ setLocalArtifact(name);
360
+ setLocalTab('artifacts');
361
+ setLocalMobileView('inspector');
362
+ }
363
+ }
364
+ function selectTab(next) {
365
+ if (typeof storeActions?.selectTab === 'function')
366
+ storeActions.selectTab(next);
367
+ else {
368
+ setLocalTab(next);
369
+ setLocalMobileView(next === 'members' ? 'execution' : 'inspector');
370
+ if (next !== 'members')
371
+ setLocalMemberId(undefined);
372
+ if (next !== 'artifacts')
373
+ setLocalArtifact(undefined);
374
+ }
375
+ }
376
+ function showRuns() {
377
+ if (typeof storeActions?.showRuns === 'function')
378
+ storeActions.showRuns();
379
+ else
380
+ setLocalMobileView('runs');
381
+ }
382
+ function showExecution() {
383
+ if (typeof storeActions?.showExecution === 'function')
384
+ storeActions.showExecution();
385
+ else
386
+ setLocalMobileView('execution');
387
+ }
388
+ useEffect(() => {
389
+ if (rows.length === 0) {
390
+ if (typeof storeActions?.reconcileRun === 'function' || typeof storeActions?.showRuns === 'function') {
391
+ if (selectedRunId !== undefined)
392
+ storeActions.reconcileRun?.(undefined, []);
393
+ if (store?.mobileView !== 'runs')
394
+ storeActions.showRuns?.();
395
+ }
396
+ else if (localRunId !== undefined || localMobileView !== 'runs') {
397
+ setLocalRunId(undefined);
398
+ setLocalMemberId(undefined);
399
+ setLocalArtifact(undefined);
400
+ setLocalMobileView('runs');
401
+ }
402
+ return;
403
+ }
404
+ if (selectedRunId === undefined || !rows.some(run => run.runId === selectedRunId)) {
405
+ if (typeof storeActions?.reconcileRun === 'function')
406
+ storeActions.reconcileRun(selectedRunId, visibleRunIds);
407
+ else {
408
+ setLocalRunId(rows[0].runId);
409
+ setLocalMemberId(undefined);
410
+ setLocalArtifact(undefined);
411
+ }
412
+ }
413
+ }, [localMobileView, localRunId, rows, selectedRunId, store?.mobileView, storeActions, visibleRunIds]);
414
+ useEffect(() => {
415
+ if (!open || activeRows.length === 0)
416
+ return;
417
+ setNow(Date.now());
418
+ if (typeof window === 'undefined')
419
+ return;
420
+ const timer = window.setInterval(() => { setNow(Date.now()); }, 1_000);
421
+ return () => { window.clearInterval(timer); };
422
+ }, [activeRows.length, open]);
423
+ useEffect(() => {
424
+ if (!open || typeof window === 'undefined')
425
+ return;
426
+ const onResize = () => { setNarrow(window.innerWidth < 1_200); };
427
+ onResize();
428
+ window.addEventListener('resize', onResize);
429
+ return () => { window.removeEventListener('resize', onResize); };
430
+ }, [open]);
431
+ function beginRead() {
432
+ const abort = new AbortController();
433
+ reads.current.add(abort);
434
+ return abort;
435
+ }
436
+ function currentRead(token, abort) {
437
+ return token === readGeneration.current && !abort.signal.aborted;
438
+ }
439
+ function endRead(abort) { reads.current.delete(abort); }
440
+ function mergeRun(run) {
441
+ setSource(previous => {
442
+ const index = previous.runs.findIndex(candidate => candidate.runId === run.runId);
443
+ if (index < 0)
444
+ return previous;
445
+ const current = previous.runs[index];
446
+ if (run.revision < current.revision)
447
+ return previous;
448
+ const next = [...previous.runs];
449
+ next[index] = run;
450
+ return { ...previous, runs: next, phase: 'ready', status: 'ready', error: undefined };
451
+ });
452
+ }
453
+ function loadDetail(runId, token = readGeneration.current) {
454
+ if (sessionId === undefined)
455
+ return;
456
+ const abort = beginRead();
457
+ setDetail(previous => ({ phase: 'loading', value: previous.value }));
458
+ void operations.detail(sessionId, runId, abort.signal).then(value => {
459
+ if (!currentRead(token, abort))
460
+ return;
461
+ setDetail({ phase: 'ready', value });
462
+ mergeRun(value.run);
463
+ }, error => {
464
+ if (currentRead(token, abort) && !isAbort(error)) {
465
+ setDetail(previous => ({ phase: 'error', value: previous.value, error: pageError(error) }));
466
+ }
467
+ }).finally(() => { endRead(abort); });
468
+ }
469
+ function loadMembers(runId, cursor, token = readGeneration.current) {
470
+ if (sessionId === undefined)
471
+ return;
472
+ const previous = cursor === undefined ? undefined : membersRef.current.value;
473
+ const abort = beginRead();
474
+ if (previous === undefined)
475
+ setMembers({ phase: 'loading' });
476
+ else
477
+ setMembers({ phase: 'ready', value: previous, paging: true });
478
+ void operations.members(sessionId, runId, cursor, abort.signal).then(page => {
479
+ if (!currentRead(token, abort))
480
+ return;
481
+ if (previous === undefined)
482
+ setMembers({ phase: 'ready', value: page });
483
+ else {
484
+ const joined = appendItems(previous, page);
485
+ setMembers(joined === undefined
486
+ ? { phase: 'ready', value: previous, pageError: GENERIC_LOAD_ERROR }
487
+ : { phase: 'ready', value: joined });
488
+ }
489
+ }, error => {
490
+ if (!currentRead(token, abort) || isAbort(error))
491
+ return;
492
+ const message = pageError(error);
493
+ setMembers(previous === undefined
494
+ ? { phase: 'error', error: message }
495
+ : { phase: 'ready', value: previous, pageError: message });
496
+ }).finally(() => { endRead(abort); });
497
+ }
498
+ function loadMemberDetail(runId, memberId, token = readGeneration.current) {
499
+ if (sessionId === undefined)
500
+ return;
501
+ const abort = beginRead();
502
+ setMemberDetail({ phase: 'loading' });
503
+ void operations.memberDetail(sessionId, runId, memberId, abort.signal).then(value => {
504
+ if (currentRead(token, abort))
505
+ setMemberDetail({ phase: 'ready', value });
506
+ }, error => {
507
+ if (currentRead(token, abort) && !isAbort(error))
508
+ setMemberDetail({ phase: 'error', error: GENERIC_LOAD_ERROR });
509
+ }).finally(() => { endRead(abort); });
510
+ }
511
+ function loadLogs(runId, cursor, token = readGeneration.current) {
512
+ if (sessionId === undefined)
513
+ return;
514
+ const previous = cursor === undefined ? undefined : logsRef.current.value;
515
+ const abort = beginRead();
516
+ if (previous === undefined)
517
+ setLogs({ phase: 'loading' });
518
+ else
519
+ setLogs({ phase: 'ready', value: previous, paging: true });
520
+ void operations.logs(sessionId, runId, cursor, abort.signal).then(page => {
521
+ if (!currentRead(token, abort))
522
+ return;
523
+ if (previous === undefined)
524
+ setLogs({ phase: 'ready', value: page });
525
+ else {
526
+ const joined = appendItems(previous, page);
527
+ setLogs(joined === undefined
528
+ ? { phase: 'ready', value: previous, pageError: GENERIC_LOAD_ERROR }
529
+ : { phase: 'ready', value: joined });
530
+ }
531
+ }, error => {
532
+ if (!currentRead(token, abort) || isAbort(error))
533
+ return;
534
+ const message = pageError(error);
535
+ setLogs(previous === undefined
536
+ ? { phase: 'error', error: message }
537
+ : { phase: 'ready', value: previous, pageError: message });
538
+ }).finally(() => { endRead(abort); });
539
+ }
540
+ function loadResult(runId, token = readGeneration.current) {
541
+ if (sessionId === undefined)
542
+ return;
543
+ const abort = beginRead();
544
+ setResult(previous => ({ phase: 'loading', value: previous.value }));
545
+ void operations.result(sessionId, runId, abort.signal).then(value => {
546
+ if (currentRead(token, abort))
547
+ setResult({ phase: 'ready', value });
548
+ }, error => {
549
+ if (currentRead(token, abort) && !isAbort(error)) {
550
+ setResult(previous => ({ phase: 'error', value: previous.value, error: pageError(error) }));
551
+ }
552
+ }).finally(() => { endRead(abort); });
553
+ }
554
+ function loadArtifacts(runId, cursor, token = readGeneration.current) {
555
+ if (sessionId === undefined)
556
+ return;
557
+ const previous = cursor === undefined ? undefined : artifactsRef.current.value;
558
+ const abort = beginRead();
559
+ if (previous === undefined)
560
+ setArtifacts({ phase: 'loading' });
561
+ else
562
+ setArtifacts({ phase: 'ready', value: previous, paging: true });
563
+ void operations.artifacts(sessionId, runId, cursor, abort.signal).then(page => {
564
+ if (!currentRead(token, abort))
565
+ return;
566
+ if (previous === undefined)
567
+ setArtifacts({ phase: 'ready', value: page });
568
+ else {
569
+ const joined = appendItems(previous, page);
570
+ setArtifacts(joined === undefined
571
+ ? { phase: 'ready', value: previous, pageError: GENERIC_LOAD_ERROR }
572
+ : { phase: 'ready', value: joined });
573
+ }
574
+ }, error => {
575
+ if (!currentRead(token, abort) || isAbort(error))
576
+ return;
577
+ const message = pageError(error);
578
+ setArtifacts(previous === undefined
579
+ ? { phase: 'error', error: message }
580
+ : { phase: 'ready', value: previous, pageError: message });
581
+ }).finally(() => { endRead(abort); });
582
+ }
583
+ function loadArtifact(runId, name, cursor, token = readGeneration.current) {
584
+ if (sessionId === undefined)
585
+ return;
586
+ const previous = cursor === undefined ? undefined : chunkRef.current.value;
587
+ const expectedRevision = artifactsRef.current.value?.revision;
588
+ const abort = beginRead();
589
+ if (previous === undefined)
590
+ setArtifactChunk({ phase: 'loading' });
591
+ else
592
+ setArtifactChunk({ phase: 'ready', value: previous, paging: true });
593
+ void operations.artifact(sessionId, runId, name, cursor, expectedRevision, abort.signal).then(chunk => {
594
+ if (!currentRead(token, abort))
595
+ return;
596
+ if (previous === undefined) {
597
+ if (chunk.offsetBytes !== 0)
598
+ setArtifactChunk({ phase: 'error', error: GENERIC_LOAD_ERROR });
599
+ else
600
+ setArtifactChunk({ phase: 'ready', value: chunk });
601
+ }
602
+ else {
603
+ const joined = appendArtifactChunk(previous, chunk);
604
+ setArtifactChunk(joined === undefined
605
+ ? { phase: 'ready', value: previous, pageError: GENERIC_LOAD_ERROR }
606
+ : { phase: 'ready', value: joined });
607
+ }
608
+ }, error => {
609
+ if (!currentRead(token, abort) || isAbort(error))
610
+ return;
611
+ const message = pageError(error);
612
+ setArtifactChunk(previous === undefined
613
+ ? { phase: 'error', error: message }
614
+ : { phase: 'ready', value: previous, pageError: message });
615
+ }).finally(() => { endRead(abort); });
616
+ }
617
+ useEffect(() => {
618
+ const token = ++readGeneration.current;
619
+ for (const request of reads.current)
620
+ request.abort('workflow selection changed');
621
+ reads.current.clear();
622
+ setDetail({ phase: 'idle' });
623
+ setMembers({ phase: 'idle' });
624
+ setMemberDetail({ phase: 'idle' });
625
+ setLogs({ phase: 'idle' });
626
+ setResult({ phase: 'idle' });
627
+ setArtifacts({ phase: 'idle' });
628
+ setArtifactChunk({ phase: 'idle' });
629
+ if (storeActions === undefined) {
630
+ setLocalMemberId(undefined);
631
+ setLocalArtifact(undefined);
632
+ setLocalTab('members');
633
+ }
634
+ controlAbortRef.current?.abort('workflow selection changed');
635
+ controlAbortRef.current = undefined;
636
+ pendingControlRef.current = undefined;
637
+ setPendingControl(undefined);
638
+ setControlFeedback(undefined);
639
+ if (open && selectedKey !== undefined && sessionId !== undefined) {
640
+ loadDetail(selectedKey, token);
641
+ loadMembers(selectedKey, undefined, token);
642
+ }
643
+ return () => {
644
+ if (readGeneration.current !== token)
645
+ return;
646
+ for (const request of reads.current)
647
+ request.abort('workflow selection changed');
648
+ reads.current.clear();
649
+ };
650
+ // Each run identity owns every subordinate cursor and selection.
651
+ // eslint-disable-next-line react-hooks/exhaustive-deps
652
+ }, [open, operations, selectedKey, sessionId]);
653
+ useEffect(() => {
654
+ if (!open || selectedKey === undefined)
655
+ return;
656
+ if (tab === 'logs' && logs.phase === 'idle')
657
+ loadLogs(selectedKey);
658
+ else if (tab === 'result' && result.phase === 'idle')
659
+ loadResult(selectedKey);
660
+ else if (tab === 'artifacts' && artifacts.phase === 'idle')
661
+ loadArtifacts(selectedKey);
662
+ // eslint-disable-next-line react-hooks/exhaustive-deps
663
+ }, [artifacts.phase, logs.phase, open, result.phase, selectedKey, tab]);
664
+ useEffect(() => {
665
+ if (!open || selectedKey === undefined || selectedMemberId === undefined)
666
+ return;
667
+ loadMemberDetail(selectedKey, selectedMemberId);
668
+ // eslint-disable-next-line react-hooks/exhaustive-deps
669
+ }, [open, selectedKey, selectedMemberId]);
670
+ useEffect(() => {
671
+ if (!open || selectedKey === undefined || selectedArtifact === undefined)
672
+ return;
673
+ setArtifactChunk({ phase: 'idle' });
674
+ loadArtifact(selectedKey, selectedArtifact);
675
+ // The collection revision fences every chunk for this selection.
676
+ // eslint-disable-next-line react-hooks/exhaustive-deps
677
+ }, [open, selectedArtifact, selectedKey, artifacts.value?.revision]);
678
+ useEffect(() => () => {
679
+ ++readGeneration.current;
680
+ for (const request of reads.current)
681
+ request.abort('workflow dashboard disposed');
682
+ reads.current.clear();
683
+ controlAbortRef.current?.abort('workflow dashboard disposed');
684
+ }, []);
685
+ function executeControl(action) {
686
+ const run = selectedRunRef.current;
687
+ if (sessionId === undefined || run === undefined || pendingControlRef.current !== undefined)
688
+ return;
689
+ if (!run.allowedActions.includes(action))
690
+ return;
691
+ const abort = new AbortController();
692
+ controlAbortRef.current?.abort('workflow control superseded');
693
+ controlAbortRef.current = abort;
694
+ pendingControlRef.current = action;
695
+ setPendingControl(action);
696
+ setControlFeedback(undefined);
697
+ void operations.control(sessionId, run.runId, action, run.revision, abort.signal).then(value => {
698
+ if (abort.signal.aborted || controlAbortRef.current !== abort)
699
+ return;
700
+ mergeRun(value.run);
701
+ setControlFeedback({ kind: 'notice', message: `${labels[action]} requested for ${value.run.displayName}.` });
702
+ }, error => {
703
+ if (abort.signal.aborted || controlAbortRef.current !== abort || isAbort(error))
704
+ return;
705
+ if (error instanceof WorkflowRunsRemoteError) {
706
+ const authoritative = runFromDetails(error);
707
+ if (authoritative !== undefined)
708
+ mergeRun(authoritative);
709
+ if (error.code === 'revision-conflict') {
710
+ setControlFeedback({ kind: 'error', message: STALE_CONTROL_ERROR });
711
+ return;
712
+ }
713
+ if (error.code === 'action-unavailable'
714
+ && error.details?.reason === 'budget-limited'
715
+ && action === 'resume') {
716
+ const displayName = authoritative?.displayName ?? run.displayName;
717
+ setControlFeedback({
718
+ kind: 'error',
719
+ message: `workflow "${displayName}" requires a higher agent_budget to resume`,
720
+ });
721
+ return;
722
+ }
723
+ }
724
+ setControlFeedback({ kind: 'error', message: GENERIC_CONTROL_ERROR, retryAction: action });
725
+ }).finally(() => {
726
+ if (controlAbortRef.current !== abort)
727
+ return;
728
+ controlAbortRef.current = undefined;
729
+ pendingControlRef.current = undefined;
730
+ setPendingControl(undefined);
731
+ });
732
+ }
733
+ executeControlRef.current = executeControl;
734
+ function loadMoreRuns() {
735
+ if (sessionId === undefined || source.nextCursor === undefined || runPaging)
736
+ return;
737
+ setRunPaging(true);
738
+ setRunPageError(undefined);
739
+ void operations.loadMore(sessionId).then(snapshot => {
740
+ // Do not depend on an external-store subscription being mounted in
741
+ // exactly the same render. The operation's authoritative snapshot also
742
+ // clears any prior source/page error in one update.
743
+ setSource(snapshot);
744
+ setRunPageError(undefined);
745
+ }, error => {
746
+ if (!isAbort(error))
747
+ setRunPageError(pageError(error));
748
+ }).finally(() => { setRunPaging(false); });
749
+ }
750
+ useEffect(() => {
751
+ if (!open)
752
+ return;
753
+ openerRef.current = invoker ?? (document.activeElement instanceof HTMLElement ? document.activeElement : null);
754
+ const root = rootRef.current;
755
+ if (root === null)
756
+ return;
757
+ const overlayLayer = root.closest('[data-shell-overlay]') ?? root;
758
+ const parent = overlayLayer.parentElement;
759
+ const siblings = parent === null ? [] : [...parent.children]
760
+ .filter((node) => node instanceof HTMLElement && node !== overlayLayer)
761
+ .map(element => ({
762
+ element,
763
+ inert: element.getAttribute('inert'),
764
+ ariaHidden: element.getAttribute('aria-hidden'),
765
+ }));
766
+ for (const { element } of siblings) {
767
+ element.setAttribute('inert', '');
768
+ element.setAttribute('aria-hidden', 'true');
769
+ }
770
+ root.focus();
771
+ const recoverFocus = (event) => {
772
+ if (event.target instanceof Node && (event.target === root || root.contains(event.target)))
773
+ return;
774
+ (focusable(root)[0] ?? root).focus();
775
+ };
776
+ const onKey = (event) => {
777
+ if (!(event.target instanceof Node) || (event.target !== root && !root.contains(event.target)))
778
+ return;
779
+ if (event.key === 'Escape') {
780
+ event.preventDefault();
781
+ event.stopPropagation();
782
+ onCloseRef.current?.();
783
+ return;
784
+ }
785
+ if (event.key === 'Tab') {
786
+ const targets = focusable(root);
787
+ if (targets.length === 0) {
788
+ event.preventDefault();
789
+ root.focus();
790
+ return;
791
+ }
792
+ const first = targets[0];
793
+ const last = targets.at(-1);
794
+ if (event.shiftKey && (document.activeElement === first || document.activeElement === root)) {
795
+ event.preventDefault();
796
+ last?.focus();
797
+ }
798
+ else if (!event.shiftKey && document.activeElement === last) {
799
+ event.preventDefault();
800
+ first?.focus();
801
+ }
802
+ return;
803
+ }
804
+ if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey || event.repeat || editableTarget(event.target))
805
+ return;
806
+ const action = SHORTCUTS[event.key.toLowerCase()];
807
+ const run = selectedRunRef.current;
808
+ if (action === undefined || run === undefined || !run.allowedActions.includes(action))
809
+ return;
810
+ event.preventDefault();
811
+ executeControlRef.current(action);
812
+ };
813
+ document.addEventListener('focusin', recoverFocus, true);
814
+ document.addEventListener('keydown', onKey, true);
815
+ return () => {
816
+ document.removeEventListener('focusin', recoverFocus, true);
817
+ document.removeEventListener('keydown', onKey, true);
818
+ for (const { element, inert, ariaHidden } of siblings) {
819
+ if (inert === null)
820
+ element.removeAttribute('inert');
821
+ else
822
+ element.setAttribute('inert', inert);
823
+ if (ariaHidden === null)
824
+ element.removeAttribute('aria-hidden');
825
+ else
826
+ element.setAttribute('aria-hidden', ariaHidden);
827
+ }
828
+ if (openerRef.current?.isConnected === true)
829
+ openerRef.current.focus();
830
+ openerRef.current = null;
831
+ };
832
+ }, [invoker, open]);
833
+ useEffect(() => {
834
+ if (!open || !narrow)
835
+ return;
836
+ const root = rootRef.current;
837
+ if (root === null)
838
+ return;
839
+ let target;
840
+ if (mobileView === 'runs') {
841
+ target = [...root.querySelectorAll('[data-workflow-run-id]')]
842
+ .find(element => element.dataset.workflowRunId === selectedKey);
843
+ }
844
+ else if (mobileView === 'execution') {
845
+ target = [...root.querySelectorAll('[data-workflow-member-id]')]
846
+ .find(element => element.dataset.workflowMemberId === selectedMemberId)
847
+ ?? [...root.querySelectorAll('[data-workflow-output-tab]')]
848
+ .find(element => element.dataset.workflowOutputTab === tab)
849
+ ?? root.querySelector('#workflow-run-heading');
850
+ }
851
+ else {
852
+ target = root.querySelector('[role="tab"][aria-selected="true"]')
853
+ ?? root.querySelector('[data-pane="inspector"] h2');
854
+ }
855
+ target?.focus();
856
+ }, [mobileView, narrow, open, selectedKey, selectedMemberId, tab]);
857
+ if (!open)
858
+ return null;
859
+ const memberRows = members.value?.items ?? [];
860
+ const currentMember = memberRows.find(member => member.memberId === selectedMemberId);
861
+ const execution = detail.value;
862
+ const renderPageError = (state, retry) => (state.pageError === undefined ? null : _jsx(ErrorRetry, { message: state.pageError, onRetry: retry, disabled: state.paging }));
863
+ const logsPane = () => {
864
+ if (logs.phase === 'loading' && logs.value === undefined)
865
+ return _jsx("p", { role: "status", children: "Loading logs\u2026" });
866
+ if (logs.phase === 'error' && logs.value === undefined)
867
+ return _jsx(ErrorRetry, { message: logs.error ?? GENERIC_LOAD_ERROR, onRetry: () => selectedKey !== undefined && loadLogs(selectedKey) });
868
+ const page = logs.value;
869
+ if (page === undefined)
870
+ return _jsx("p", { children: "Logs load on demand." });
871
+ return (_jsxs("div", { className: css.paneContents, children: [page.items.length === 0 && page.evicted === 0 && _jsx("p", { children: labels.noLogLines }), page.items.length === 0 && page.evicted > 0 && _jsx("p", { children: labels.noRetainedLogLines }), page.evicted > 0 && page.items.length > 0 && _jsxs("p", { children: [page.evicted, " earlier log lines were evicted by retention."] }), page.items.map(line => _jsxs("p", { className: css.logLine, children: [_jsx("code", { children: line.index }), _jsx("span", { children: line.text })] }, line.index)), _jsxs("p", { className: css.retention, children: ["Loaded ", page.items.length, " of ", page.total, " retained log lines."] }), page.nextCursor !== undefined && _jsx("button", { type: "button", disabled: logs.paging, onClick: () => selectedKey !== undefined && loadLogs(selectedKey, page.nextCursor), children: logs.paging ? 'Loading…' : 'Load more logs' }), renderPageError(logs, () => selectedKey !== undefined && page.nextCursor !== undefined && loadLogs(selectedKey, page.nextCursor))] }));
872
+ };
873
+ const resultPane = () => {
874
+ if (result.phase === 'loading' && result.value === undefined)
875
+ return _jsx("p", { role: "status", children: "Loading final result\u2026" });
876
+ if (result.phase === 'error' && result.value === undefined)
877
+ return _jsx(ErrorRetry, { message: result.error ?? GENERIC_LOAD_ERROR, onRetry: () => selectedKey !== undefined && loadResult(selectedKey) });
878
+ return result.value === undefined ? _jsx("p", { children: "Final result loads on demand." }) : (_jsxs("div", { className: css.paneContents, children: [_jsx(ResultView, { result: result.value }), result.phase === 'error' && _jsx(ErrorRetry, { message: result.error ?? GENERIC_LOAD_ERROR, onRetry: () => selectedKey !== undefined && loadResult(selectedKey) })] }));
879
+ };
880
+ const artifactPane = () => {
881
+ if (artifacts.phase === 'loading' && artifacts.value === undefined)
882
+ return _jsx("p", { role: "status", children: "Loading scratch artifacts\u2026" });
883
+ if (artifacts.phase === 'error' && artifacts.value === undefined)
884
+ return _jsx(ErrorRetry, { message: artifacts.error ?? GENERIC_LOAD_ERROR, onRetry: () => selectedKey !== undefined && loadArtifacts(selectedKey) });
885
+ const page = artifacts.value;
886
+ if (page === undefined)
887
+ return _jsx("p", { children: "Scratch artifacts load on demand." });
888
+ return (_jsxs("div", { className: css.paneContents, children: [page.items.length === 0 && page.omitted === 0 && _jsx("p", { children: "No scratch artifacts were produced." }), page.items.length === 0 && page.omitted > 0 && _jsx("p", { children: "All artifact names were omitted by retention." }), page.omitted > 0 && page.items.length > 0 && _jsxs("p", { children: [page.omitted, " artifact names were omitted by retention."] }), _jsx("div", { className: css.artifactList, children: page.items.map(item => (_jsxs("button", { type: "button", "aria-pressed": selectedArtifact === item.name, onClick: () => selectArtifact(item.name), children: [_jsx("span", { children: item.name }), _jsxs("span", { children: [item.bytes, " bytes"] })] }, item.name))) }), _jsxs("p", { className: css.retention, children: ["Loaded ", page.items.length, " of ", page.total, " retained artifact names."] }), page.nextCursor !== undefined && _jsx("button", { type: "button", disabled: artifacts.paging, onClick: () => selectedKey !== undefined && loadArtifacts(selectedKey, page.nextCursor), children: artifacts.paging ? 'Loading…' : 'Load more artifacts' }), renderPageError(artifacts, () => selectedKey !== undefined && page.nextCursor !== undefined && loadArtifacts(selectedKey, page.nextCursor)), selectedArtifact !== undefined && (_jsxs("section", { className: css.artifactViewer, "aria-label": `Artifact ${selectedArtifact}`, children: [_jsx("h3", { children: selectedArtifact }), artifactChunk.phase === 'loading' && artifactChunk.value === undefined && _jsx("p", { role: "status", children: "Loading artifact\u2026" }), artifactChunk.phase === 'error' && artifactChunk.value === undefined && _jsx(ErrorRetry, { message: artifactChunk.error ?? GENERIC_LOAD_ERROR, onRetry: () => selectedKey !== undefined && loadArtifact(selectedKey, selectedArtifact) }), artifactChunk.value !== undefined && (_jsxs(_Fragment, { children: [_jsx("pre", { children: artifactChunk.value.text }), _jsxs("p", { className: css.retention, children: [artifactChunk.value.returnedBytes, " of ", artifactChunk.value.totalBytes, " bytes loaded."] }), artifactChunk.value.nextCursor !== undefined && _jsx("button", { type: "button", disabled: artifactChunk.paging, onClick: () => selectedKey !== undefined && loadArtifact(selectedKey, selectedArtifact, artifactChunk.value?.nextCursor), children: artifactChunk.paging ? 'Loading…' : 'Load more artifact content' }), renderPageError(artifactChunk, () => selectedKey !== undefined && artifactChunk.value?.nextCursor !== undefined && loadArtifact(selectedKey, selectedArtifact, artifactChunk.value.nextCursor))] }))] }))] }));
889
+ };
890
+ const inspectorPane = () => {
891
+ if (tab === 'logs')
892
+ return _jsxs(_Fragment, { children: [_jsx(PaneHeading, { title: "Logs", onBack: showExecution, backLabel: labels.backExecution }), logsPane()] });
893
+ if (tab === 'result')
894
+ return _jsxs(_Fragment, { children: [_jsx(PaneHeading, { title: "Final result", onBack: showExecution, backLabel: labels.backExecution }), resultPane()] });
895
+ if (tab === 'artifacts')
896
+ return _jsxs(_Fragment, { children: [_jsx(PaneHeading, { title: "Scratch artifacts", onBack: showExecution, backLabel: labels.backExecution }), artifactPane()] });
897
+ if (selectedMemberId === undefined) {
898
+ return _jsxs(_Fragment, { children: [_jsx(PaneHeading, { title: "Member outcome", onBack: showExecution, backLabel: labels.backExecution }), _jsx("p", { children: "Select a member to inspect its outcome." })] });
899
+ }
900
+ return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: css.drilldownBack, onClick: showExecution, children: labels.backExecution }), _jsx(WorkflowMemberInspector, { member: currentMember, detail: memberDetail.value, loading: memberDetail.phase === 'loading', error: memberDetail.phase === 'error' ? memberDetail.error : undefined, onRetry: () => selectedKey !== undefined && loadMemberDetail(selectedKey, selectedMemberId), labels: labels, onOpenChild: memberDetail.value?.childSessionId === undefined || sessionId === undefined
901
+ ? undefined
902
+ : () => operations.resolveAndOpenChild(sessionId, memberDetail.value.childSessionId) })] }));
903
+ };
904
+ return (_jsx("div", { ref: rootRef, className: clsx(css.dashboard), role: "dialog", "aria-modal": "true", "aria-labelledby": "workflow-dashboard-title", tabIndex: -1, "data-workflows-dashboard": true, "data-mobile-view": mobileView, onMouseDown: event => {
905
+ if (event.target === event.currentTarget)
906
+ onCloseRef.current?.();
907
+ }, children: _jsxs("div", { className: css.frame, "data-workflows-frame": true, children: [_jsxs("header", { className: clsx(css.header), children: [_jsxs("div", { className: css.headerCopy, children: [_jsx("p", { className: css.eyebrow, children: "Background orchestration" }), _jsx("h1", { id: "workflow-dashboard-title", children: labels.title }), _jsxs("p", { className: css.topSummary, children: [definitionsPhase === 'ready' || definitions.length > 0 ? `${labels.savedCount(definitions.length)} · ` : '', activeRows.length, " active \u00B7 ", rows.length, " loaded of ", source.total, " runs"] })] }), _jsx("p", { className: css.kbdHint, children: labels.kbdHint }), _jsx("button", { type: "button", className: css.close, onClick: () => onCloseRef.current?.(), "aria-label": labels.close, children: "Close" })] }), source.phase === 'reconnecting' && rows.length > 0 && _jsx("p", { className: css.notice, role: "status", children: labels.reconnecting }), source.phase === 'loading' && rows.length === 0 && (_jsx("p", { className: css.notice, role: "status", children: labels.loading })), source.phase === 'error' && rows.length === 0 && (_jsx(ErrorRetry, { message: source.error ?? GENERIC_LOAD_ERROR, onRetry: () => { if (sessionId !== undefined)
908
+ void operations.refresh(sessionId).catch(() => undefined); } })), source.phase === 'reconnecting' && rows.length === 0 && (_jsx("p", { className: css.notice, role: "status", children: labels.reconnecting })), source.phase === 'error' && rows.length > 0 && runPageError === undefined && (_jsx(ErrorRetry, { message: GENERIC_LOAD_ERROR, onRetry: () => { if (sessionId !== undefined)
909
+ void operations.refresh(sessionId).catch(() => undefined); } })), launchFeedback !== undefined && (_jsx("div", { className: launchFeedback.kind === 'error' ? css.error : css.feedback, role: launchFeedback.kind === 'error' ? 'alert' : 'status', children: _jsx("p", { children: launchFeedback.message }) })), rows.length === 0 && source.phase !== 'loading' && source.phase !== 'error' && source.phase !== 'reconnecting' ? (typeof operations.listDefinitions === 'function' ? (_jsxs("main", { className: css.catalog, "aria-label": labels.savedTitle, children: [_jsxs("div", { className: css.catalogHead, children: [_jsx("h2", { children: labels.savedTitle }), _jsx("p", { children: labels.emptyRunsHint })] }), definitionsPhase === 'loading' && _jsx("p", { role: "status", children: labels.loadingSaved }), definitionsPhase === 'error' && (_jsx(ErrorRetry, { message: GENERIC_LOAD_ERROR, onRetry: () => {
910
+ if (sessionId === undefined || typeof operations.listDefinitions !== 'function')
911
+ return;
912
+ setDefinitionsPhase('loading');
913
+ void operations.listDefinitions(sessionId).then(items => {
914
+ setDefinitions(Array.isArray(items) ? items : []);
915
+ setDefinitionsPhase('ready');
916
+ }, () => { setDefinitionsPhase('error'); });
917
+ } })), definitionsPhase === 'ready' && definitions.length === 0 && _jsx("p", { children: labels.savedEmpty }), definitions.length > 0 && (_jsx("div", { className: css.savedGrid, children: definitions.map(definition => (_jsx(SavedCard, { definition: definition, starting: startingName, labels: labels, onStart: () => { void startDefinition(definition.name); } }, definition.name))) }))] })) : (_jsxs("main", { className: css.empty, children: [_jsx("h2", { children: labels.emptyTitle }), _jsx("p", { children: labels.emptyBody })] }))) : rows.length === 0 ? null : (_jsxs("div", { className: css.layout, "data-inspector": selectedMemberId !== undefined ? 'open' : 'closed', children: [_jsxs("nav", { className: css.navigator, "aria-label": "Workflow runs", "data-pane": "navigator", children: [definitions.length > 0 && (_jsxs("section", { className: css.runGroup, "aria-labelledby": "saved-workflows-heading", children: [_jsxs("h2", { id: "saved-workflows-heading", children: [labels.savedTitle, " \u00B7 ", definitions.length] }), definitions.map(definition => (_jsxs("div", { className: css.savedRow, children: [_jsxs("div", { children: [_jsx("strong", { children: definition.name }), _jsx("span", { children: definition.description })] }), _jsx("button", { type: "button", className: css.start, disabled: startingName !== undefined, "aria-label": `${labels.start} ${definition.name}`, onClick: () => { void startDefinition(definition.name); }, children: startingName === definition.name ? labels.starting : labels.start })] }, definition.name)))] })), _jsxs("section", { className: css.runGroup, "aria-labelledby": "active-workflows-heading", children: [_jsxs("h2", { id: "active-workflows-heading", children: ["Active \u00B7 ", activeRows.length] }), activeRows.length === 0 && _jsx("p", { className: css.groupEmpty, children: "No active runs" }), activeRows.map(run => _jsx(RunRow, { run: run, selected: run.runId === selectedKey, labels: labels, now: now, onSelect: () => selectRun(run.runId) }, run.runId))] }), _jsxs("section", { className: css.runGroup, "aria-labelledby": "workflow-history-heading", children: [_jsxs("h2", { id: "workflow-history-heading", children: ["History \u00B7 ", historyRows.length] }), historyRows.length === 0 && _jsx("p", { className: css.groupEmpty, children: "No settled runs" }), historyRows.map(run => _jsx(RunRow, { run: run, selected: run.runId === selectedKey, labels: labels, now: now, onSelect: () => selectRun(run.runId) }, run.runId))] }), _jsxs("footer", { className: css.navigatorFooter, children: [_jsxs("p", { children: [rows.length, " loaded of ", source.total, " runs"] }), source.nextCursor !== undefined && _jsx("button", { type: "button", disabled: runPaging, onClick: loadMoreRuns, children: runPaging ? 'Loading…' : 'Load more runs' }), runPageError !== undefined && _jsx(ErrorRetry, { message: runPageError, onRetry: loadMoreRuns, disabled: runPaging })] })] }), _jsxs("main", { className: css.detail, "aria-live": "polite", "data-pane": "execution", children: [_jsx("button", { type: "button", className: css.drilldownBack, onClick: showRuns, children: labels.backRuns }), selectedRun === undefined ? _jsx("p", { children: "Select a run to inspect its progress." }) : (_jsxs(_Fragment, { children: [_jsxs("header", { className: css.executionHeader, children: [_jsxs("div", { children: [_jsx("p", { className: css.eyebrow, children: statusLabel(selectedRun.status, labels) }), _jsx("h2", { id: "workflow-run-heading", tabIndex: -1, children: selectedRun.displayName }), _jsx("p", { children: selectedRun.description }), _jsx("p", { className: css.muted, children: formatDuration((selectedRun.settledAt ?? now) - selectedRun.startedAt) })] }), _jsx("div", { className: css.actions, "aria-label": `Controls for ${selectedRun.displayName}`, children: ACTION_ORDER.filter(action => selectedRun.allowedActions.includes(action)).map(action => (_jsx("button", { type: "button", disabled: pendingControl !== undefined, onClick: () => executeControl(action), children: labels[action] }, action))) })] }), controlFeedback !== undefined && (_jsxs("div", { className: controlFeedback.kind === 'error' ? css.error : css.feedback, role: controlFeedback.kind === 'error' ? 'alert' : 'status', children: [_jsx("p", { children: controlFeedback.message }), controlFeedback.retryAction !== undefined && _jsx("button", { type: "button", disabled: pendingControl !== undefined, onClick: () => executeControl(controlFeedback.retryAction), children: "Retry" })] })), selectedRun.status === 'budget-limited' && (_jsxs("aside", { className: css.callout, role: "note", children: [_jsx("strong", { children: labels.budgetLimitTitle }), _jsx("span", { children: labels.budgetLimitBody })] })), selectedRun.status === 'interrupted' && (_jsx("p", { className: css.notice, role: "status", children: labels.interruptedSettlement })), _jsxs("dl", { className: css.facts, children: [_jsxs("div", { children: [_jsx("dt", { children: "Status" }), _jsx("dd", { children: statusLabel(execution?.run.status ?? selectedRun.status, labels) })] }), _jsxs("div", { children: [_jsx("dt", { children: "Live phase" }), _jsxs("dd", { children: [_jsx("code", { children: execution?.run.phase ?? selectedRun.phase ?? labels.noPhaseYet }), (execution?.run.phase ?? selectedRun.phase) === '' && _jsx("span", { className: css.muted, children: " empty string" })] })] }), _jsxs("div", { children: [_jsx("dt", { children: "Agent budget" }), _jsxs("dd", { children: [selectedRun.budget.spent, "/", selectedRun.budget.total, " spent \u00B7 ", selectedRun.budget.remaining, " remaining"] })] }), _jsxs("div", { children: [_jsx("dt", { children: "Members" }), _jsx("dd", { children: memberSummary(selectedRun, labels) })] }), _jsxs("div", { children: [_jsx("dt", { children: "Stop reason" }), _jsx("dd", { children: selectedRun.terminal?.stopReason ?? '—' })] }), _jsxs("div", { children: [_jsx("dt", { children: "Result" }), _jsx("dd", { children: terminalResult(selectedRun) })] }), selectedRun.terminal?.error !== undefined && selectedRun.terminal.error !== INTERRUPTED_SETTLEMENT && _jsxs("div", { children: [_jsx("dt", { children: "Error" }), _jsx("dd", { children: selectedRun.terminal.error })] })] }), detail.phase === 'loading' && detail.value === undefined && _jsx("p", { role: "status", children: "Loading run detail\u2026" }), detail.phase === 'error' && _jsx(ErrorRetry, { message: detail.error ?? GENERIC_LOAD_ERROR, onRetry: () => loadDetail(selectedRun.runId) }), _jsxs("section", { "aria-labelledby": "declared-phases-heading", children: [_jsx("h3", { id: "declared-phases-heading", children: "Declared phases" }), _jsx("ol", { className: css.phaseRail, children: (() => {
918
+ const declared = declaredWorkflowPhases(execution, selectedRun, memberRows);
919
+ const live = execution?.run.phase ?? selectedRun.phase;
920
+ const currentIndex = live === undefined ? -1 : declared.findIndex(item => item.title === live);
921
+ return declared.map((phase, index) => {
922
+ const current = live !== undefined && phase.title === live;
923
+ const reached = currentIndex >= 0 && index < currentIndex;
924
+ return (_jsxs("li", { "data-current": current ? 'true' : 'false', title: phase.title, children: [_jsx("strong", { children: phase.title }), phase.detail !== undefined && _jsx("span", { children: phase.detail }), (phase.provider !== undefined || phase.model !== undefined) && _jsx("small", { children: [phase.provider, phase.model].filter(Boolean).join(' · ') }), _jsx("small", { children: current ? labels.livePhaseCurrent : reached ? labels.livePhaseReached : labels.livePhaseUpcoming })] }, `${index}:${phase.title}`));
925
+ });
926
+ })() }), declaredWorkflowPhases(execution, selectedRun, memberRows).length === 0 && execution !== undefined && _jsx("p", { children: "No declared phases." })] }), execution?.gate !== undefined && _jsxs("p", { className: css.notice, children: ["Waiting for input: ", execution.gate.message] }), execution?.error !== undefined && _jsxs("p", { className: css.errorText, children: ["Retained error: ", execution.error] }), _jsx("div", { className: css.tabs, role: "tablist", "aria-label": "Workflow execution details", children: ['members', 'logs', 'result', 'artifacts'].map(value => (_jsx("button", { type: "button", role: "tab", "aria-selected": tab === value, "data-workflow-output-tab": value, onClick: () => selectTab(value), children: value === 'members' ? 'Members' : value === 'logs' ? 'Logs' : value === 'result' ? 'Result' : 'Artifacts' }, value))) }), tab === 'members' && (_jsxs("section", { className: css.members, "aria-label": "Workflow members", children: [members.phase === 'loading' && members.value === undefined && _jsx("p", { role: "status", children: "Loading members\u2026" }), members.phase === 'error' && members.value === undefined && _jsx(ErrorRetry, { message: members.error ?? GENERIC_LOAD_ERROR, onRetry: () => loadMembers(selectedRun.runId) }), groupMembers(memberRows).map(group => {
927
+ const groupLabel = group.phase === undefined ? labels.unphased : group.phase === '' ? labels.emptyPhase : group.phase;
928
+ return (_jsxs("section", { className: css.memberGroup, "aria-label": groupLabel, children: [_jsx("h3", { children: groupLabel }), group.members.map(member => (_jsxs("button", { type: "button", "data-workflow-member-id": member.memberId, "data-status": member.status, "aria-pressed": selectedMemberId === member.memberId, onClick: () => selectMember(member.memberId), children: [_jsx("span", { children: member.label === '' ? 'Unnamed member' : member.label }), _jsx("span", { children: labels.memberStatus[member.status] }), _jsx("span", { children: memberOutcomeLabel(member, labels) })] }, member.memberId)))] }, group.key));
929
+ }), members.value !== undefined && memberRows.length === 0 && (_jsx("p", { className: css.activity, children: selectedRun.budget.spent > 0 ? labels.noMembersYet : 'No members started.' })), members.value !== undefined && _jsxs("p", { className: css.retention, children: ["Loaded ", memberRows.length, " of ", members.value.total, " members."] }), members.value?.nextCursor !== undefined && _jsx("button", { type: "button", disabled: members.paging, onClick: () => loadMembers(selectedRun.runId, members.value?.nextCursor), children: members.paging ? 'Loading…' : 'Load more members' }), renderPageError(members, () => members.value?.nextCursor !== undefined && loadMembers(selectedRun.runId, members.value.nextCursor))] }))] }))] }), _jsx("aside", { className: css.inspector, "aria-live": "polite", "data-pane": "inspector", children: inspectorPane() })] }))] }) }));
930
+ }
931
+ function SavedCard({ definition, starting, labels, onStart }) {
932
+ return (_jsxs("article", { className: css.savedCard, children: [_jsxs("div", { children: [_jsx("strong", { children: definition.name }), definition.description !== '' && _jsx("p", { children: definition.description }), _jsx("span", { className: css.muted, children: [definition.scope, definition.whenToUse].filter(Boolean).join(' · ') })] }), _jsx("button", { type: "button", className: css.start, disabled: starting !== undefined, "aria-label": `${labels.start} ${definition.name}`, onClick: onStart, children: starting === definition.name ? labels.starting : labels.start })] }));
933
+ }
934
+ function RunRow({ run, selected, onSelect, labels, now }) {
935
+ const settlement = run.status === 'interrupted'
936
+ && (run.terminal?.error === undefined || run.terminal.error === INTERRUPTED_SETTLEMENT)
937
+ ? labels.interruptedSettlement
938
+ : undefined;
939
+ return (_jsxs("button", { type: "button", className: css.runRow, "data-selected": selected ? 'true' : 'false', "data-status": run.status, "data-workflow-run-id": run.runId, "aria-pressed": selected, onClick: onSelect, children: [_jsxs("span", { className: css.runTitle, children: [_jsx("strong", { children: run.displayName }), _jsx("span", { children: statusLabel(run.status, labels) })] }), _jsx("span", { children: run.description }), _jsxs("span", { children: ["Phase: ", _jsx("code", { children: run.phase ?? labels.noPhaseYet }), run.phase === '' && ' (empty string)'] }), _jsxs("span", { children: [budgetSummary(run), " \u00B7 ", memberSummary(run, labels)] }), _jsx("span", { children: formatDuration((run.settledAt ?? now) - run.startedAt) }), _jsx("span", { children: terminalResult(run) }), run.terminal?.error !== undefined && run.terminal.error !== INTERRUPTED_SETTLEMENT && _jsxs("span", { children: ["Error: ", run.terminal.error] }), settlement !== undefined && _jsx("span", { children: settlement }), _jsxs("span", { children: ["Stop reason: ", run.terminal?.stopReason ?? '—'] })] }));
940
+ }
941
+ export default WorkflowsDashboard;
942
+ //# sourceMappingURL=WorkflowsDashboard.js.map