@deepseek-ai/dsh-client-ui-workflow-run 0.0.1-rc.2

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.
package/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, DeepSeek
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,6 @@
1
+ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
2
+ # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
+ # after editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/client/ui-workflow-run/README.md
5
+ README.md: 66539e0c16ac4102f9e1fe881106e6881b36a7d5
6
+ README.zh.md: a803857af24802e8a4645c4d5aca56c04424c85e
package/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # @deepseek-ai/dsh-client-ui-workflow-run
2
+
3
+ English | [中文](README.zh.md)
4
+
5
+ The browser plugin that reconstructs durable top-level workflow runs as independent Chat nodes. It consumes the four `tool-workflow/*` Session events owned by [`dsh-tool-workflow`](../../workflow/tool-workflow/README.md), registers one `ConversationNodeDefinition`, and renders through the keyed `conversation.chat.node` slot without changing the existing workflow tool card.
6
+
7
+ ## Durable state and replay
8
+
9
+ `tool-workflow/run-start` creates one Context keyed by `runId`; member starts, member endings, and the run ending update that Context in log order. A history tail containing only updates remains pending until an older page supplies the unique start, after which prepend, complete replay, and live append produce the same state. A closed Turn or Step with missing terminal events presents the affected run or members as interrupted without changing the tool result.
10
+
11
+ Phase groups come only from members that actually started. Exact phase strings share a group, an omitted phase is distinct from the empty string, and settlement changes status without removing or reordering members.
12
+
13
+ ## Presentation and navigation
14
+
15
+ The run and each phase have independent disclosure state. The run uses a 32-pixel `--dsw-alias-bg-module-platform` row with persistent right/down chevrons and an inline state dot plus status text, without a badge. Phases use 32-pixel disclosure rows with title and member count in the flexible main area and a fixed precise aggregate-status tail, without another dot. Members use a 16-pixel dot slot, a truncating name area, and a fixed 64-pixel status column. A running run initially expands; a terminal run loaded from history initially collapses. Local choices survive data updates while the keyed node remains mounted and reset only on a full remount.
16
+
17
+ A member opens a child Session only while every current fact agrees: the member is running, the child id is in the ordinary Session list, the row has `origin: 'subagent'`, its `parentId` is the current Session, and the list row is still running. Underlined member text is the only visible navigation affordance; keyboard focus draws a two-pixel business-primary ring around the name area, while status copy remains `Running`. The component calls only the injected ordinary `sessions.open(id)` action; remote, addressed-only, wrong-parent, or terminal rows remain non-interactive.
18
+
19
+ ## Composition
20
+
21
+ The package registers its Definition, locale dictionary, and `workflow-run` renderer as Cordis effects. Removing the client entry retracts all three contributions. The shipped Web bundle includes the plugin after `ui-conversation` and `ui-tool`.
22
+
23
+ ## Model Experience
24
+
25
+ None, as this package renders durable Session facts for humans and adds no prompt, tool schema, request content, or model-visible result.
26
+
27
+ #### KV Cache effect
28
+
29
+ None.
30
+
31
+ ## Known Limitations and Deferred Work
32
+
33
+ - Only top-level calls through `dsh-tool-workflow` produce these records; nested Code Mode calls and direct `WorkflowService` consumers do not.
34
+ - Navigation is intentionally live-only. Terminal members remain visible for review but never expose a cold-session opener from this node.
35
+ - The node shows run, phase, member identity, and status only; scripts, outputs, errors, logs, usage, static topology, and controls remain outside this surface.
package/README.zh.md ADDED
@@ -0,0 +1,35 @@
1
+ # @deepseek-ai/dsh-client-ui-workflow-run
2
+
3
+ [English](README.md) | 中文
4
+
5
+ 这个浏览器插件把持久化的顶层工作流运行重建为独立 Chat 节点。它消费由 [`dsh-tool-workflow`](../../workflow/tool-workflow/README.md) 拥有的四类 `tool-workflow/*` Session 事件,注册一个 `ConversationNodeDefinition`,并通过 keyed `conversation.chat.node` slot 渲染,不改变现有工作流工具卡。
6
+
7
+ ## 持久状态与回放
8
+
9
+ `tool-workflow/run-start` 以 `runId` 创建唯一 Context;成员开始、成员结束和运行结束事件按日志顺序更新该 Context。只有 update 的历史尾页会保持 pending,直到更早页面补入唯一 start;此后 prepend、完整回放和实时 append 得到相同状态。若所属 Turn 或 Step 已关闭但终点事件缺失,界面把相应运行或成员显示为已中断,而不改写工具结果。
10
+
11
+ 阶段组只来自真正开始过的成员。完全相同的阶段字符串归入同一组,字段缺省与空字符串保持不同身份;成员结算只改变状态,不删除或重排成员。
12
+
13
+ ## 展示与导航
14
+
15
+ 运行和每个阶段分别拥有本地 disclosure 状态。运行使用 32 像素 `--dsw-alias-bg-module-platform` 背景行,常驻向右/向下 chevron,并以内联状态点加状态文字表达结局,不使用胶囊。阶段使用 32 像素 disclosure 行,在可伸缩主区显示标题与成员数,在固定尾部精确显示聚合状态且不重复状态点。成员使用 16 像素状态点槽、可省略名称区和固定 64 像素状态列。运行中记录首次挂载时展开,从历史加载的终态记录首次挂载时折叠。只要 keyed 节点仍挂载,本地选择就在数据更新时保持;只有完整 remount 才重新初始化。
16
+
17
+ 只有所有实时事实同时成立时,成员才可打开子 Session:成员仍在运行、子 id 位于普通 Session 列表、列表行为 `origin: 'subagent'`、`parentId` 等于当前 Session,且列表行仍标记运行。带下划线的成员文字是唯一可见导航提示;键盘聚焦时,名称区显示 2 像素 business-primary 焦点环,右侧状态仍只显示“运行中”。组件只调用注入的普通 `sessions.open(id)`;远程、仅地址化、父级不符或终态的行都不可交互。
18
+
19
+ ## 装配
20
+
21
+ 本包把 Definition、locale 字典和 `workflow-run` renderer 都注册为 Cordis effect;移除客户端 entry 会撤销三者。shipped Web bundle 在 `ui-conversation` 与 `ui-tool` 之后装配该插件。
22
+
23
+ ## Model Experience
24
+
25
+ 无,因为本包只为人类展示持久 Session 事实,不增加 prompt、工具 schema、请求内容或模型可见结果。
26
+
27
+ #### KV Cache effect
28
+
29
+ 无。
30
+
31
+ ## Known Limitations and Deferred Work
32
+
33
+ - 只有经 `dsh-tool-workflow` 发起的顶层调用会生成这些记录;嵌套 Code Mode 调用和直接 `WorkflowService` 消费方不会生成。
34
+ - 导航刻意只面向实时运行。终态成员继续保留供复盘,但本节点永不为其提供冷 Session 入口。
35
+ - 节点只显示运行、阶段、成员身份与状态;脚本、输出、错误、日志、用量、静态拓扑和控制操作都不属于本界面。
package/lib/client.js ADDED
@@ -0,0 +1,457 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@deepseek-ai/dsh-client-ui-workflow-run",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react_jsx_runtime = require("react/jsx-runtime");
8
+ let react = require("react");
9
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
10
+ let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
11
+ //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-workflow-run/src/client/WorkflowRunPanel.module.css.mjs
12
+ const css = ".DBuyfa_root{width:100%;min-width:0}.DBuyfa_runHeader{box-sizing:border-box;background:var(--dsw-alias-bg-module-platform);cursor:pointer;border-radius:8px;align-items:center;gap:6px;width:100%;min-width:0;height:32px;padding:0 8px;display:flex}.DBuyfa_runHeader:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}.DBuyfa_runLeading{width:16px;height:16px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;margin-right:0;display:inline-flex}.DBuyfa_runTitle{max-width:42%;color:var(--dsw-alias-label-secondary);text-overflow:ellipsis;white-space:nowrap;flex:none;font-size:14px;font-weight:510;line-height:24px;overflow:hidden}.DBuyfa_runSummary{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;flex:1;font-size:12px;line-height:18px;overflow:hidden}.DBuyfa_statusTail{height:20px;color:var(--dsw-alias-label-secondary);white-space:nowrap;flex:none;align-items:center;gap:4px;font-size:11px;font-weight:510;line-height:16px;display:inline-flex;overflow:hidden}.DBuyfa_phaseHeader{box-sizing:border-box;cursor:pointer;align-items:center;gap:6px;width:100%;min-width:0;height:32px;display:flex}.DBuyfa_phaseHeader:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px;border-radius:4px}.DBuyfa_phaseLeading{width:16px;height:16px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;margin-right:0;display:inline-flex}.DBuyfa_phaseTitle{min-width:0;max-width:42%;color:var(--dsw-alias-label-secondary);text-overflow:ellipsis;white-space:nowrap;flex:0 auto;font-size:14px;line-height:24px;overflow:hidden}.DBuyfa_phaseCount{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;flex:1;font-size:13px;line-height:20px;overflow:hidden}.DBuyfa_phaseStatus{width:132px;color:var(--dsw-alias-label-secondary);text-align:right;text-overflow:ellipsis;white-space:nowrap;flex:none;font-size:13px;line-height:20px;overflow:hidden}.DBuyfa_separator{background:var(--dsw-alias-label-tertiary);border-radius:50%;flex:none;width:2px;height:2px}.DBuyfa_phaseList{flex-direction:column;gap:4px;min-width:0;padding:4px 0 0 16px;display:flex}.DBuyfa_phase{min-width:0}.DBuyfa_members{flex-direction:column;gap:2px;min-width:0;padding:0 0 0 16px;display:flex}.DBuyfa_memberRow,.DBuyfa_memberButton{width:100%;min-width:0;min-height:24px;color:var(--dsw-alias-label-secondary);font:inherit;text-align:left;background:0 0;border:0;border-radius:4px;align-items:center;gap:12px;padding:0;display:flex}.DBuyfa_memberButton{cursor:pointer}.DBuyfa_memberButton .DBuyfa_memberLabel{color:var(--dsw-alias-state-business-primary);text-underline-position:from-font;text-decoration:underline}.DBuyfa_dotSlot{flex:none;justify-content:center;align-items:center;width:16px;height:24px;display:inline-flex;overflow:hidden}.DBuyfa_memberButton:focus-visible{outline:none}.DBuyfa_memberButton:focus-visible .DBuyfa_memberLabelWrap{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-1px}.DBuyfa_memberLabelWrap{border-radius:4px;flex:1;align-items:center;min-width:0;height:24px;padding:0 2px;display:flex;overflow:hidden}.DBuyfa_memberLabel{min-width:0;color:var(--dsw-alias-label-secondary);text-overflow:ellipsis;white-space:nowrap;flex:1;font-size:14px;line-height:24px;overflow:hidden}.DBuyfa_memberStatus{width:64px;color:var(--dsw-alias-label-secondary);text-align:right;text-overflow:ellipsis;white-space:nowrap;flex:none;font-size:13px;line-height:20px;overflow:hidden}.DBuyfa_empty{color:var(--dsw-alias-label-tertiary);padding:0;font-size:13px;line-height:20px}@media (width<=560px){.DBuyfa_phaseList,.DBuyfa_members{padding-left:12px}}";
13
+ const tagId = "@deepseek-ai/dsh-client-ui-workflow-run/WorkflowRunPanel.module.css";
14
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
15
+ const tag = document.createElement("style");
16
+ tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-workflow-run";
17
+ tag.dataset.pluginCss = tagId;
18
+ tag.textContent = css;
19
+ document.head.appendChild(tag);
20
+ }
21
+ var WorkflowRunPanel_module_css_default = {
22
+ "members": "DBuyfa_members",
23
+ "runLeading": "DBuyfa_runLeading",
24
+ "phaseLeading": "DBuyfa_phaseLeading",
25
+ "phaseTitle": "DBuyfa_phaseTitle",
26
+ "separator": "DBuyfa_separator",
27
+ "memberLabel": "DBuyfa_memberLabel",
28
+ "runSummary": "DBuyfa_runSummary",
29
+ "dotSlot": "DBuyfa_dotSlot",
30
+ "phaseList": "DBuyfa_phaseList",
31
+ "memberStatus": "DBuyfa_memberStatus",
32
+ "runTitle": "DBuyfa_runTitle",
33
+ "memberRow": "DBuyfa_memberRow",
34
+ "phaseStatus": "DBuyfa_phaseStatus",
35
+ "empty": "DBuyfa_empty",
36
+ "statusTail": "DBuyfa_statusTail",
37
+ "memberButton": "DBuyfa_memberButton",
38
+ "memberLabelWrap": "DBuyfa_memberLabelWrap",
39
+ "phase": "DBuyfa_phase",
40
+ "phaseHeader": "DBuyfa_phaseHeader",
41
+ "phaseCount": "DBuyfa_phaseCount",
42
+ "root": "DBuyfa_root",
43
+ "runHeader": "DBuyfa_runHeader"
44
+ };
45
+ //#endregion
46
+ //#region lib/types/client/WorkflowRunPanel.js
47
+ const STATUS_KEYS = {
48
+ running: "status.running",
49
+ completed: "status.completed",
50
+ failed: "status.failed",
51
+ cancelled: "status.cancelled",
52
+ interrupted: "status.interrupted"
53
+ };
54
+ function dotState(status) {
55
+ switch (status) {
56
+ case "running": return "ongoing";
57
+ case "completed": return "done";
58
+ case "failed": return "error";
59
+ case "cancelled":
60
+ case "interrupted": return "warning";
61
+ /* v8 ignore next -- WorkflowRunStatus is closed and every variant is handled above. */
62
+ default: return status;
63
+ }
64
+ }
65
+ function readablePhase(phase, t) {
66
+ if (phase === null) return t("phase.unassigned");
67
+ return phase === "" ? t("phase.empty") : phase;
68
+ }
69
+ function readableMember(label, t) {
70
+ return label === "" ? t("member.empty") : label;
71
+ }
72
+ function statusCount(status, count, t) {
73
+ return t(`statusCount.${status}`, { count });
74
+ }
75
+ function memberCount(count, t) {
76
+ return t(count === 1 ? "run.members.one" : "run.members.other", { count });
77
+ }
78
+ function phaseStatusSummary(members, t) {
79
+ const counts = /* @__PURE__ */ new Map();
80
+ for (const member of members) counts.set(member.status, (counts.get(member.status) ?? 0) + 1);
81
+ const count = (status) => counts.get(status) ?? 0;
82
+ const active = [
83
+ "running",
84
+ "failed",
85
+ "cancelled",
86
+ "interrupted"
87
+ ].filter((status) => count(status) > 0);
88
+ if (active.length === 0) return statusCount("completed", count("completed"), t);
89
+ return (active.includes("interrupted") && count("completed") > 0 ? ["completed", ...active] : active).map((status) => statusCount(status, count(status), t)).join(" · ");
90
+ }
91
+ function navigableMembers(sessions, phases, parentId) {
92
+ const ordinary = new Set(sessions.ids);
93
+ const result = [];
94
+ for (const phase of phases) for (const member of phase.members) {
95
+ const summary = sessions.byId[member.childId];
96
+ if (member.status === "running" && ordinary.has(member.childId) && summary?.origin === "subagent" && summary.parentId === parentId && summary.running) result.push(member.childId);
97
+ }
98
+ return result;
99
+ }
100
+ function RunHeader({ count, name, onToggle, open, status, t }) {
101
+ return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DisclosureRow, {
102
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, {}),
103
+ title: t("run.title", { name }),
104
+ open,
105
+ expandable: true,
106
+ onToggle,
107
+ expandOnRowClick: true,
108
+ previewChevron: false,
109
+ keepContentWhenOpen: true,
110
+ rowClassName: WorkflowRunPanel_module_css_default.runHeader,
111
+ leadingClassName: WorkflowRunPanel_module_css_default.runLeading,
112
+ titleClassName: WorkflowRunPanel_module_css_default.runTitle,
113
+ collapsedContent: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
114
+ (0, react_jsx_runtime.jsx)("span", {
115
+ className: WorkflowRunPanel_module_css_default.separator,
116
+ "aria-hidden": true
117
+ }),
118
+ (0, react_jsx_runtime.jsx)("span", {
119
+ className: WorkflowRunPanel_module_css_default.runSummary,
120
+ children: memberCount(count, t)
121
+ }),
122
+ (0, react_jsx_runtime.jsxs)("span", {
123
+ className: WorkflowRunPanel_module_css_default.statusTail,
124
+ "data-status": status,
125
+ children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: dotState(status) }), (0, react_jsx_runtime.jsx)("span", { children: t(STATUS_KEYS[status]) })]
126
+ })
127
+ ] })
128
+ });
129
+ }
130
+ function MemberRow({ member, navigable, openSession, t }) {
131
+ const name = readableMember(member.label, t);
132
+ const content = (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
133
+ (0, react_jsx_runtime.jsx)("span", {
134
+ className: WorkflowRunPanel_module_css_default.dotSlot,
135
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: dotState(member.status) })
136
+ }),
137
+ (0, react_jsx_runtime.jsx)("span", {
138
+ className: WorkflowRunPanel_module_css_default.memberLabelWrap,
139
+ "data-member-label-wrap": true,
140
+ children: (0, react_jsx_runtime.jsx)("span", {
141
+ className: WorkflowRunPanel_module_css_default.memberLabel,
142
+ "data-member-label": true,
143
+ children: name
144
+ })
145
+ }),
146
+ (0, react_jsx_runtime.jsx)("span", {
147
+ className: WorkflowRunPanel_module_css_default.memberStatus,
148
+ "data-member-status-text": true,
149
+ children: t(STATUS_KEYS[member.status])
150
+ })
151
+ ] });
152
+ if (!navigable) return (0, react_jsx_runtime.jsx)("div", {
153
+ className: WorkflowRunPanel_module_css_default.memberRow,
154
+ "data-member-status": member.status,
155
+ children: content
156
+ });
157
+ return (0, react_jsx_runtime.jsx)("button", {
158
+ type: "button",
159
+ className: WorkflowRunPanel_module_css_default.memberButton,
160
+ "data-member-status": member.status,
161
+ "aria-label": t("member.open", { name }),
162
+ onClick: () => {
163
+ openSession(member.childId);
164
+ },
165
+ children: content
166
+ });
167
+ }
168
+ function PhaseSection({ phase, navigable, openSession, t }) {
169
+ const [open, setOpen] = (0, react.useState)(false);
170
+ const toggle = () => {
171
+ setOpen((value) => !value);
172
+ };
173
+ return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DisclosureRow, {
174
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, {}),
175
+ title: readablePhase(phase.phase, t),
176
+ open,
177
+ expandable: true,
178
+ onToggle: toggle,
179
+ expandOnRowClick: true,
180
+ previewChevron: false,
181
+ keepContentWhenOpen: true,
182
+ className: WorkflowRunPanel_module_css_default.phase,
183
+ rowClassName: WorkflowRunPanel_module_css_default.phaseHeader,
184
+ leadingClassName: WorkflowRunPanel_module_css_default.phaseLeading,
185
+ titleClassName: WorkflowRunPanel_module_css_default.phaseTitle,
186
+ collapsedContent: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
187
+ (0, react_jsx_runtime.jsx)("span", {
188
+ className: WorkflowRunPanel_module_css_default.separator,
189
+ "aria-hidden": true
190
+ }),
191
+ (0, react_jsx_runtime.jsx)("span", {
192
+ className: WorkflowRunPanel_module_css_default.phaseCount,
193
+ "data-phase-count": true,
194
+ children: memberCount(phase.members.length, t)
195
+ }),
196
+ (0, react_jsx_runtime.jsx)("span", {
197
+ className: WorkflowRunPanel_module_css_default.phaseStatus,
198
+ "data-phase-status-text": true,
199
+ children: phaseStatusSummary(phase.members, t)
200
+ })
201
+ ] }),
202
+ children: (0, react_jsx_runtime.jsx)("div", {
203
+ className: WorkflowRunPanel_module_css_default.members,
204
+ children: phase.members.map((member) => (0, react_jsx_runtime.jsx)(MemberRow, {
205
+ member,
206
+ navigable: navigable.includes(member.childId),
207
+ openSession,
208
+ t
209
+ }, member.seq))
210
+ })
211
+ });
212
+ }
213
+ /** Render one durable workflow run with independent run and phase disclosure. */
214
+ function WorkflowRunPanel({ node, sessionId, useSessions, openSession, t }) {
215
+ const [open, setOpen] = (0, react.useState)(() => node.data.status === "running");
216
+ const memberCount = node.data.phases.reduce((count, phase) => count + phase.members.length, 0);
217
+ const navigable = useSessions((sessions) => navigableMembers(sessions, node.data.phases, sessionId), _deepseek_ai_dsh_client_runtime_client.shallowEqual);
218
+ return (0, react_jsx_runtime.jsxs)("section", {
219
+ className: WorkflowRunPanel_module_css_default.root,
220
+ "data-workflow-run": true,
221
+ "data-run-status": node.data.status,
222
+ children: [(0, react_jsx_runtime.jsx)(RunHeader, {
223
+ count: memberCount,
224
+ name: node.data.name,
225
+ open,
226
+ status: node.data.status,
227
+ t,
228
+ onToggle: () => {
229
+ setOpen((value) => !value);
230
+ }
231
+ }), open && (0, react_jsx_runtime.jsx)("div", {
232
+ className: WorkflowRunPanel_module_css_default.phaseList,
233
+ children: node.data.phases.length === 0 ? (0, react_jsx_runtime.jsx)("span", {
234
+ className: WorkflowRunPanel_module_css_default.empty,
235
+ children: t("run.empty")
236
+ }) : node.data.phases.map((phase) => (0, react_jsx_runtime.jsx)(PhaseSection, {
237
+ phase,
238
+ navigable,
239
+ openSession,
240
+ t
241
+ }, phase.key))
242
+ })]
243
+ });
244
+ }
245
+ //#endregion
246
+ //#region lib/types/client/locales.js
247
+ /** `workflowRun` namespace dictionaries. */
248
+ /** Dictionary namespace owned by this plugin. */
249
+ const NS = "workflowRun";
250
+ /** Simplified Chinese dictionary (the key-set source of truth). */
251
+ const zh = {
252
+ "run.title": "{name}",
253
+ "run.members.one": "{count} 个成员",
254
+ "run.members.other": "{count} 个成员",
255
+ "run.empty": "没有启动成员",
256
+ "phase.unassigned": "未分阶段",
257
+ "phase.empty": "空阶段名",
258
+ "statusCount.running": "运行中 {count}",
259
+ "statusCount.completed": "已完成 {count}",
260
+ "statusCount.failed": "失败 {count}",
261
+ "statusCount.cancelled": "已取消 {count}",
262
+ "statusCount.interrupted": "已中断 {count}",
263
+ "member.empty": "空成员名",
264
+ "member.open": "打开 {name}",
265
+ "status.running": "运行中",
266
+ "status.completed": "已完成",
267
+ "status.failed": "失败",
268
+ "status.cancelled": "已取消",
269
+ "status.interrupted": "已中断"
270
+ };
271
+ /** English dictionary (same key set). */
272
+ const en = {
273
+ "run.title": "{name}",
274
+ "run.members.one": "{count} member",
275
+ "run.members.other": "{count} members",
276
+ "run.empty": "No members started",
277
+ "phase.unassigned": "Unphased",
278
+ "phase.empty": "Empty phase name",
279
+ "statusCount.running": "Running {count}",
280
+ "statusCount.completed": "Completed {count}",
281
+ "statusCount.failed": "Failed {count}",
282
+ "statusCount.cancelled": "Cancelled {count}",
283
+ "statusCount.interrupted": "Interrupted {count}",
284
+ "member.empty": "Empty member name",
285
+ "member.open": "Open {name}",
286
+ "status.running": "Running",
287
+ "status.completed": "Completed",
288
+ "status.failed": "Failed",
289
+ "status.cancelled": "Cancelled",
290
+ "status.interrupted": "Interrupted"
291
+ };
292
+ //#endregion
293
+ //#region lib/types/client/workflow-definition.js
294
+ /**
295
+ * Build a collision-free phase key preserving absent versus empty identity.
296
+ * @param phase - exact phase string, or null for an omitted field.
297
+ * @returns the stable renderer key for that phase identity.
298
+ */
299
+ function workflowPhaseKey(phase) {
300
+ return phase === null ? "missing" : `value:${phase.length}:${phase}`;
301
+ }
302
+ function statusFromStopReason(stopReason) {
303
+ switch (stopReason) {
304
+ case "completed": return "completed";
305
+ case "cancelled": return "cancelled";
306
+ case "error": return "failed";
307
+ /* v8 ignore next -- WorkflowStopReason is closed and every variant is handled above. */
308
+ default: return stopReason;
309
+ }
310
+ }
311
+ function statusFromOutcome(outcome) {
312
+ switch (outcome) {
313
+ case "completed": return "completed";
314
+ case "cancelled": return "cancelled";
315
+ case "failed": return "failed";
316
+ /* v8 ignore next -- WorkflowAgentOutcome is closed and every variant is handled above. */
317
+ default: return outcome;
318
+ }
319
+ }
320
+ function locationClosed(location) {
321
+ if (location.kind === "step") return location.step.status === "closed" || location.turn.status === "closed";
322
+ return location.kind === "turn" && location.turn.status === "closed";
323
+ }
324
+ function projectWorkflow(context, location) {
325
+ const state = context.state;
326
+ const interrupted = state.stopReason === void 0 && locationClosed(location);
327
+ const phases = /* @__PURE__ */ new Map();
328
+ for (const member of state.members) {
329
+ const phase = member.phase === void 0 ? null : member.phase;
330
+ const key = workflowPhaseKey(phase);
331
+ let group = phases.get(key);
332
+ if (group === void 0) {
333
+ group = {
334
+ phase,
335
+ members: []
336
+ };
337
+ phases.set(key, group);
338
+ }
339
+ group.members.push({
340
+ seq: member.seq,
341
+ label: member.label,
342
+ childId: member.childId,
343
+ status: member.outcome === void 0 ? interrupted ? "interrupted" : "running" : statusFromOutcome(member.outcome)
344
+ });
345
+ }
346
+ const projectedPhases = [...phases].map(([key, phase]) => ({
347
+ key,
348
+ phase: phase.phase,
349
+ members: phase.members
350
+ }));
351
+ return {
352
+ name: state.name,
353
+ status: state.stopReason === void 0 ? interrupted ? "interrupted" : "running" : statusFromStopReason(state.stopReason),
354
+ phases: projectedPhases
355
+ };
356
+ }
357
+ function updateAgentStart(state, data) {
358
+ const member = {
359
+ seq: data.seq,
360
+ label: data.label,
361
+ ...data.phase === void 0 ? {} : { phase: data.phase },
362
+ childId: data.childId
363
+ };
364
+ return {
365
+ ...state,
366
+ members: [...state.members, member]
367
+ };
368
+ }
369
+ function updateAgentEnd(state, data) {
370
+ return {
371
+ ...state,
372
+ members: state.members.map((member) => member.seq === data.seq ? {
373
+ ...member,
374
+ outcome: data.outcome
375
+ } : member)
376
+ };
377
+ }
378
+ /** Durable workflow event family folded into one keyed Chat node. */
379
+ const workflowRunDefinition = {
380
+ kind: "workflow-run",
381
+ target: "chat",
382
+ match: (event) => {
383
+ if (event.type === "tool-workflow/run-start") return {
384
+ id: String(event.data.runId),
385
+ role: "start"
386
+ };
387
+ if (event.type === "tool-workflow/agent-start" || event.type === "tool-workflow/agent-end" || event.type === "tool-workflow/run-end") return {
388
+ id: String(event.data.runId),
389
+ role: "update"
390
+ };
391
+ return null;
392
+ },
393
+ start: (_context, match) => {
394
+ if (match.event.type !== "tool-workflow/run-start") throw new Error("workflow-run start requires tool-workflow/run-start");
395
+ return {
396
+ name: match.event.data.name,
397
+ members: []
398
+ };
399
+ },
400
+ update: (context, match) => {
401
+ if (match.event.type === "tool-workflow/agent-start") return updateAgentStart(context.state, match.event.data);
402
+ if (match.event.type === "tool-workflow/agent-end") return updateAgentEnd(context.state, match.event.data);
403
+ if (match.event.type === "tool-workflow/run-end") return {
404
+ ...context.state,
405
+ stopReason: match.event.data.stopReason
406
+ };
407
+ return context.state;
408
+ },
409
+ buildViewNode: (context) => {
410
+ if (context.start === void 0) return null;
411
+ const data = projectWorkflow(context, context.start.location);
412
+ return {
413
+ key: context.key,
414
+ kind: "workflow-run",
415
+ id: context.id,
416
+ target: "chat",
417
+ anchorSeq: context.start.event.seq,
418
+ location: context.start.location,
419
+ visibility: "visible",
420
+ data
421
+ };
422
+ }
423
+ };
424
+ //#endregion
425
+ //#region lib/types/client/index.js
426
+ /** Browser plugin for durable workflow-run Conversation Nodes. */
427
+ /** Required services for Definition, keyed renderer, navigation, and copy. */
428
+ const inject = [
429
+ "conversationEvents",
430
+ "slots",
431
+ "sessions",
432
+ "locale"
433
+ ];
434
+ /** Register the workflow Definition, dictionary, and keyed Chat renderer. */
435
+ function apply(ctx) {
436
+ ctx.conversationEvents.register(workflowRunDefinition);
437
+ ctx.effect(() => ctx.locale.register(NS, {
438
+ zh,
439
+ en
440
+ }), "ui-workflow-run: dictionaries");
441
+ ctx.slots.inject("conversation.chat.node", () => ctx.slots.register({
442
+ name: "conversation.chat.node",
443
+ key: "workflow-run",
444
+ locale: NS,
445
+ inject: () => ({ openSession: (id) => {
446
+ ctx.sessions.open(id);
447
+ } })
448
+ }, WorkflowRunPanel));
449
+ }
450
+ //#endregion
451
+ exports.apply = apply;
452
+ exports.inject = inject;
453
+ return module.exports;
454
+ }
455
+ });
456
+
457
+ //# sourceMappingURL=client.js.map
package/lib/index.js ADDED
@@ -0,0 +1,6 @@
1
+ //#region lib/types/index.js
2
+ /** Durable workflow-run UI plugin, node half. */
3
+ /** Host plugin body; the feature is entirely browser-side. */
4
+ function apply() {}
5
+ //#endregion
6
+ export { apply };
@@ -0,0 +1,17 @@
1
+ //#region lib/types/invariant.js
2
+ /** Package-owned invariant companion for the workflow-run UI plugin. */
3
+ const PACKAGE_NAME = "@deepseek-ai/dsh-client-ui-workflow-run";
4
+ /** Cordis companion plugin name. */
5
+ const name = "client-ui-workflow-run-invariant";
6
+ /** Service required before the companion can reserve package ownership. */
7
+ const inject = ["invariants"];
8
+ /**
9
+ * No runtime invariant: the browser plugin contributes one effect-owned
10
+ * Conversation Definition, keyed renderer, and dictionary; tests prove their
11
+ * disposal and the Host tool package owns the durable event invariant.
12
+ */
13
+ const install = () => {};
14
+ /** Register this package's invariant companion. */
15
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
16
+ //#endregion
17
+ export { apply, inject, name };
@@ -0,0 +1,11 @@
1
+ import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
2
+ import { type SessionId } from '@deepseek-ai/dsh-client-runtime/client';
3
+ /** Navigation action injected from the plugin's own SessionsService access. */
4
+ export interface WorkflowRunInjected {
5
+ readonly openSession: (id: SessionId) => void;
6
+ }
7
+ /** Complete keyed Chat renderer props. */
8
+ export type WorkflowRunPanelProps = PropsRuntime<'conversation.chat.node', 'workflow-run'> & PropsLocale<'workflowRun'> & WorkflowRunInjected;
9
+ /** Render one durable workflow run with independent run and phase disclosure. */
10
+ export declare function WorkflowRunPanel({ node, sessionId, useSessions, openSession, t }: WorkflowRunPanelProps): import("react").JSX.Element;
11
+ //# sourceMappingURL=WorkflowRunPanel.d.ts.map
@@ -0,0 +1,14 @@
1
+ /** Browser plugin for durable workflow-run Conversation Nodes. */
2
+ import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
3
+ import { type WorkflowRunKey } from './locales.ts';
4
+ declare module '@deepseek-ai/dsh-client-ui-slots' {
5
+ interface LocaleNamespaceMap {
6
+ /** Durable workflow-run node copy. */
7
+ workflowRun: WorkflowRunKey;
8
+ }
9
+ }
10
+ /** Required services for Definition, keyed renderer, navigation, and copy. */
11
+ export declare const inject: string[];
12
+ /** Register the workflow Definition, dictionary, and keyed Chat renderer. */
13
+ export declare function apply(ctx: ClientContext): void;
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,29 @@
1
+ /** `workflowRun` namespace dictionaries. */
2
+ /** Dictionary namespace owned by this plugin. */
3
+ export declare const NS = "workflowRun";
4
+ /** Simplified Chinese dictionary (the key-set source of truth). */
5
+ export declare const zh: {
6
+ 'run.title': string;
7
+ 'run.members.one': string;
8
+ 'run.members.other': string;
9
+ 'run.empty': string;
10
+ 'phase.unassigned': string;
11
+ 'phase.empty': string;
12
+ 'statusCount.running': string;
13
+ 'statusCount.completed': string;
14
+ 'statusCount.failed': string;
15
+ 'statusCount.cancelled': string;
16
+ 'statusCount.interrupted': string;
17
+ 'member.empty': string;
18
+ 'member.open': string;
19
+ 'status.running': string;
20
+ 'status.completed': string;
21
+ 'status.failed': string;
22
+ 'status.cancelled': string;
23
+ 'status.interrupted': string;
24
+ };
25
+ /** English dictionary (same key set). */
26
+ export declare const en: Record<WorkflowRunKey, string>;
27
+ /** Union of this namespace's dictionary keys. */
28
+ export type WorkflowRunKey = keyof typeof zh;
29
+ //# sourceMappingURL=locales.d.ts.map
@@ -0,0 +1,50 @@
1
+ import type { ConversationNodeDefinition } from '@deepseek-ai/dsh-client-runtime/client';
2
+ import type { SessionId } from '@deepseek-ai/dsh-session/types';
3
+ import type { ToolWorkflowAgentStartData } from '@deepseek-ai/dsh-tool-workflow/types';
4
+ import type { WorkflowAgentOutcome, WorkflowStopReason } from '@deepseek-ai/dsh-workflow/types';
5
+ /** Status shown for a workflow, phase, or member. */
6
+ export type WorkflowRunStatus = 'running' | 'completed' | 'failed' | 'cancelled' | 'interrupted';
7
+ /** Final renderer data for one member. */
8
+ export interface WorkflowRunMemberData {
9
+ readonly seq: number;
10
+ readonly label: string;
11
+ readonly childId: SessionId;
12
+ readonly status: WorkflowRunStatus;
13
+ }
14
+ /** Final renderer data for one exact phase identity. */
15
+ export interface WorkflowRunPhaseData {
16
+ readonly key: string;
17
+ /** `null` is the absent field; the empty string remains a distinct identity. */
18
+ readonly phase: string | null;
19
+ readonly members: readonly WorkflowRunMemberData[];
20
+ }
21
+ /** Final keyed Chat payload for one workflow run. */
22
+ export interface WorkflowRunChatData {
23
+ readonly name: string;
24
+ readonly status: WorkflowRunStatus;
25
+ readonly phases: readonly WorkflowRunPhaseData[];
26
+ }
27
+ declare module '@deepseek-ai/dsh-client-ui-conversation/client' {
28
+ interface ChatNodeDataMap {
29
+ /** Durable top-level workflow run and all members that actually started. */
30
+ 'workflow-run': WorkflowRunChatData;
31
+ }
32
+ }
33
+ interface WorkflowMemberState extends Omit<ToolWorkflowAgentStartData, 'runId'> {
34
+ readonly outcome?: WorkflowAgentOutcome;
35
+ }
36
+ interface WorkflowState {
37
+ readonly name: string;
38
+ readonly stopReason?: WorkflowStopReason;
39
+ readonly members: readonly WorkflowMemberState[];
40
+ }
41
+ /**
42
+ * Build a collision-free phase key preserving absent versus empty identity.
43
+ * @param phase - exact phase string, or null for an omitted field.
44
+ * @returns the stable renderer key for that phase identity.
45
+ */
46
+ export declare function workflowPhaseKey(phase: string | null): string;
47
+ /** Durable workflow event family folded into one keyed Chat node. */
48
+ export declare const workflowRunDefinition: ConversationNodeDefinition<WorkflowState>;
49
+ export {};
50
+ //# sourceMappingURL=workflow-definition.d.ts.map
@@ -0,0 +1,4 @@
1
+ /** Durable workflow-run UI plugin, node half. */
2
+ /** Host plugin body; the feature is entirely browser-side. */
3
+ export declare function apply(): void;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,9 @@
1
+ /** Package-owned invariant companion for the workflow-run UI plugin. */
2
+ import type { Context } from '@deepseek-ai/cordis';
3
+ /** Cordis companion plugin name. */
4
+ export declare const name = "client-ui-workflow-run-invariant";
5
+ /** Service required before the companion can reserve package ownership. */
6
+ export declare const inject: string[];
7
+ /** Register this package's invariant companion. */
8
+ export declare const apply: (ctx: Context) => Promise<() => void>;
9
+ //# sourceMappingURL=invariant.d.ts.map
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "@deepseek-ai/dsh-client-ui-workflow-run",
3
+ "description": "Durable workflow-run Conversation Node and nested member disclosure for dsh web",
4
+ "version": "0.0.1-rc.2",
5
+ "publishConfig": {
6
+ "access": "restricted"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
11
+ "directory": "packages/client/ui-workflow-run"
12
+ },
13
+ "type": "module",
14
+ "main": "lib/index.js",
15
+ "types": "lib/types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./lib/types/index.d.ts",
19
+ "default": "./lib/index.js"
20
+ },
21
+ "./invariant": {
22
+ "types": "./lib/types/invariant.d.ts",
23
+ "default": "./lib/invariant.js"
24
+ },
25
+ "./client": {
26
+ "types": "./lib/types/client/index.d.ts",
27
+ "default": "./lib/client.js"
28
+ },
29
+ "./src/*": "./src/*",
30
+ "./package.json": "./package.json"
31
+ },
32
+ "dsh": {
33
+ "client": {
34
+ "inject": [
35
+ "@deepseek-ai/dsh-client-locale",
36
+ "@deepseek-ai/dsh-client-runtime",
37
+ "@deepseek-ai/dsh-client-ui-conversation"
38
+ ],
39
+ "platform": "web"
40
+ }
41
+ },
42
+ "files": [
43
+ "lib/index.js",
44
+ "lib/invariant.js",
45
+ "lib/client.js",
46
+ "lib/types/**/*.d.ts"
47
+ ],
48
+ "license": "BSD-3-Clause",
49
+ "dependencies": {
50
+ "react": "^18.2.0"
51
+ },
52
+ "peerDependencies": {
53
+ "@deepseek-ai/dsh-client-locale": "^0.0.1-rc.2",
54
+ "@deepseek-ai/dsh-client-runtime": "^0.0.1-rc.2",
55
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.0.1-rc.2",
56
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.0.1-rc.2",
57
+ "@deepseek-ai/dsh-client-ui-slots": "^0.0.1-rc.2",
58
+ "@deepseek-ai/dsh-invariants": "^0.0.1-rc.2",
59
+ "@deepseek-ai/dsh-session": "^0.0.1-rc.2",
60
+ "@deepseek-ai/dsh-tool-workflow": "^0.0.1-rc.2",
61
+ "@deepseek-ai/dsh-workflow": "^0.0.1-rc.2",
62
+ "@deepseek-ai/cordis": "^4.0.1-rc.1"
63
+ },
64
+ "devDependencies": {
65
+ "@types/react": "~18.3.1",
66
+ "@deepseek-ai/dsh-client-runtime": "^0.0.1-rc.2",
67
+ "@deepseek-ai/dsh-client-test-runtime": "^0.0.1-rc.2",
68
+ "@deepseek-ai/dsh-client-locale": "^0.0.1-rc.2",
69
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.0.1-rc.2",
70
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.0.1-rc.2",
71
+ "@deepseek-ai/dsh-client-ui-slots": "^0.0.1-rc.2",
72
+ "@deepseek-ai/dsh-invariants": "^0.0.1-rc.2",
73
+ "@deepseek-ai/dsh-session": "^0.0.1-rc.2",
74
+ "@deepseek-ai/dsh-tool-workflow": "^0.0.1-rc.2",
75
+ "@deepseek-ai/dsh-workflow": "^0.0.1-rc.2",
76
+ "@deepseek-ai/cordis": "^4.0.1-rc.1"
77
+ },
78
+ "scripts": {
79
+ "bundle": "tsdown",
80
+ "watch": "tsdown --watch"
81
+ }
82
+ }