dsh-knj-workflow 2026.9.72 → 2026.9.73

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/lib/client.js CHANGED
@@ -503,6 +503,20 @@ table.knj-tbl{width:100%;border-collapse:collapse;table-layout:fixed}
503
503
  .knj-archive-list{flex:1;padding:14px 16px;overflow-y:auto}
504
504
  .knj-arch-row{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:10px;padding:12px 14px;display:flex;align-items:center;gap:12px;margin-bottom:10px}
505
505
  .knj-arch-meta{margin-left:auto;font-size:12px;color:var(--dsw-alias-label-tertiary)}
506
+ /* ===== 流程运行面板:状态对比增强 + 运行中呼吸脉冲 =====
507
+ 只对「正在运行」这一主信号加动画(其余状态保持静态),脉冲柔和克制、契合 DSH 原生风格。
508
+ 色值为既有语义色(RUN_STATUS/STAGE_META),未新增调色板。 */
509
+ @keyframes knjRunPulse{0%,100%{opacity:.9}50%{opacity:.25}}
510
+ @keyframes knjRunBreath{0%,100%{box-shadow:0 0 0 3px rgba(47,107,255,.22)}50%{box-shadow:0 0 0 9px rgba(47,107,255,.05)}}
511
+ @keyframes knjBarStrip{to{background-position:14px 0}}
512
+ .knj-run-glow{animation:knjRunPulse 2s ease-in-out infinite}
513
+ .knj-node-dot.running{animation:knjRunBreath 1.6s ease-in-out infinite}
514
+ .knj-node-card.running{border-color:#2f6bff;background:var(--dsw-alias-bg-base)}
515
+ .knj-node-card.done{border-color:var(--dsw-alias-state-success-primary)}
516
+ .knj-node-card.pending{border-color:var(--dsw-alias-border-l2);opacity:.85}
517
+ .knj-node-card.failed{border-color:var(--dsw-alias-state-error-primary)}
518
+ .knj-node-card.skipped{opacity:.55}
519
+ .knj-progress.animated>div{background-image:linear-gradient(90deg,var(--dsw-alias-state-business-primary) 0 62%,rgba(47,107,255,.5) 62% 100%);background-size:14px 100%;animation:knjBarStrip .9s linear infinite}
506
520
  `;
507
521
 
508
522
  function toast(msg) {
@@ -2893,7 +2907,7 @@ table.knj-tbl{width:100%;border-collapse:collapse;table-layout:fixed}
2893
2907
  kids.push(react.createElement('rect', { key: 'b', x: n.x - 31, y: n.y - 18, width: 62, height: 36, rx: 18, fill: 'var(--dsw-alias-bg-base)', stroke: isSkipped ? '#b6bdc7' : (status === 'pending' ? '#8b95a5' : c), strokeWidth: 1.4, strokeDasharray: isSkipped ? '4 3' : undefined, opacity: dim }));
2894
2908
  kids.push(react.createElement('text', { key: 't', x: n.x, y: n.y + 4, fontSize: 12, textAnchor: 'middle', fill: 'var(--dsw-alias-label-secondary)' }, n.type === 'start' ? '开始' : '结束'));
2895
2909
  } else {
2896
- kids.push(react.createElement('rect', { key: 'b', x: n.x - w / 2, y: n.y - hh / 2, width: w, height: hh, rx: 9, fill: n.type === 'human' ? '#f3efff' : 'var(--dsw-alias-bg-base)', stroke: 'var(--dsw-alias-border-l2)', strokeWidth: 1, opacity: dim }));
2910
+ kids.push(react.createElement('rect', { key: 'b', x: n.x - w / 2, y: n.y - hh / 2, width: w, height: hh, rx: 9, fill: n.type === 'human' ? '#f3efff' : (status === 'running' ? '#e7efff' : status === 'done' ? '#f0faf3' : 'var(--dsw-alias-bg-base)'), stroke: isSkipped ? '#b6bdc7' : (status === 'pending' ? 'var(--dsw-alias-border-l2)' : c), strokeWidth: status === 'running' ? 2.1 : 1, opacity: dim }));
2897
2911
  kids.push(react.createElement('rect', { key: 'bar', x: n.x - w / 2 + 1, y: n.y - hh / 2 + 1, width: 3.5, height: hh - 2, rx: 1.75, fill: isSkipped ? '#9aa3af' : c, strokeDasharray: isSkipped ? '3 2' : undefined }));
2898
2912
  kids.push(react.createElement('text', { key: 'tt', x: n.x + 5, y: n.y - 1, fontSize: 13, fontWeight: 600, textAnchor: 'middle', fill: 'var(--dsw-alias-label-primary)', style: { userSelect: 'none' } }, (n.title || n.id).length > 11 ? (n.title || n.id).slice(0, 11) + '…' : (n.title || n.id)));
2899
2913
  const sub = status === 'running' ? '运行中' : status === 'waiting' ? '等待人工审批' : (status === 'passed' ? '已过' : meta.label);
@@ -2907,8 +2921,8 @@ table.knj-tbl{width:100%;border-collapse:collapse;table-layout:fixed}
2907
2921
  else if (status === 'skipped') kids.push(react.createElement('path', { key: 'ic2', d: `M ${ix - 3.2} ${iy} h 6.4`, stroke: '#fff', strokeWidth: 1.6, strokeLinecap: 'round' }));
2908
2922
  else if (status === 'waiting') kids.push(react.createElement('circle', { key: 'ic2', cx: ix, cy: iy - 1.5, r: 2.2, fill: 'none', stroke: '#fff', strokeWidth: 1.2 }), react.createElement('path', { key: 'ic3', d: `M ${ix - 3.6} ${iy + 4.6} c 0 -2.1 1.7 -3.1 3.6 -3.1 s 3.6 1 3.6 3.1`, stroke: '#fff', strokeWidth: 1.2, fill: 'none', strokeLinecap: 'round' }));
2909
2923
  }
2910
- // running 光环(静态两层,替代动画,避免依赖注入 keyframes)
2911
- if (status === 'running') kids.push(react.createElement('circle', { key: 'halo', cx: n.x, cy: n.y, r: 33, fill: 'none', stroke: '#2f6bff', strokeWidth: 1.6, opacity: 0.35 }));
2924
+ // running 光环:贴合节点矩形的呼吸光边(非圆环;仅运行节点=当前主信号上动画)
2925
+ if (status === 'running') kids.push(react.createElement('rect', { key: 'halo', className: 'knj-run-glow', x: n.x - w / 2 - 5, y: n.y - hh / 2 - 5, width: w + 10, height: hh + 10, rx: 13, fill: 'rgba(47,107,255,.1)', stroke: '#2f6bff', strokeWidth: 2.2 }));
2912
2926
  }
2913
2927
  const isSel = sel;
2914
2928
  const hit = react.createElement('rect', { x: n.x - 70, y: n.y - 38, width: 140, height: 76, rx: 12, fill: 'transparent', onClick: (e) => { e.stopPropagation(); this.setState({ graphSel: n.id }); const stage = (this.props.task.stageStates || []).find((x) => x.id === n.id); if (stage && stage.status === 'done' && this.resultFor(n.id) == null) this.loadStageResult(n.id); }, style: { cursor: 'pointer' } });
@@ -2935,9 +2949,9 @@ table.knj-tbl{width:100%;border-collapse:collapse;table-layout:fixed}
2935
2949
  : s.status === 'skipped' ? '–'
2936
2950
  : s.status === 'failed' ? icon('x')
2937
2951
  : (i + 1);
2938
- return react.createElement('div', { key: s.id, className: 'knj-node-card' },
2952
+ return react.createElement('div', { key: s.id, className: 'knj-node-card' + (isRunning ? ' running' : s.status === 'done' ? ' done' : s.status === 'pending' ? ' pending' : s.status === 'failed' ? ' failed' : s.status === 'skipped' ? ' skipped' : '') },
2939
2953
  react.createElement('div', { className: 'knj-node-head', onClick: () => hasDetail && this.toggle(s.id) },
2940
- react.createElement('div', { className: 'knj-node-dot', style: { background: meta.color, ...(isRunning ? { boxShadow: '0 0 0 4px rgba(37,99,235,.2)' } : {}) } }, statusIcon),
2954
+ react.createElement('div', { className: 'knj-node-dot' + (isRunning ? ' running' : ''), style: { background: meta.color } }, statusIcon),
2941
2955
  react.createElement('div', { style: { flex: 1, minWidth: 0 } },
2942
2956
  react.createElement('div', { className: 'knj-node-title' }, s.title),
2943
2957
  react.createElement('div', { className: 'knj-node-sub' }, dur ? `${dur} · ${meta.label}` : meta.label),
@@ -3233,7 +3247,7 @@ table.knj-tbl{width:100%;border-collapse:collapse;table-layout:fixed}
3233
3247
  task.model ? react.createElement('span', { className: 'knj-badge', style: { background: '#5b6472' }, title: '该任务固定的执行模型(创建时指定)' }, task.model) : null,
3234
3248
  ),
3235
3249
  // 整体进度条 + 摘要
3236
- react.createElement('div', { className: 'knj-progress', style: { width: '100%', marginTop: 12 } },
3250
+ react.createElement('div', { className: 'knj-progress' + (task.status === 'running' ? ' animated' : ''), style: { width: '100%', marginTop: 12 } },
3237
3251
  react.createElement('div', { style: { width: pct + '%' } }),
3238
3252
  ),
3239
3253
  react.createElement('div', { className: 'sub', style: { marginTop: 4 } }, `进度 ${pct}% · 完成 ${doneCount}/${relevant.length} 阶段${steps.length - relevant.length ? `(跳过 ${steps.length - relevant.length})` : ''}`),
package/lib/index.js CHANGED
@@ -627,6 +627,18 @@ export class WorkflowBridge {
627
627
  // sessionIds 无限增长(每次续跑 +N 条重复 id)
628
628
  const ids = s.sessionIds || [];
629
629
  if (!ids.includes(childId)) s.sessionIds = [...ids, childId];
630
+ // 真实执行信号:主体 subagent 启动 = 本节点正在跑。多轮复用同一节点时
631
+ // (如人工驳回 → fix 重做 → 再驳回 → fix 再重做),上一轮 done 会挡死状态:
632
+ // onPhase 只把 pending/skipped→running,onAgentEnd 只把 running→done,
633
+ // done 阶段被再次执行时永远停留在旧 done/旧 finishedAt(task-mtu18nd3 实测:
634
+ // 显示 fix 已完成 12:06,实际 checkpoint 12:31 才落盘)。这里以 agent-start
635
+ // 为唯一事实源强制置回 running:清 finishedAt、刷新 startedAt 为本轮纪元。
636
+ if (s.status !== 'running') {
637
+ s.status = 'running';
638
+ s.startedAt = nowIso();
639
+ s.finishedAt = undefined;
640
+ s.error = undefined;
641
+ }
630
642
  }
631
643
  }
632
644
  }).catch(() => {});
@@ -36,6 +36,12 @@ let humanFeedback = null; // 人工意见:注入去向目标节点(仅一
36
36
  // 直接走其最后去向(目标已在 initialResults 则被 skipSet 跳过;否则正常执行)。
37
37
  // 没有它,两个顺序 human 的任务会在 H1/H2 之间无限振荡(H2 的决策被 H1 的重新暂停吞掉)。
38
38
  const decidedMap = args.decided || {};
39
+ // 本轮已放行过一次去向的 human(消费当前决策 / 重放历史决策都算放行)。
40
+ // 去向把流程带回同一个 human(如驳回 → fix → submit → 同一 human 的回环)时,再次到达
41
+ // = 新一轮审批,必须暂停等人工,不能再用 decidedMap 里的旧决策放行第二次——
42
+ // 否则 fix/submit 被反复重执行直到 maxRuns 超限报错,人工节点永远无法重新触发
43
+ // (task-mttp8q71/task-mttz5ok7 实测:驳回后 fix/submit 空转 → 任务卡 running/失败)。
44
+ const humanReleased = new Set();
39
45
 
40
46
  /** 节点最大执行次数:显式配置 maxRuns 优先,默认 3(首次 + 最多 2 次重跑)。 */
41
47
  function maxRunsOf(node) {
@@ -355,12 +361,16 @@ async function executeUntil(nodeId, joinId) {
355
361
  const route = matchRoute(node, decision.value);
356
362
  if (decision.feedback) humanFeedback = decision.feedback;
357
363
  skipSet.delete(route.to); // 去向目标应重新执行(接受人工意见),避免被断点跳过
364
+ humanReleased.add(node.id); // 本 walk 已放行过一次 → 回访即暂停(新一轮审批)
358
365
  current = route.to;
359
- } else if (decidedMap[node.id]) {
366
+ } else if (decidedMap[node.id] && !humanReleased.has(node.id)) {
360
367
  // 历史决策重放:这个 human 已审批过(如顺序两个 human,后一个决策触发 resume 重走图)。
361
368
  // 直接走最后去向:目标已在 initialResults → skipSet 跳过;未执行过 → 正常执行。
362
369
  // 不注入意见(当初决策后的执行已带过意见)。
370
+ // 每个 human 每轮 walk 只重放一次:去向若把流程带回同一 human(驳回回环),
371
+ // 第二次到达意味着要重新审批,直接暂停而不是再次放行旧决策(见 humanReleased 注释)。
363
372
  const route = matchRoute(node, decidedMap[node.id]);
373
+ humanReleased.add(node.id);
364
374
  current = route.to;
365
375
  } else {
366
376
  return { ok: true, current: null, paused: true, pausedAt: node.id };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-knj-workflow",
3
- "version": "2026.9.72",
3
+ "version": "2026.9.73",
4
4
  "description": "KNJ 开发任务编排插件:配置驱动的工作流 + 开发任务管理 + 可视化阶段进度(DeepSeek Harness)",
5
5
  "author": "knj",
6
6
  "license": "MIT",