dsh-workflow-map 0.0.0-stage → 0.3.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/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Workflow Map",
4
+ "description": "Floating two-column panel in the top-right corner showing the session's steps and each step's flow nodes."
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "工作流地图",
4
+ "description": "在界面右上角以悬浮双列面板展示当前会话的分步总览与每步的流程节点明细。"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,6 +1,67 @@
1
1
  {
2
2
  "name": "dsh-workflow-map",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.3.2",
4
+ "description": "工作流地图插件(DeepSeek Harness bundle):在会话标签栏、左栏菜单与主内容区以同一份面板展示当前会话的分步总览、每步的工具/文件类型明细、唯一彩色流光进度条,并支持热注入(steer)与「需确认」标注。宿主半折叠会话事件并读取 PAE 计划,经本地 HTTP 视图供数据;浏览器半渲染面板。",
5
+ "private": false,
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "keywords": [
9
+ "deepseek-harness",
10
+ "dsh",
11
+ "cordis",
12
+ "plugin",
13
+ "bundle",
14
+ "workflow",
15
+ "progress",
16
+ "ui"
17
+ ],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/Joz739/dsh-workflow-map.git"
21
+ },
22
+ "homepage": "https://github.com/Joz739/dsh-workflow-map#readme",
23
+ "bugs": {
24
+ "url": "https://github.com/Joz739/dsh-workflow-map/issues"
25
+ },
26
+ "engines": {
27
+ "node": ">=20"
28
+ },
29
+ "exports": {
30
+ ".": "./index.js",
31
+ "./client": "./client.js",
32
+ "./locale/*.json": "./locale/*.json",
33
+ "./icon": "./icon.svg"
34
+ },
35
+ "files": [
36
+ "index.js",
37
+ "lib",
38
+ "src",
39
+ "client.js",
40
+ "cordis.patch.yml",
41
+ "locale/*.json",
42
+ "icon.svg",
43
+ "README.md",
44
+ "README.en.md",
45
+ "CHANGELOG.md",
46
+ "LICENSE",
47
+ "docs/OPEN-SOURCE-GUIDE.md",
48
+ "docs/screenshot.png",
49
+ "docs/screenshot-detail.png",
50
+ "package.json"
51
+ ],
52
+ "scripts": {
53
+ "build": "node tools/build-client.mjs",
54
+ "test": "node tools/selftest-fold.mjs && node tools/selftest-host.mjs && node tools/selftest-orchestrator.mjs && node tools/selftest-shape.mjs && node tools/selftest-panel.mjs && node tools/selftest-detail.mjs && node tools/selftest-effects.mjs && node tools/selftest-settings.mjs && node tools/probe-route.mjs && node tools/probe-steer.mjs",
55
+ "test:real": "node tools/check-real-injects.mjs"
56
+ },
57
+ "dsh": {
58
+ "bundle": {
59
+ "patch": "./cordis.patch.yml"
60
+ },
61
+ "client": {
62
+ "platform": "web",
63
+ "immediately": true,
64
+ "inject": []
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Branches.js — the parallel lanes beside a step's flow.
3
+ *
4
+ * What a lane is: a subagent, a workflow run or a background job started by this plan
5
+ * step. Its activity runs IN PARALLEL with the step, so it is drawn as a branch off the
6
+ * main flow track instead of being mixed into the node list:
7
+ *
8
+ * main track (FlowColumn) lane (this file)
9
+ * ─ ● read · a.js ┌ ⧉ subagent · review the fold module ●
10
+ * ─ ● edit · b.js ──────6px─┤ ▸ subagent started 21:03:07
11
+ * ─ ● command · npm test │ ▪ done 21:04:12
12
+ * └ (click the head to fold the lane)
13
+ *
14
+ * The lane data comes from `view.branches` (src/fold.mjs): branches NEVER enter
15
+ * `state.nodes`, so the main flow's node count, its same-tool groups, both caps and the
16
+ * "current activity" highlight are untouched by them. A lane therefore has its OWN status
17
+ * dot — a running lane breathes, it does not flash as the session's current node.
18
+ *
19
+ * Pure: `h` and `say` are injected, no React import, no DOM. Inlined into client.js by
20
+ * tools/build-client.mjs.
21
+ */
22
+
23
+ /**
24
+ * One hue per lane (index into this list), so two adjacent lanes never read as one.
25
+ * Deliberately not kind-based: the badge text already says subagent / workflow / background job.
26
+ */
27
+ var WF_BRANCH_HUES = [268, 196, 152, 32, 340, 224];
28
+
29
+ /** Glyph per lane row kind (the row kinds fold.mjs writes). */
30
+ var WF_BRANCH_ROW_GLYPH = {
31
+ start: "▸",
32
+ end: "▪",
33
+ phase: "◈",
34
+ agent: "⧉",
35
+ job: "⏱",
36
+ tool: "⚙",
37
+ };
38
+
39
+ /** Lane ordinal → hue. Pure and total: any index, including a negative or missing one. */
40
+ function wfBranchHue(index) {
41
+ var value = wfFinite(index);
42
+ var slot = value === null ? 0 : Math.abs(Math.round(value)) % WF_BRANCH_HUES.length;
43
+ return WF_BRANCH_HUES[slot];
44
+ }
45
+
46
+ /** Lane status → the four-state vocabulary the rest of the panel uses. */
47
+ function wfBranchStatus(branch) {
48
+ var status = branch && typeof branch.status === "string" ? branch.status : "running";
49
+ if (status === "failed") return "error";
50
+ if (status === "done") return "done";
51
+ return "running";
52
+ }
53
+
54
+ /** Lane kind -> the locale key of its badge (subagent / workflow / background job). */
55
+ function wfBranchBadgeKey(kind) {
56
+ if (kind === "workflow") return "panel.branch.workflow";
57
+ if (kind === "job") return "panel.branch.job";
58
+ return "panel.branch.subagent";
59
+ }
60
+
61
+ /** Lane row status → dot state. A phase heading has no dot at all (it is not work). */
62
+ function wfBranchRowStatus(node) {
63
+ var status = node && typeof node.status === "string" ? node.status : "";
64
+ if (status === "failed") return "error";
65
+ if (status === "done") return "done";
66
+ if (status === "running") return "running";
67
+ return "pending";
68
+ }
69
+
70
+ /**
71
+ * Is the lane folded? The per-step UI state keeps `closedBranches` (lanes open by
72
+ * default — the point of a lane is to show the parallel work), so this is a pure read.
73
+ */
74
+ function wfBranchIsCollapsed(state, branchId) {
75
+ if (!state || !Array.isArray(state.closedBranches) || typeof branchId !== "string") return false;
76
+ return state.closedBranches.indexOf(branchId) >= 0;
77
+ }
78
+
79
+ /**
80
+ * One activity row. It is a BUTTON because mounts the same detail view for any
81
+ * row; until then the click only reports the id through `actions.onSelectNode`.
82
+ */
83
+ function wfRenderBranchRow(h, node, say, actions, ctx) {
84
+ var fire = actions || {};
85
+ var context = ctx || {};
86
+ var status = wfBranchRowStatus(node);
87
+ var selected = context.selectedNodeId !== null && context.selectedNodeId === node.id;
88
+ var detail = node && typeof node.detail === "string" ? node.detail : "";
89
+
90
+ return h(
91
+ "li",
92
+ {
93
+ className: "wf-branch-row",
94
+ key: "row-" + node.id,
95
+ "data-status": status,
96
+ "data-node-id": node.id,
97
+ },
98
+ h(
99
+ "button",
100
+ {
101
+ type: "button",
102
+ className: "wf-branch-node",
103
+ "data-dsh-workflow-map": "branch-node",
104
+ "data-node-id": node.id,
105
+ "data-kind": node.kind,
106
+ "data-status": status,
107
+ "data-selected": selected ? "true" : "false",
108
+ "aria-pressed": selected ? "true" : "false",
109
+ title: detail === "" ? node.title : node.title + " — " + detail,
110
+ onClick: function () {
111
+ if (typeof fire.onSelectNode === "function") fire.onSelectNode(node.id);
112
+ },
113
+ },
114
+ h("span", { className: "wf-branch-glyph", key: "glyph", "aria-hidden": "true" }, WF_BRANCH_ROW_GLYPH[node.kind] || "·"),
115
+ h(
116
+ "span",
117
+ { className: "wf-branch-row-main", key: "main" },
118
+ h("span", { className: "wf-branch-row-title", key: "title" }, node.title || "—"),
119
+ detail === "" ? null : h("span", { className: "wf-branch-row-detail", key: "detail" }, detail),
120
+ ),
121
+ h("span", { className: "wf-branch-clock", key: "clock" }, wfFormatClock(node.at)),
122
+ h("span", { className: "wf-flow-status", key: "dot", "data-status": status, "aria-hidden": "true" }),
123
+ ),
124
+ );
125
+ }
126
+
127
+ /**
128
+ * One lane: the coloured 6 px connector from the main track, the head (badge · label ·
129
+ * count · status dot · caret) and the body. Folding is per lane and remembered for the
130
+ * SELECTED step only (see wfFlowUiFor), exactly like a folded group.
131
+ */
132
+ function wfRenderBranch(h, branch, index, flowState, say, actions, ctx) {
133
+ var fire = actions || {};
134
+ var hue = wfBranchHue(index);
135
+ var status = wfBranchStatus(branch);
136
+ var collapsed = wfBranchIsCollapsed(flowState, branch.id);
137
+ var nodes = Array.isArray(branch.nodes) ? branch.nodes : [];
138
+ var count = wfFinite(branch.count) === null ? nodes.length : branch.count;
139
+ var truncated = wfFinite(branch.truncated) === null ? Math.max(0, count - nodes.length) : branch.truncated;
140
+ var label = typeof branch.label === "string" && branch.label !== "" ? branch.label : "—";
141
+
142
+ var rows = [];
143
+ if (!collapsed) {
144
+ if (truncated > 0) {
145
+ rows.push(h("li", { className: "wf-branch-trunc", key: "trunc" }, say("panel.branch.truncated", { count: truncated })));
146
+ }
147
+ for (var i = 0; i < nodes.length; i += 1) {
148
+ var node = nodes[i];
149
+ if (node.kind === "phase") {
150
+ rows.push(h("li", { className: "wf-branch-phase", key: "phase-" + node.id }, node.title));
151
+ } else {
152
+ rows.push(wfRenderBranchRow(h, node, say, actions, ctx));
153
+ }
154
+ }
155
+ }
156
+
157
+ return h(
158
+ "div",
159
+ {
160
+ className: "wf-branch",
161
+ key: "branch-" + branch.id,
162
+ // Concrete colours, not a CSS colour function: the panel must not depend on the
163
+ // Electron build supporting the space-separated hsl syntax.
164
+ style: {
165
+ "--wf-branch-h": String(hue),
166
+ "--wf-branch-line": `hsl(${hue}, 78%, 62%)`,
167
+ "--wf-branch-dim": `hsla(${hue}, 70%, 50%, .5)`,
168
+ "--wf-branch-text": `hsl(${hue}, 74%, 74%)`,
169
+ },
170
+ "data-dsh-workflow-map": "branch",
171
+ "data-branch-id": branch.id,
172
+ "data-kind": branch.kind,
173
+ "data-status": status,
174
+ "data-expanded": collapsed ? "false" : "true",
175
+ "data-hue": hue,
176
+ },
177
+ h("span", { className: "wf-branch-link", key: "link", "aria-hidden": "true" }),
178
+ h(
179
+ "div",
180
+ { className: "wf-branch-card", key: "card" },
181
+ h(
182
+ "button",
183
+ {
184
+ type: "button",
185
+ className: "wf-branch-head",
186
+ "data-dsh-workflow-map": "branch-head",
187
+ "data-branch-id": branch.id,
188
+ "aria-expanded": collapsed ? "false" : "true",
189
+ // Mirrored on the card (data-expanded) as well, so a test can read either.
190
+ title: say(collapsed ? "panel.branch.expand" : "panel.branch.collapse"),
191
+ onClick: function () {
192
+ if (typeof fire.onToggleBranch === "function") fire.onToggleBranch(branch.id);
193
+ },
194
+ },
195
+ h("span", { className: "wf-branch-badge", key: "badge" }, say(wfBranchBadgeKey(branch.kind))),
196
+ h("span", { className: "wf-branch-label", key: "label" }, label),
197
+ h("span", { className: "wf-branch-meta", key: "meta" }, say("panel.branch.nodes", { count: count })),
198
+ h("span", { className: "wf-branch-dot", key: "dot", "data-status": status, "aria-hidden": "true" }),
199
+ h("span", { className: "wf-branch-caret", key: "caret", "aria-hidden": "true" }, "▶"),
200
+ ),
201
+ collapsed ? null : h("ul", { className: "wf-branch-body", key: "body", role: "list" }, rows),
202
+ ),
203
+ );
204
+ }
205
+
206
+ /**
207
+ * The whole branch area for the selected step, or NULL when nothing runs in parallel —
208
+ * the acceptance asks for NO shell at all in a session without subagents, not an empty
209
+ * placeholder (an empty box would read as "a branch exists but is empty").
210
+ */
211
+ function wfRenderBranches(h, model, say, actions, flowState) {
212
+ var flow = model && model.flow && typeof model.flow === "object" ? model.flow : null;
213
+ var branches = flow && Array.isArray(flow.branches) ? flow.branches : [];
214
+ if (branches.length === 0) return null;
215
+
216
+ var hidden = flow && wfFinite(flow.hiddenBranches) !== null ? Math.max(0, flow.hiddenBranches) : 0;
217
+ var dropped = flow && wfFinite(flow.branchDropped) !== null ? Math.max(0, flow.branchDropped) : 0;
218
+ var ctx = {
219
+ selectedNodeId:
220
+ flowState && typeof flowState.selectedNode === "string" && flowState.selectedNode !== "" ? flowState.selectedNode : null,
221
+ };
222
+
223
+ var notes = [];
224
+ if (hidden > 0) notes.push(say("panel.branch.more", { count: hidden }));
225
+ if (dropped > 0) notes.push(say("panel.branch.dropped", { count: dropped }));
226
+
227
+ return h(
228
+ "div",
229
+ { className: "wf-branches", key: "branches", "data-dsh-workflow-map": "branches", "data-branch-count": branches.length },
230
+ h(
231
+ "div",
232
+ { className: "wf-branches-h", key: "head" },
233
+ h("span", { key: "label" }, say("panel.branch")),
234
+ h("span", { className: "wf-count", key: "count" }, String(branches.length)),
235
+ ),
236
+ branches.map(function (branch, index) {
237
+ return wfRenderBranch(h, branch, index, flowState, say, actions, ctx);
238
+ }),
239
+ notes.length === 0 ? null : h("div", { className: "wf-branch-more", key: "more" }, notes.join(" · ")),
240
+ );
241
+ }