@prettier-ai/dsh-client-ui-workflow-run 0.1.2-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeepSeek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -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: 1cf482bb8f9d0f8c41e82a639fa6cb656f5fd563
6
+ README.zh.md: 2b559c666b1ea00374717b9f401ea02113459ac6
package/README.md ADDED
@@ -0,0 +1,100 @@
1
+ ---
2
+ description: "Durable workflow-run Conversation Node for the dsh web client: reconstructs top-level workflow runs as independent chat nodes with nested member disclosure."
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @deepseek-ai/dsh-client-ui-workflow-run
7
+
8
+ English | [中文](README.zh.md)
9
+
10
+ ## Summary
11
+
12
+ `dsh-client-ui-workflow-run` is the browser plugin that reconstructs durable top-level workflow runs as independent Chat nodes in the dsh web client. It consumes the four `tool-workflow/*` Session events owned by `dsh-tool-workflow`, registers one `ConversationNodeDefinition`, and renders through the keyed `conversation.chat.node` slot without changing the existing workflow tool card. The run and each phase are controlled disclosures: a mount opens running, failed, cancelled, and interrupted levels and closes fully completed levels, and users can toggle either level with the full row, Enter, or Space. A member opens a child Session only while every current fact agrees, and the node shows run, phase, member identity, and status only.
13
+
14
+ ## Table of Contents
15
+
16
+ - [Use this package](#use-this-package)
17
+ - [Understand the implementation](#understand-the-implementation)
18
+ - [Further Exploration](#further-exploration)
19
+ - [Model Experience](#model-experience)
20
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
21
+ - [Dev Note](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## Use this package
27
+
28
+ A top-level workflow run through `dsh-tool-workflow` appears in the conversation as its own node: expand the run to see its phases, and expand a phase to see its members. Phase groups come only from members that started, and settlement changes status without removing or reordering members.
29
+
30
+ ### Navigating the node
31
+
32
+ The run uses a 32-pixel row with persistent chevrons, an inline state dot, and status text; phases use disclosure rows with title and member count in the main area and a fixed aggregate-status tail; members use a 16-pixel dot slot, a truncating name area, and a fixed status column. Opening a member's child Session requires the member to be running, the child id to be in the ordinary Session list, the row to have `origin: 'subagent'`, its `parentId` to be the current Session, and the list row to still be running — remote, addressed-only, wrong-parent, or terminal rows remain non-interactive.
33
+
34
+ ### State and completion
35
+
36
+ Completion updates the visible status immediately but delays its automatic close while focus remains inside the content. A closed Turn or Step with missing terminal events presents the affected run or members as interrupted without changing the tool result.
37
+
38
+ -----
39
+
40
+ <a id="understand-the-implementation"></a>
41
+ ## Understand the implementation
42
+
43
+ <details>
44
+ <summary>Implementation internals — click to expand</summary>
45
+
46
+ The node is a deterministic replay of durable session events: `tool-workflow/run-start` creates one Context keyed by `runId`, and 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.
47
+
48
+ ### Disclosure choices
49
+
50
+ Ordinary running updates preserve the current choice, the first abnormal edge opens once, normal completion closes once, and a completed phase plus the outer run open again when a new running member starts under the same phase key. If an entire new clean cycle arrives in one render while the run remains active, the phase finishes folded but the outer run opens once to expose its updated summary. `WorkflowRunPanel` owns the phase choices, so closing and reopening the outer run does not reset them; a renderer remount reconstructs every initial choice from durable facts.
51
+
52
+ ### Composition
53
+
54
+ 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`.
55
+
56
+ </details>
57
+
58
+ -----
59
+
60
+ <a id="further-exploration"></a>
61
+ ## Further Exploration
62
+
63
+ These pages cover the tool seam, the conversation host, and the tool presentation layer.
64
+
65
+ - [tool-workflow](../../workflow/tool-workflow/README.md) — the tool that owns the four `tool-workflow/*` Session events.
66
+ - [ui-conversation](../ui-conversation/README.md) — the chat surface hosting the `conversation.chat.node` slot.
67
+ - [ui-tool](../ui-tool/README.md) — the tool-call presentation layer this node sits beside.
68
+ - [Conversation subsystem](../../../docs/subsystems/conversation.md) — how a business-owned feature registers a Conversation node.
69
+
70
+ -----
71
+
72
+ <a id="model-experience"></a>
73
+ ## Model Experience
74
+
75
+ None, as the package is a browser-side UI plugin layer that renders durable workflow records without changing model context.
76
+
77
+ #### KV Cache effect
78
+
79
+ None; this package neither assembles nor sends a provider request.
80
+
81
+ ## Known Limitations and Deferred Work
82
+
83
+ <a id="known-limitations-and-deferred-work"></a>
84
+
85
+
86
+ These limits define which runs produce records and what the node exposes; they are current package constraints.
87
+
88
+ - **Only top-level calls through `dsh-tool-workflow` produce these records** — nested PTC mode calls and direct `WorkflowEngine` consumers do not.
89
+ - **Navigation is intentionally live-only** — terminal members remain visible for review but never expose a cold-session opener from this node.
90
+ - **The node shows run, phase, member identity, and status only** — scripts, outputs, errors, logs, usage, static topology, and controls remain outside this surface.
91
+
92
+ <a id="dev-note"></a>
93
+ ### Dev Note
94
+
95
+ <details>
96
+ <summary>Working context for maintainers — click to expand</summary>
97
+
98
+ None.
99
+
100
+ </details>
package/README.zh.md ADDED
@@ -0,0 +1,100 @@
1
+ ---
2
+ description: "dsh Web 客户端的持久化工作流运行 Conversation Node:把顶层工作流运行重建为带嵌套成员折叠的独立聊天节点。"
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @deepseek-ai/dsh-client-ui-workflow-run
7
+
8
+ [English](README.md) | 中文
9
+
10
+ ## 概述
11
+
12
+ `dsh-client-ui-workflow-run` 是浏览器插件,把持久化的顶层工作流运行重建为 dsh Web 客户端中的独立 Chat 节点。它消费由 `dsh-tool-workflow` 拥有的四类 `tool-workflow/*` Session 事件,注册一个 `ConversationNodeDefinition`,并通过 keyed `conversation.chat.node` slot 渲染,不改变现有工作流工具卡。运行与每个阶段都是受控 disclosure:挂载时运行中、失败、已取消与已中断层级默认展开,全部完成的层级默认折叠,用户可以点击整行或按 Enter、Space 切换任一层级。只有当所有实时事实同时成立时,成员才可打开子 Session;节点只显示运行、阶段、成员身份与状态。
13
+
14
+ ## 目录
15
+
16
+ - [使用本包](#use-this-package)
17
+ - [理解实现](#understand-the-implementation)
18
+ - [进一步探索](#further-exploration)
19
+ - [模型体验](#model-experience)
20
+ - [已知限制与延期工作](#known-limitations-and-deferred-work)
21
+ - [开发备注](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## 使用本包
27
+
28
+ 经 `dsh-tool-workflow` 发起的顶层工作流运行会在对话中显示为独立节点:展开运行查看其阶段,展开阶段查看其成员。阶段组只来自开始过的成员;成员结算只改变状态,不删除或重排成员。
29
+
30
+ ### 导航节点
31
+
32
+ 运行使用 32 像素行,带常驻 chevron、行内状态点与状态文字;阶段使用 disclosure 行,在主区显示标题与成员数、在固定尾部显示聚合状态;成员使用 16 像素状态点槽、可省略名称区与固定状态列。打开成员的子 Session 需要成员仍在运行、子 id 位于普通 Session 列表、列表行为 `origin: 'subagent'`、`parentId` 等于当前 Session,且列表行仍标记运行——远程、仅地址化、父级不符或终态的行都不可交互。
33
+
34
+ ### 状态与完成
35
+
36
+ 完成状态会立即更新,但只要焦点仍位于展开内容内,自动折叠就会等待焦点离开。若所属 Turn 或 Step 已关闭但终点事件缺失,界面把相应运行或成员显示为已中断,而不改写工具结果。
37
+
38
+ -----
39
+
40
+ <a id="understand-the-implementation"></a>
41
+ ## 理解实现
42
+
43
+ <details>
44
+ <summary>实现细节——点击展开</summary>
45
+
46
+ 节点是持久化会话事件的确定性回放:`tool-workflow/run-start` 以 `runId` 创建唯一 Context,成员开始、成员结束与运行结束事件按日志顺序更新该 Context。只有 update 的历史尾页会保持 pending,直到更早页面补入唯一 start;此后 prepend、完整回放与实时 append 得到相同状态。
47
+
48
+ ### 展开选择
49
+
50
+ 普通运行更新保留当前选择,首次异常边沿只自动展开一次,正常完成只自动折叠一次;已完成阶段在同一 phase key 下开始新的运行成员时,该 Phase 与外层运行会再次自动展开。若一个完整的新干净周期在同一次渲染中送达且运行仍处于活动状态,Phase 保持折叠,但外层运行会自动展开一次以展示更新后的摘要。Phase 选择由 `WorkflowRunPanel` 持有,因此关闭并重新打开外层运行不会重置它们;renderer remount 会从持久事实重建每层的初始选择。
51
+
52
+ ### 装配
53
+
54
+ 本包把 Definition、locale 字典与 `workflow-run` renderer 都注册为 Cordis effect;移除客户端 entry 会撤销三者。shipped Web bundle 在 `ui-conversation` 与 `ui-tool` 之后装配该插件。
55
+
56
+ </details>
57
+
58
+ -----
59
+
60
+ <a id="further-exploration"></a>
61
+ ## 进一步探索
62
+
63
+ 以下页面覆盖工具 seam、对话宿主与工具展示层。
64
+
65
+ - [tool-workflow](../../workflow/tool-workflow/README.zh.md)——拥有四类 `tool-workflow/*` Session 事件的工具。
66
+ - [ui-conversation](../ui-conversation/README.zh.md)——承载 `conversation.chat.node` slot 的聊天界面。
67
+ - [ui-tool](../ui-tool/README.zh.md)——本节点相邻的工具调用展示层。
68
+ - [Conversation 子系统](../../../docs/subsystems/conversation.zh.md)——业务自有功能如何注册 Conversation node。
69
+
70
+ -----
71
+
72
+ <a id="model-experience"></a>
73
+ ## 模型体验
74
+
75
+ 无。该包是浏览器端 UI 插件层,只渲染持久化工作流记录,不改变模型上下文。
76
+
77
+ #### KV Cache 影响
78
+
79
+ 无;该包既不组装也不发送提供方请求。
80
+
81
+ ## 已知限制与延期工作
82
+
83
+ <a id="known-limitations-and-deferred-work"></a>
84
+
85
+
86
+ 这些限制定义哪些运行会产生记录、节点暴露什么;它们是当前包约束。
87
+
88
+ - **只有经 `dsh-tool-workflow` 发起的顶层调用会生成这些记录**:嵌套 PTC mode 调用和直接 `WorkflowEngine` 消费方不会生成。
89
+ - **导航刻意只面向实时运行**:终态成员继续保留供复盘,但本节点永不为其提供冷 Session 入口。
90
+ - **节点只显示运行、阶段、成员身份与状态**:脚本、输出、错误、日志、用量、静态拓扑与控制操作都不属于本界面。
91
+
92
+ <a id="dev-note"></a>
93
+ ### 开发备注
94
+
95
+ <details>
96
+ <summary>维护者的工作上下文——点击展开</summary>
97
+
98
+ 无。
99
+
100
+ </details>
package/lib/client.js ADDED
@@ -0,0 +1,649 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@prettier-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 _prettier_ai_dsh_client_ui_primitives = require("@prettier-ai/dsh-client-ui-primitives");
10
+ let _prettier_ai_dsh_client_store = require("@prettier-ai/dsh-client-store");
11
+ //#region \0dsh-css:/home/runner/work/dsh-publisher/dsh-publisher/upstream/packages/client/ui-workflow-run/src/client/WorkflowRunPanel.module.css.mjs
12
+ const css = ".KFKxAq_root{width:100%;min-width:0}.KFKxAq_runHeader{box-sizing:border-box;background:var(--dsw-alias-bg-module-platform);border-radius:8px;align-items:center;gap:6px;width:100%;min-width:0;height:32px;padding:0 8px;display:flex}.KFKxAq_runHeader:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}.KFKxAq_runLeading{width:calc(16px + var(--dsh-content-font-delta,0px));height:calc(16px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;margin-right:0;display:inline-flex}.KFKxAq_runTitle{max-width:42%;color:var(--dsw-alias-label-secondary);font-size:var(--dsh-content-font-size-secondary,13px);font-weight:510;line-height:calc(24px + var(--dsh-content-font-delta,0px));text-overflow:ellipsis;white-space:nowrap;flex:none;overflow:hidden}.KFKxAq_runSummary{min-width:0;color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(18px + var(--dsh-content-font-delta-secondary,0px));text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.KFKxAq_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}.KFKxAq_phaseHeader{box-sizing:border-box;align-items:center;gap:6px;width:100%;min-width:0;height:32px;display:flex}.KFKxAq_phaseHeader:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px;border-radius:4px}.KFKxAq_phaseLeading{width:calc(16px + var(--dsh-content-font-delta,0px));height:calc(16px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;margin-right:0;display:inline-flex}.KFKxAq_phaseTitle{min-width:0;max-width:42%;color:var(--dsw-alias-label-secondary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));text-overflow:ellipsis;white-space:nowrap;flex:0 auto;overflow:hidden}.KFKxAq_phaseCount{min-width:0;color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(20px + var(--dsh-content-font-delta-secondary,0px));text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.KFKxAq_phaseStatus{width:calc(132px + var(--dsh-content-font-delta-secondary,0px) * 10);color:var(--dsw-alias-label-secondary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(20px + var(--dsh-content-font-delta-secondary,0px));text-align:right;text-overflow:ellipsis;white-space:nowrap;flex:none;overflow:hidden}.KFKxAq_separator{background:var(--dsw-alias-label-tertiary);border-radius:50%;flex:none;width:2px;height:2px}.KFKxAq_phaseList{flex-direction:column;gap:4px;min-width:0;padding:4px 0 0 16px;display:flex}.KFKxAq_phase{min-width:0}.KFKxAq_members{flex-direction:column;gap:2px;min-width:0;padding:0 0 0 16px;display:flex}.KFKxAq_memberRow,.KFKxAq_memberButton{width:100%;min-width:0;min-height:calc(24px + var(--dsh-content-font-delta,0px));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}.KFKxAq_memberButton{cursor:pointer}.KFKxAq_memberButton .KFKxAq_memberLabel{color:var(--dsw-alias-state-business-primary);text-underline-position:from-font;text-decoration:underline}.KFKxAq_dotSlot{width:16px;height:calc(24px + var(--dsh-content-font-delta,0px));flex:none;justify-content:center;align-items:center;display:inline-flex;overflow:hidden}.KFKxAq_memberButton:focus-visible{outline:none}.KFKxAq_memberButton:focus-visible .KFKxAq_memberLabelWrap{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-1px}.KFKxAq_memberLabelWrap{min-width:0;height:calc(24px + var(--dsh-content-font-delta,0px));border-radius:4px;flex:1;align-items:center;padding:0 2px;display:flex;overflow:hidden}.KFKxAq_memberLabel{min-width:0;color:var(--dsw-alias-label-secondary);font-size:var(--dsh-content-font-size,14px);line-height:calc(24px + var(--dsh-content-font-delta,0px));text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.KFKxAq_memberStatus{width:64px;color:var(--dsw-alias-label-secondary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(20px + var(--dsh-content-font-delta-secondary,0px));text-align:right;text-overflow:ellipsis;white-space:nowrap;flex:none;overflow:hidden}.KFKxAq_empty{color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(20px + var(--dsh-content-font-delta-secondary,0px));padding:0}@media (width<=560px){.KFKxAq_phaseList,.KFKxAq_members{padding-left:12px}}";
13
+ const tagId = "@prettier-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 = "@prettier-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
+ "dotSlot": "KFKxAq_dotSlot",
23
+ "empty": "KFKxAq_empty",
24
+ "memberButton": "KFKxAq_memberButton",
25
+ "memberLabel": "KFKxAq_memberLabel",
26
+ "memberLabelWrap": "KFKxAq_memberLabelWrap",
27
+ "memberRow": "KFKxAq_memberRow",
28
+ "memberStatus": "KFKxAq_memberStatus",
29
+ "members": "KFKxAq_members",
30
+ "phase": "KFKxAq_phase",
31
+ "phaseCount": "KFKxAq_phaseCount",
32
+ "phaseHeader": "KFKxAq_phaseHeader",
33
+ "phaseLeading": "KFKxAq_phaseLeading",
34
+ "phaseList": "KFKxAq_phaseList",
35
+ "phaseStatus": "KFKxAq_phaseStatus",
36
+ "phaseTitle": "KFKxAq_phaseTitle",
37
+ "root": "KFKxAq_root",
38
+ "runHeader": "KFKxAq_runHeader",
39
+ "runLeading": "KFKxAq_runLeading",
40
+ "runSummary": "KFKxAq_runSummary",
41
+ "runTitle": "KFKxAq_runTitle",
42
+ "separator": "KFKxAq_separator",
43
+ "statusTail": "KFKxAq_statusTail"
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 StatusDisclosure(props) {
79
+ return (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.DisclosureRow, {
80
+ ...props,
81
+ expandable: true
82
+ });
83
+ }
84
+ function abnormal(status) {
85
+ return status === "failed" || status === "cancelled" || status === "interrupted";
86
+ }
87
+ function phaseDisclosureFacts(phase) {
88
+ return {
89
+ mode: phase.members.some((member) => abnormal(member.status)) ? "abnormal" : phase.members.some((member) => member.status === "running") ? "running" : "clean",
90
+ activityCount: phase.members.length
91
+ };
92
+ }
93
+ function runDisclosureFacts(status, phases) {
94
+ return {
95
+ mode: abnormal(status) || phases.some(([, facts]) => facts.mode === "abnormal") ? "abnormal" : status === "running" || phases.some(([, facts]) => facts.mode === "running") ? "running" : "clean",
96
+ activityCount: phases.reduce((count, [, facts]) => count + facts.activityCount, 0)
97
+ };
98
+ }
99
+ function initialDisclosureState(facts) {
100
+ return {
101
+ ...facts,
102
+ open: facts.mode !== "clean",
103
+ pendingCleanCollapse: false
104
+ };
105
+ }
106
+ function advanceDisclosureState(current, facts, focusWithin) {
107
+ if (current.mode === facts.mode && current.activityCount === facts.activityCount) {
108
+ if (!current.pendingCleanCollapse || focusWithin) return current;
109
+ return {
110
+ ...current,
111
+ open: false,
112
+ pendingCleanCollapse: false
113
+ };
114
+ }
115
+ if (facts.mode === "clean") {
116
+ const deferCollapse = current.open && focusWithin;
117
+ return {
118
+ ...facts,
119
+ open: deferCollapse,
120
+ pendingCleanCollapse: deferCollapse
121
+ };
122
+ }
123
+ if (current.mode === "clean" || facts.mode === "abnormal" && current.mode !== "abnormal") return {
124
+ ...facts,
125
+ open: true,
126
+ pendingCleanCollapse: false
127
+ };
128
+ return {
129
+ ...facts,
130
+ open: current.open,
131
+ pendingCleanCollapse: false
132
+ };
133
+ }
134
+ function focusIsWithin(element) {
135
+ if (element === null || element === void 0) return false;
136
+ return element.contains(element.ownerDocument.activeElement);
137
+ }
138
+ function collapsePending(state) {
139
+ if (!state.pendingCleanCollapse) return state;
140
+ return {
141
+ ...state,
142
+ open: false,
143
+ pendingCleanCollapse: false
144
+ };
145
+ }
146
+ function existingPhaseState(phases, key) {
147
+ const phase = phases.get(key);
148
+ /* v8 ignore next -- mounted phase callbacks are created from this owner map. */
149
+ if (phase === void 0) throw new Error(`Missing disclosure state for phase ${key}`);
150
+ return phase;
151
+ }
152
+ function preventPendingHeaderFocus(event) {
153
+ const header = event.currentTarget.querySelector("[data-disclosure-row]");
154
+ /* v8 ignore next -- DisclosureRow always renders its header before the content. */
155
+ if (header === null) throw new Error("Missing disclosure header");
156
+ if (header.contains(event.target)) event.preventDefault();
157
+ }
158
+ function phaseStatusSummary(members, t) {
159
+ const counts = /* @__PURE__ */ new Map();
160
+ for (const member of members) counts.set(member.status, (counts.get(member.status) ?? 0) + 1);
161
+ const count = (status) => counts.get(status) ?? 0;
162
+ const active = [
163
+ "running",
164
+ "failed",
165
+ "cancelled",
166
+ "interrupted"
167
+ ].filter((status) => count(status) > 0);
168
+ if (active.length === 0) return statusCount("completed", count("completed"), t);
169
+ return (active.includes("interrupted") && count("completed") > 0 ? ["completed", ...active] : active).map((status) => statusCount(status, count(status), t)).join(" · ");
170
+ }
171
+ function navigableMembers(sessions, phases, parentId) {
172
+ const ordinary = new Set(sessions.ids);
173
+ const result = [];
174
+ for (const phase of phases) for (const member of phase.members) {
175
+ const summary = sessions.byId[member.childId];
176
+ if (member.status === "running" && ordinary.has(member.childId) && summary?.origin === "subagent" && summary.parentId === parentId && summary.running) result.push(member.childId);
177
+ }
178
+ return result;
179
+ }
180
+ function RunHeader({ children, count, name, onToggle, open, status, t }) {
181
+ return (0, react_jsx_runtime.jsx)(StatusDisclosure, {
182
+ icon: (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.IconChevronRightOutline14, {}),
183
+ title: t("run.title", { name }),
184
+ open,
185
+ onToggle,
186
+ expandOnRowClick: true,
187
+ previewChevron: false,
188
+ keepContentWhenOpen: true,
189
+ rowClassName: WorkflowRunPanel_module_css_default.runHeader,
190
+ leadingClassName: WorkflowRunPanel_module_css_default.runLeading,
191
+ titleClassName: WorkflowRunPanel_module_css_default.runTitle,
192
+ collapsedContent: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
193
+ (0, react_jsx_runtime.jsx)("span", {
194
+ className: WorkflowRunPanel_module_css_default.separator,
195
+ "aria-hidden": true
196
+ }),
197
+ (0, react_jsx_runtime.jsx)("span", {
198
+ className: WorkflowRunPanel_module_css_default.runSummary,
199
+ children: memberCount(count, t)
200
+ }),
201
+ (0, react_jsx_runtime.jsxs)("span", {
202
+ className: WorkflowRunPanel_module_css_default.statusTail,
203
+ "data-status": status,
204
+ children: [(0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.StateDot, { state: dotState(status) }), (0, react_jsx_runtime.jsx)("span", { children: t(STATUS_KEYS[status]) })]
205
+ })
206
+ ] }),
207
+ children
208
+ });
209
+ }
210
+ function MemberRow({ member, navigable, openSession, t }) {
211
+ const name = readableMember(member.label, t);
212
+ const [focused, setFocused] = (0, react.useState)(false);
213
+ const renderButton = navigable || focused;
214
+ const content = (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
215
+ (0, react_jsx_runtime.jsx)("span", {
216
+ className: WorkflowRunPanel_module_css_default.dotSlot,
217
+ children: (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.StateDot, { state: dotState(member.status) })
218
+ }),
219
+ (0, react_jsx_runtime.jsx)("span", {
220
+ className: WorkflowRunPanel_module_css_default.memberLabelWrap,
221
+ "data-member-label-wrap": true,
222
+ children: (0, react_jsx_runtime.jsx)("span", {
223
+ className: WorkflowRunPanel_module_css_default.memberLabel,
224
+ "data-member-label": true,
225
+ children: name
226
+ })
227
+ }),
228
+ (0, react_jsx_runtime.jsx)("span", {
229
+ className: WorkflowRunPanel_module_css_default.memberStatus,
230
+ "data-member-status-text": true,
231
+ children: t(STATUS_KEYS[member.status])
232
+ })
233
+ ] });
234
+ if (!renderButton) return (0, react_jsx_runtime.jsx)("div", {
235
+ className: WorkflowRunPanel_module_css_default.memberRow,
236
+ "data-member-status": member.status,
237
+ children: content
238
+ });
239
+ return (0, react_jsx_runtime.jsx)("button", {
240
+ type: "button",
241
+ className: navigable ? WorkflowRunPanel_module_css_default.memberButton : WorkflowRunPanel_module_css_default.memberRow,
242
+ "data-member-status": member.status,
243
+ "aria-disabled": navigable ? void 0 : true,
244
+ "aria-label": navigable ? t("member.open", { name }) : name,
245
+ tabIndex: navigable ? void 0 : -1,
246
+ onFocus: () => {
247
+ setFocused(true);
248
+ },
249
+ onBlur: () => {
250
+ setFocused(false);
251
+ },
252
+ onClick: navigable ? () => {
253
+ openSession(member.childId);
254
+ } : void 0,
255
+ children: content
256
+ });
257
+ }
258
+ function PhaseSection({ contentRef, onContentBlur, onToggle, open, pendingCleanCollapse, phase, navigable, openSession, t }) {
259
+ return (0, react_jsx_runtime.jsx)("div", {
260
+ className: WorkflowRunPanel_module_css_default.phase,
261
+ onMouseDownCapture: pendingCleanCollapse ? preventPendingHeaderFocus : void 0,
262
+ children: (0, react_jsx_runtime.jsx)(StatusDisclosure, {
263
+ icon: (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.IconChevronRightOutline14, {}),
264
+ title: readablePhase(phase.phase, t),
265
+ open,
266
+ onToggle,
267
+ expandOnRowClick: true,
268
+ previewChevron: false,
269
+ keepContentWhenOpen: true,
270
+ rowClassName: WorkflowRunPanel_module_css_default.phaseHeader,
271
+ leadingClassName: WorkflowRunPanel_module_css_default.phaseLeading,
272
+ titleClassName: WorkflowRunPanel_module_css_default.phaseTitle,
273
+ collapsedContent: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
274
+ (0, react_jsx_runtime.jsx)("span", {
275
+ className: WorkflowRunPanel_module_css_default.separator,
276
+ "aria-hidden": true
277
+ }),
278
+ (0, react_jsx_runtime.jsx)("span", {
279
+ className: WorkflowRunPanel_module_css_default.phaseCount,
280
+ "data-phase-count": true,
281
+ children: memberCount(phase.members.length, t)
282
+ }),
283
+ (0, react_jsx_runtime.jsx)("span", {
284
+ className: WorkflowRunPanel_module_css_default.phaseStatus,
285
+ "data-phase-status-text": true,
286
+ children: phaseStatusSummary(phase.members, t)
287
+ })
288
+ ] }),
289
+ children: (0, react_jsx_runtime.jsx)("div", {
290
+ ref: contentRef,
291
+ className: WorkflowRunPanel_module_css_default.members,
292
+ onBlur: onContentBlur,
293
+ children: phase.members.map((member) => (0, react_jsx_runtime.jsx)(MemberRow, {
294
+ member,
295
+ navigable: navigable.includes(member.childId),
296
+ openSession,
297
+ t
298
+ }, member.seq))
299
+ })
300
+ })
301
+ });
302
+ }
303
+ /** Render one durable workflow run with status-driven run and phase disclosure. */
304
+ function WorkflowRunPanel({ node, sessionId, useSessions, openSession, t }) {
305
+ const phaseFacts = (0, react.useMemo)(() => node.data.phases.map((phase) => [phase.key, phaseDisclosureFacts(phase)]), [node.data.phases]);
306
+ const runFacts = (0, react.useMemo)(() => runDisclosureFacts(node.data.status, phaseFacts), [node.data.status, phaseFacts]);
307
+ const totalMembers = runFacts.activityCount;
308
+ const [disclosures, setDisclosures] = (0, react.useState)(() => ({
309
+ run: initialDisclosureState(runFacts),
310
+ phases: new Map(phaseFacts.map(([key, facts]) => [key, initialDisclosureState(facts)]))
311
+ }));
312
+ const runContentRef = (0, react.useRef)(null);
313
+ const phaseContentRefs = (0, react.useRef)(/* @__PURE__ */ new Map());
314
+ const navigable = useSessions((sessions) => navigableMembers(sessions, node.data.phases, sessionId), _prettier_ai_dsh_client_store.shallowEqual);
315
+ (0, react.useLayoutEffect)(() => {
316
+ setDisclosures((current) => {
317
+ const phases = /* @__PURE__ */ new Map();
318
+ let phasesChanged = current.phases.size !== phaseFacts.length;
319
+ let phaseStartedCycle = false;
320
+ for (const [key, facts] of phaseFacts) {
321
+ const previous = current.phases.get(key);
322
+ const next = previous === void 0 ? initialDisclosureState(facts) : advanceDisclosureState(previous, facts, focusIsWithin(phaseContentRefs.current.get(key)));
323
+ phases.set(key, next);
324
+ if (next !== previous) phasesChanged = true;
325
+ if (previous?.mode === "clean" && (facts.mode !== "clean" || facts.activityCount !== previous.activityCount)) phaseStartedCycle = true;
326
+ }
327
+ const advancedRun = advanceDisclosureState(current.run, runFacts, focusIsWithin(runContentRef.current));
328
+ const run = phaseStartedCycle && runFacts.mode !== "clean" && !advancedRun.open ? {
329
+ ...advancedRun,
330
+ open: true,
331
+ pendingCleanCollapse: false
332
+ } : advancedRun;
333
+ return run !== current.run || phasesChanged ? {
334
+ run,
335
+ phases
336
+ } : current;
337
+ });
338
+ }, [
339
+ disclosures.run.open,
340
+ phaseFacts,
341
+ runFacts
342
+ ]);
343
+ const toggleRun = () => {
344
+ setDisclosures((current) => ({
345
+ ...current,
346
+ run: {
347
+ ...current.run,
348
+ open: !current.run.open,
349
+ pendingCleanCollapse: false
350
+ }
351
+ }));
352
+ };
353
+ const togglePhase = (key) => {
354
+ setDisclosures((current) => {
355
+ const phases = new Map(current.phases);
356
+ const phase = existingPhaseState(phases, key);
357
+ phases.set(key, {
358
+ ...phase,
359
+ open: !phase.open,
360
+ pendingCleanCollapse: false
361
+ });
362
+ return {
363
+ ...current,
364
+ phases
365
+ };
366
+ });
367
+ };
368
+ const settleRunBlur = (event) => {
369
+ if (event.currentTarget.contains(event.relatedTarget)) return;
370
+ setDisclosures((current) => {
371
+ const run = collapsePending(current.run);
372
+ return run === current.run ? current : {
373
+ ...current,
374
+ run
375
+ };
376
+ });
377
+ };
378
+ const settlePhaseBlur = (key, event) => {
379
+ if (event.currentTarget.contains(event.relatedTarget)) return;
380
+ setDisclosures((current) => {
381
+ const phase = existingPhaseState(current.phases, key);
382
+ const next = collapsePending(phase);
383
+ if (next === phase) return current;
384
+ const phases = new Map(current.phases);
385
+ phases.set(key, next);
386
+ return {
387
+ ...current,
388
+ phases
389
+ };
390
+ });
391
+ };
392
+ return (0, react_jsx_runtime.jsx)("section", {
393
+ className: WorkflowRunPanel_module_css_default.root,
394
+ "data-workflow-run": true,
395
+ "data-run-status": node.data.status,
396
+ onMouseDownCapture: disclosures.run.pendingCleanCollapse ? preventPendingHeaderFocus : void 0,
397
+ children: (0, react_jsx_runtime.jsx)(RunHeader, {
398
+ count: totalMembers,
399
+ name: node.data.name,
400
+ open: disclosures.run.open,
401
+ onToggle: toggleRun,
402
+ status: node.data.status,
403
+ t,
404
+ children: (0, react_jsx_runtime.jsx)("div", {
405
+ ref: runContentRef,
406
+ className: WorkflowRunPanel_module_css_default.phaseList,
407
+ onBlur: settleRunBlur,
408
+ children: node.data.phases.length === 0 ? (0, react_jsx_runtime.jsx)("span", {
409
+ className: WorkflowRunPanel_module_css_default.empty,
410
+ children: t("run.empty")
411
+ }) : node.data.phases.map((phase) => {
412
+ const facts = phaseDisclosureFacts(phase);
413
+ const disclosure = disclosures.phases.get(phase.key) ?? initialDisclosureState(facts);
414
+ return (0, react_jsx_runtime.jsx)(PhaseSection, {
415
+ contentRef: (element) => {
416
+ if (element === null) phaseContentRefs.current.delete(phase.key);
417
+ else phaseContentRefs.current.set(phase.key, element);
418
+ },
419
+ onContentBlur: (event) => {
420
+ settlePhaseBlur(phase.key, event);
421
+ },
422
+ onToggle: () => {
423
+ togglePhase(phase.key);
424
+ },
425
+ open: disclosure.open,
426
+ pendingCleanCollapse: disclosure.pendingCleanCollapse,
427
+ phase,
428
+ navigable,
429
+ openSession,
430
+ t
431
+ }, phase.key);
432
+ })
433
+ })
434
+ })
435
+ });
436
+ }
437
+ //#endregion
438
+ //#region lib/types/client/locales.js
439
+ /** `workflowRun` namespace dictionaries. */
440
+ /** Dictionary namespace owned by this plugin. */
441
+ const NS = "workflowRun";
442
+ /** Simplified Chinese dictionary (the key-set source of truth). */
443
+ const zh = {
444
+ "run.title": "{name}",
445
+ "run.members.one": "{count} 个成员",
446
+ "run.members.other": "{count} 个成员",
447
+ "run.empty": "没有启动成员",
448
+ "phase.unassigned": "未分阶段",
449
+ "phase.empty": "空阶段名",
450
+ "statusCount.running": "运行中 {count}",
451
+ "statusCount.completed": "已完成 {count}",
452
+ "statusCount.failed": "失败 {count}",
453
+ "statusCount.cancelled": "已取消 {count}",
454
+ "statusCount.interrupted": "已中断 {count}",
455
+ "member.empty": "空成员名",
456
+ "member.open": "打开 {name}",
457
+ "status.running": "运行中",
458
+ "status.completed": "已完成",
459
+ "status.failed": "失败",
460
+ "status.cancelled": "已取消",
461
+ "status.interrupted": "已中断"
462
+ };
463
+ /** English dictionary (same key set). */
464
+ const en = {
465
+ "run.title": "{name}",
466
+ "run.members.one": "{count} member",
467
+ "run.members.other": "{count} members",
468
+ "run.empty": "No members started",
469
+ "phase.unassigned": "Unphased",
470
+ "phase.empty": "Empty phase name",
471
+ "statusCount.running": "Running {count}",
472
+ "statusCount.completed": "Completed {count}",
473
+ "statusCount.failed": "Failed {count}",
474
+ "statusCount.cancelled": "Cancelled {count}",
475
+ "statusCount.interrupted": "Interrupted {count}",
476
+ "member.empty": "Empty member name",
477
+ "member.open": "Open {name}",
478
+ "status.running": "Running",
479
+ "status.completed": "Completed",
480
+ "status.failed": "Failed",
481
+ "status.cancelled": "Cancelled",
482
+ "status.interrupted": "Interrupted"
483
+ };
484
+ //#endregion
485
+ //#region lib/types/client/workflow-definition.js
486
+ /**
487
+ * Build a collision-free phase key preserving absent versus empty identity.
488
+ * @param phase - exact phase string, or null for an omitted field.
489
+ * @returns the stable renderer key for that phase identity.
490
+ */
491
+ function workflowPhaseKey(phase) {
492
+ return phase === null ? "missing" : `value:${phase.length}:${phase}`;
493
+ }
494
+ function statusFromStopReason(stopReason) {
495
+ switch (stopReason) {
496
+ case "completed": return "completed";
497
+ case "cancelled": return "cancelled";
498
+ case "error": return "failed";
499
+ /* v8 ignore next -- WorkflowStopReason is closed and every variant is handled above. */
500
+ default: return stopReason;
501
+ }
502
+ }
503
+ function statusFromOutcome(outcome) {
504
+ switch (outcome) {
505
+ case "completed": return "completed";
506
+ case "cancelled": return "cancelled";
507
+ case "failed": return "failed";
508
+ /* v8 ignore next -- WorkflowAgentOutcome is closed and every variant is handled above. */
509
+ default: return outcome;
510
+ }
511
+ }
512
+ function locationClosed(location) {
513
+ if (location.kind === "step") return location.step.status === "closed" || location.turn.status === "closed";
514
+ return location.kind === "turn" && location.turn.status === "closed";
515
+ }
516
+ function projectWorkflow(context, location) {
517
+ const state = context.state;
518
+ const interrupted = state.stopReason === void 0 && locationClosed(location);
519
+ const phases = /* @__PURE__ */ new Map();
520
+ for (const member of state.members) {
521
+ const phase = member.phase === void 0 ? null : member.phase;
522
+ const key = workflowPhaseKey(phase);
523
+ let group = phases.get(key);
524
+ if (group === void 0) {
525
+ group = {
526
+ phase,
527
+ members: []
528
+ };
529
+ phases.set(key, group);
530
+ }
531
+ group.members.push({
532
+ seq: member.seq,
533
+ label: member.label,
534
+ childId: member.childId,
535
+ status: member.outcome === void 0 ? interrupted ? "interrupted" : "running" : statusFromOutcome(member.outcome)
536
+ });
537
+ }
538
+ const projectedPhases = [...phases].map(([key, phase]) => ({
539
+ key,
540
+ phase: phase.phase,
541
+ members: phase.members
542
+ }));
543
+ return {
544
+ name: state.name,
545
+ status: state.stopReason === void 0 ? interrupted ? "interrupted" : "running" : statusFromStopReason(state.stopReason),
546
+ phases: projectedPhases
547
+ };
548
+ }
549
+ function updateAgentStart(state, data) {
550
+ const member = {
551
+ seq: data.seq,
552
+ label: data.label,
553
+ ...data.phase === void 0 ? {} : { phase: data.phase },
554
+ childId: data.childId
555
+ };
556
+ return {
557
+ ...state,
558
+ members: [...state.members, member]
559
+ };
560
+ }
561
+ function updateAgentEnd(state, data) {
562
+ return {
563
+ ...state,
564
+ members: state.members.map((member) => member.seq === data.seq ? {
565
+ ...member,
566
+ outcome: data.outcome
567
+ } : member)
568
+ };
569
+ }
570
+ /** Durable workflow event family folded into one keyed Chat node. */
571
+ const workflowRunDefinition = {
572
+ kind: "workflow-run",
573
+ target: "chat",
574
+ match: (event) => {
575
+ if (event.type === "tool-workflow/run-start") return {
576
+ id: String(event.data.runId),
577
+ role: "start"
578
+ };
579
+ if (event.type === "tool-workflow/agent-start" || event.type === "tool-workflow/agent-end" || event.type === "tool-workflow/run-end") return {
580
+ id: String(event.data.runId),
581
+ role: "update"
582
+ };
583
+ return null;
584
+ },
585
+ start: (_context, match) => {
586
+ if (match.event.type !== "tool-workflow/run-start") throw new Error("workflow-run start requires tool-workflow/run-start");
587
+ return {
588
+ name: match.event.data.name,
589
+ members: []
590
+ };
591
+ },
592
+ update: (context, match) => {
593
+ if (match.event.type === "tool-workflow/agent-start") return updateAgentStart(context.state, match.event.data);
594
+ if (match.event.type === "tool-workflow/agent-end") return updateAgentEnd(context.state, match.event.data);
595
+ if (match.event.type === "tool-workflow/run-end") return {
596
+ ...context.state,
597
+ stopReason: match.event.data.stopReason
598
+ };
599
+ return context.state;
600
+ },
601
+ buildViewNode: (context) => {
602
+ if (context.start === void 0) return null;
603
+ const data = projectWorkflow(context, context.start.location);
604
+ return {
605
+ key: context.key,
606
+ kind: "workflow-run",
607
+ id: context.id,
608
+ target: "chat",
609
+ anchorSeq: context.start.event.seq,
610
+ location: context.start.location,
611
+ visibility: "visible",
612
+ data
613
+ };
614
+ }
615
+ };
616
+ //#endregion
617
+ //#region lib/types/client/index.js
618
+ /** Browser plugin for durable workflow-run Conversation Nodes. */
619
+ /** Required services for Definition, keyed renderer, navigation, and copy. */
620
+ const inject = [
621
+ "uiConversation",
622
+ "slots",
623
+ "sessions",
624
+ "locale"
625
+ ];
626
+ /** Register the workflow Definition, dictionary, and keyed Chat renderer. */
627
+ function apply(ctx) {
628
+ ctx.uiConversation.events.register(workflowRunDefinition);
629
+ ctx.effect(() => ctx.locale.register(NS, {
630
+ zh,
631
+ en
632
+ }), "ui-workflow-run: dictionaries");
633
+ ctx.slots.inject("conversation.chat.node", () => ctx.slots.register({
634
+ name: "conversation.chat.node",
635
+ key: "workflow-run",
636
+ locale: NS,
637
+ inject: () => ({ openSession: (id) => {
638
+ ctx.sessions.open(id);
639
+ } })
640
+ }, WorkflowRunPanel));
641
+ }
642
+ //#endregion
643
+ exports.apply = apply;
644
+ exports.inject = inject;
645
+ return module.exports;
646
+ }
647
+ });
648
+
649
+ //# 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 = "@prettier-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 '@prettier-ai/dsh-client-ui-slots';
2
+ import type { SessionId } from '@prettier-ai/dsh-session/types';
3
+ /** Navigation action injected from the plugin's own Session Controller 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 status-driven 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 { Context as ClientContext } from '@prettier-ai/cordis';
3
+ import { type WorkflowRunKey } from './locales.ts';
4
+ declare module '@prettier-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 '@prettier-ai/dsh-client-ui-conversation/client';
2
+ import type { SessionId } from '@prettier-ai/dsh-session/types';
3
+ import type { ToolWorkflowAgentStartData } from '@prettier-ai/dsh-tool-workflow/types';
4
+ import type { WorkflowAgentOutcome, WorkflowStopReason } from '@prettier-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 '@prettier-ai/dsh-client-ui-chat/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 '@prettier-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,88 @@
1
+ {
2
+ "name": "@prettier-ai/dsh-client-ui-workflow-run",
3
+ "description": "Durable workflow-run Conversation Node and nested member disclosure for dsh web",
4
+ "version": "0.1.2-alpha.1",
5
+ "publishConfig": {
6
+ "access": "public"
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
+ "@prettier-ai/dsh-api-session-controller",
36
+ "@prettier-ai/dsh-client-locale",
37
+ "@prettier-ai/dsh-client-ui-chat",
38
+ "@prettier-ai/dsh-client-ui-conversation",
39
+ "@prettier-ai/dsh-client-ui-renderer",
40
+ "@prettier-ai/dsh-client-ui-session"
41
+ ],
42
+ "platform": "web"
43
+ }
44
+ },
45
+ "files": [
46
+ "lib/index.js",
47
+ "lib/invariant.js",
48
+ "lib/client.js",
49
+ "lib/types/**/*.d.ts"
50
+ ],
51
+ "license": "MIT",
52
+ "peerDependencies": {
53
+ "@prettier-ai/dsh-api-session-controller": "^0.1.2-alpha.1",
54
+ "@prettier-ai/dsh-client-ui-chat": "^0.1.2-alpha.1",
55
+ "@prettier-ai/dsh-client-locale": "^0.1.2-alpha.1",
56
+ "@prettier-ai/dsh-client-ui-renderer": "^0.1.2-alpha.1",
57
+ "@prettier-ai/dsh-client-ui-conversation": "^0.1.2-alpha.1",
58
+ "@prettier-ai/dsh-client-ui-session": "^0.1.2-alpha.1",
59
+ "@prettier-ai/dsh-invariants": "^0.1.2-alpha.1",
60
+ "@prettier-ai/dsh-session": "^0.1.2-alpha.1",
61
+ "@prettier-ai/dsh-tool-workflow": "^0.1.2-alpha.1",
62
+ "@prettier-ai/dsh-workflow": "^0.1.2-alpha.1",
63
+ "@prettier-ai/cordis": "^4.0.1"
64
+ },
65
+ "devDependencies": {
66
+ "@types/react": "~18.3.1",
67
+ "react": "^18.2.0",
68
+ "@prettier-ai/dsh-api-session-controller": "^0.1.2-alpha.1",
69
+ "@prettier-ai/dsh-client-locale": "^0.1.2-alpha.1",
70
+ "@prettier-ai/dsh-client-store": "^0.1.2-alpha.1",
71
+ "@prettier-ai/dsh-client-ui-chat": "^0.1.2-alpha.1",
72
+ "@prettier-ai/dsh-client-ui-conversation": "^0.1.2-alpha.1",
73
+ "@prettier-ai/dsh-client-test-runtime": "^0.1.2-alpha.1",
74
+ "@prettier-ai/dsh-client-ui-primitives": "^0.1.2-alpha.1",
75
+ "@prettier-ai/dsh-client-ui-renderer": "^0.1.2-alpha.1",
76
+ "@prettier-ai/dsh-client-ui-session": "^0.1.2-alpha.1",
77
+ "@prettier-ai/dsh-session": "^0.1.2-alpha.1",
78
+ "@prettier-ai/dsh-client-ui-slots": "^0.1.2-alpha.1",
79
+ "@prettier-ai/dsh-tool-workflow": "^0.1.2-alpha.1",
80
+ "@prettier-ai/dsh-workflow": "^0.1.2-alpha.1",
81
+ "@prettier-ai/dsh-invariants": "^0.1.2-alpha.1",
82
+ "@prettier-ai/cordis": "^4.0.1"
83
+ },
84
+ "scripts": {
85
+ "bundle": "tsdown",
86
+ "watch": "tsdown --watch"
87
+ }
88
+ }