@godv61/dsh-task-engine 0.23.8 → 0.24.0

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
@@ -4547,6 +4547,27 @@ var workbenchTheme = `
4547
4547
  .te-workbench button:focus-visible,.te-modal button:focus-visible,.te-input:focus-visible { outline:2px solid var(--dsw-alias-state-business-primary); outline-offset:3px; }
4548
4548
  .te-workbench h2 { margin:0 0 6px; font-size:24px; letter-spacing:-.5px; }
4549
4549
  .te-workbench h3,.te-modal h3 { margin:0; font-size:16px; overflow-wrap:anywhere; }
4550
+ .te-workbench .te-disclosure-title { font-size:16px; font-weight:700; color:var(--dsw-alias-label-primary); }
4551
+ .te-binding-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(min(100%,320px),1fr)); gap:14px; align-items:start; }
4552
+ .te-binding-picker { min-width:0; display:flex; flex-direction:column; gap:10px; border:1px solid var(--dsw-alias-border-l2); border-radius:12px; padding:14px; background:var(--dsw-alias-bg-layer-1); }
4553
+ .te-binding-heading { display:flex; align-items:center; justify-content:space-between; gap:8px; }
4554
+ .te-binding-heading h4 { margin:0; font-size:14px; font-weight:650; }
4555
+ .te-binding-count,.te-binding-meta { color:var(--dsw-alias-label-secondary); font-size:12px; }
4556
+ .te-binding-tools { display:flex; gap:10px; align-items:center; flex-wrap:wrap; }
4557
+ .te-binding-tools .te-input { flex:1; width:100%; min-width:120px; padding:8px 10px; font-size:13px; }
4558
+ .te-binding-filter { display:flex; align-items:center; gap:5px; font-size:12px; white-space:nowrap; cursor:pointer; }
4559
+ .te-binding-list { max-height:280px; overflow-y:auto; overscroll-behavior:contain; display:flex; flex-direction:column; gap:4px; scrollbar-gutter:stable; }
4560
+ .te-binding-option { display:flex; gap:9px; align-items:flex-start; padding:9px; border:1px solid transparent; border-radius:8px; cursor:pointer; }
4561
+ .te-binding-option:hover { background:var(--dsw-alias-bg-layer-2); }
4562
+ .te-binding-option.is-selected { background:var(--dsw-alias-bg-layer-2); border-color:var(--dsw-alias-border-l2); }
4563
+ .te-binding-option.is-fixed { cursor:default; }
4564
+ .te-binding-option input { flex:none; margin:3px 0 0; }
4565
+ .te-binding-picker input { accent-color:var(--dsw-alias-state-business-primary); }
4566
+ .te-binding-picker :focus-visible { outline:2px solid var(--dsw-alias-state-business-primary); outline-offset:2px; }
4567
+ .te-binding-detail { min-width:0; display:flex; flex-direction:column; gap:3px; }
4568
+ .te-binding-name { font-size:13px; font-weight:600; overflow-wrap:anywhere; }
4569
+ .te-binding-description { color:var(--dsw-alias-label-secondary); font-size:12px; line-height:1.4; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; overflow-wrap:anywhere; }
4570
+ .te-binding-empty { margin:0; padding:18px 8px; font-size:13px; }
4550
4571
  .te-workbench p,.te-modal p { color:var(--dsw-alias-label-secondary); line-height:1.65; }
4551
4572
  .te-content { max-width:1120px; margin:auto; width:100%; }
4552
4573
  .te-resource { display:flex; flex-direction:column; gap:20px; }
@@ -4613,7 +4634,7 @@ var styles = {
4613
4634
  section: { display: "flex", flexDirection: "column", gap: 12 },
4614
4635
  cardBody: { padding: "4px 0 8px", display: "flex", flexDirection: "column", gap: 12 },
4615
4636
  row: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" },
4616
- field: { display: "flex", flexDirection: "column", gap: 4, flex: "1 1 220px", minWidth: 180 },
4637
+ field: { display: "flex", flexDirection: "column", gap: 6, minWidth: 0 },
4617
4638
  fieldLabel: { color: textSecondary, fontSize: 13 },
4618
4639
  hint: { color: textTertiary, fontSize: 12, margin: 0, lineHeight: 1.4 },
4619
4640
  chips: { display: "flex", flexWrap: "wrap", gap: 6, alignItems: "center" },
@@ -4719,6 +4740,14 @@ var import_react = require("react");
4719
4740
  var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
4720
4741
 
4721
4742
  // src/engine.ts
4743
+ function formatResourceRef(ref) {
4744
+ return `${ref.source}:${ref.name}`;
4745
+ }
4746
+ function parseResourceRef(text2) {
4747
+ const match = /^(bundled|project|user):(.+)$/u.exec(text2);
4748
+ if (match === null) return void 0;
4749
+ return { source: match[1], name: match[2] };
4750
+ }
4722
4751
  var GUARD_NAMES = [
4723
4752
  "requirement_confirmation",
4724
4753
  "solution_confirmation",
@@ -4779,15 +4808,26 @@ function validateWorkflow(config) {
4779
4808
  if (!config.stages.includes(stage)) {
4780
4809
  problems.push(`stage_bindings: stage "${stage}" is not a stage`);
4781
4810
  }
4782
- for (const skill of binding.skills ?? []) {
4783
- if (typeof skill !== "string" || skill.trim() === "") {
4784
- problems.push(`stage_bindings "${stage}": empty skill name`);
4811
+ for (const [index, entry] of (binding.skills ?? []).entries()) {
4812
+ if (typeof entry?.skill?.name !== "string" || entry.skill.name.trim() === "") {
4813
+ problems.push(`stage_bindings "${stage}": skill ${index + 1} has no name`);
4814
+ continue;
4785
4815
  }
4786
- }
4787
- for (const rule of binding.rules ?? []) {
4788
- if (typeof rule !== "string" || rule.trim() === "") {
4789
- problems.push(`stage_bindings "${stage}": empty rule name`);
4816
+ if (!["bundled", "project", "user"].includes(entry.skill.source)) {
4817
+ problems.push(`stage_bindings "${stage}": skill "${entry.skill.name}" has unknown source "${String(entry.skill.source)}"`);
4790
4818
  }
4819
+ for (const rule of entry.rules ?? []) {
4820
+ if (typeof rule?.name !== "string" || rule.name.trim() === "") {
4821
+ problems.push(`stage_bindings "${stage}": skill "${entry.skill.name}" has a rule with no name`);
4822
+ continue;
4823
+ }
4824
+ if (!["bundled", "project", "user"].includes(rule.source)) {
4825
+ problems.push(`stage_bindings "${stage}": skill "${entry.skill.name}" rule "${rule.name}" has unknown source "${String(rule.source)}"`);
4826
+ }
4827
+ }
4828
+ }
4829
+ if ((binding.legacy_rules ?? []).length > 0) {
4830
+ problems.push(`stage_bindings "${stage}": ${binding.legacy_rules.length} legacy stage-level rule(s) are unassigned and still in force; assign each to the skill that should carry it`);
4791
4831
  }
4792
4832
  }
4793
4833
  if (!["task", "item", "manual"].includes(config.commit.policy)) {
@@ -4805,14 +4845,21 @@ function validateWorkflow(config) {
4805
4845
  problems.push("commit.message_pattern is not a valid regular expression");
4806
4846
  }
4807
4847
  }
4848
+ if (config.review_depth !== void 0 && config.review_depth !== "two-stage" && config.review_depth !== "single") {
4849
+ problems.push(`review_depth "${String(config.review_depth)}" is not one of: two-stage, single`);
4850
+ }
4808
4851
  return problems;
4809
4852
  }
4810
4853
 
4811
4854
  // src/workflows.ts
4855
+ function bundled(skill, ...rules) {
4856
+ const ref = (name) => ({ source: "bundled", name });
4857
+ return { skill: ref(skill), rules: rules.map(ref) };
4858
+ }
4812
4859
  var FLOW_OPTIONS = [
4813
- { id: "standard", label: "\u6807\u51C6\u7814\u53D1", description: "\u9700\u6C42\u8BC4\u5BA1 \u2192 \u8BBE\u8BA1 \u2192 \u5F00\u53D1 \u2192 \u4EA4\u4ED8 \u2192 \u4EE3\u7801\u5BA1\u6838\uFF0C\u542B\u4EA7\u7269\u95E8 + \u786E\u8BA4\u95E8" },
4814
- { id: "agile", label: "\u654F\u6377\u8F7B\u91CF", description: "\u9700\u6C42 \u2192 \u5F00\u53D1 \u2192 \u4EA4\u4ED8 \u2192 \u5BA1\u67E5\uFF0C\u56DB\u9636\u6BB5\u3001\u5C11\u4EA7\u7269" },
4815
- { id: "minimal", label: "\u7EAF\u4EE3\u7801", description: "\u5F00\u53D1 \u2192 \u4EA4\u4ED8\uFF0C\u4E24\u9636\u6BB5\uFF0C\u53EA\u7559\u63D0\u4EA4\u95E8\u7981" }
4860
+ { id: "standard", label: "\u5B8C\u6574\u7814\u53D1", description: "\u65B0\u529F\u80FD\u3001\u67B6\u6784\u6216\u8DE8\u6A21\u5757\u6539\u52A8\u3001\u9AD8\u98CE\u9669\u4EFB\u52A1\uFF1A\u9700\u6C42\u786E\u8BA4 \u2192 \u65B9\u6848\u786E\u8BA4 \u2192 \u5B9E\u73B0 \u2192 \u9A8C\u8BC1 \u2192 \u5BA1\u6838 \u2192 \u63D0\u4EA4" },
4861
+ { id: "agile", label: "\u65E5\u5E38\u8FED\u4EE3", description: "\u76EE\u6807\u660E\u786E\u7684\u5E38\u89C4\u529F\u80FD\u4E0E\u7F3A\u9677\u4FEE\u590D\uFF1A\u76EE\u6807\u4E0E\u9A8C\u6536 \u2192 \u5B9E\u73B0 \u2192 \u9A8C\u6536\u4E0E\u5BA1\u67E5 \u2192 \u63D0\u4EA4" },
4862
+ { id: "minimal", label: "\u5FEB\u901F\u4FEE\u6539", description: "\u5C40\u90E8\u3001\u4F4E\u98CE\u9669\u3001\u65B9\u6848\u660E\u786E\u7684\u6539\u52A8\uFF1A\u4FEE\u6539 \u2192 \u68C0\u67E5\u4E0E\u63D0\u4EA4" }
4816
4863
  ];
4817
4864
  var STANDARD = {
4818
4865
  stages: ["\u9700\u6C42\u8BC4\u5BA1", "\u8BBE\u8BA1", "\u5F00\u53D1", "\u4EA4\u4ED8", "\u4EE3\u7801\u5BA1\u6838", "\u5B8C\u6210"],
@@ -4838,11 +4885,16 @@ var STANDARD = {
4838
4885
  },
4839
4886
  high_risk_requires_verification: true,
4840
4887
  stage_bindings: {
4841
- "\u9700\u6C42\u8BC4\u5BA1": { skills: ["requirement-analysis"], rules: ["security-redlines"] },
4842
- "\u8BBE\u8BA1": { skills: ["solution-design"] },
4843
- "\u5F00\u53D1": { skills: ["code-implement"], rules: ["coding-conventions"] },
4844
- "\u4EA4\u4ED8": { skills: ["code-verify"], rules: ["coding-conventions"] },
4845
- "\u4EE3\u7801\u5BA1\u6838": { skills: ["code-review", "code-commit"], rules: ["security-redlines", "commit-conventions"] }
4888
+ "\u9700\u6C42\u8BC4\u5BA1": { skills: [bundled("requirement-analysis", "security-redlines")] },
4889
+ "\u8BBE\u8BA1": { skills: [bundled("solution-design")] },
4890
+ "\u5F00\u53D1": { skills: [bundled("code-implement", "coding-conventions", "security-redlines")] },
4891
+ "\u4EA4\u4ED8": { skills: [bundled("code-verify")] },
4892
+ "\u4EE3\u7801\u5BA1\u6838": {
4893
+ skills: [
4894
+ bundled("code-review", "coding-conventions", "security-redlines"),
4895
+ bundled("code-commit", "commit-conventions")
4896
+ ]
4897
+ }
4846
4898
  }
4847
4899
  };
4848
4900
  var AGILE = {
@@ -4854,21 +4906,32 @@ var AGILE = {
4854
4906
  { from: "\u4EA4\u4ED8", to: "\u5BA1\u67E5", requires: [] }
4855
4907
  ],
4856
4908
  artifacts: [
4857
- { stage: "\u9700\u6C42", id: "requirement", name: "\u9700\u6C42\u8BF4\u660E", fields: ["scope"] }
4909
+ { stage: "\u9700\u6C42", id: "requirement", name: "\u9700\u6C42\u8BF4\u660E", fields: ["scope"] },
4910
+ // The 审查 stage binds code-review, whose instructions write this artifact with
4911
+ // `record artifact=review`. Declaring it here keeps that binding executable:
4912
+ // the engine refuses an undeclared artifact, so a bound skill pointing at one
4913
+ // would be a contract the preset itself made impossible to satisfy.
4914
+ { stage: "\u5BA1\u67E5", id: "review", name: "\u8BC4\u5BA1\u8BB0\u5F55", fields: ["conclusion", "issues"] }
4858
4915
  ],
4859
4916
  commit: {
4917
+ // `item` policy emits two label shapes: `T<n>` for a mid-flow item commit and
4918
+ // `TASK` for the closing commit at the checkpoint. The pattern below accepts
4919
+ // BOTH, so the label the engine hands the model always satisfies the preset's
4920
+ // own rule. Previously it accepted only `T\d+` while the checkpoint stage
4921
+ // returned `TASK`, which made the closing commit impossible to phrase: the
4922
+ // status line and the validator disagreed about the contract.
4860
4923
  policy: "item",
4861
- message_pattern: "^\u3010(\\S+)\u3011\u3010T\\d+\u3011.+",
4862
- message_hint: "\u3010<task_id>\u3011\u3010T1\u3011\u8BF4\u660E \u2014\u2014 \u7B2C\u4E00\u6BB5\u586B\u672C\u4EFB\u52A1 id",
4924
+ message_pattern: "^\u3010(\\S+)\u3011\u3010(?:TASK|T\\d+)\u3011.+",
4925
+ message_hint: "\u3010<task_id>\u3011\u3010TASK/T1\u3011\u8BF4\u660E \u2014\u2014 \u7B2C\u4E00\u6BB5\u586B\u672C\u4EFB\u52A1 id\uFF1B\u5B9E\u65BD\u9879\u63D0\u4EA4\u7528 T1\u3001T2\uFF0C\u6536\u5C3E\u63D0\u4EA4\u7528 TASK",
4863
4926
  checkpoints: ["\u4EA4\u4ED8"],
4864
4927
  file_scope: true
4865
4928
  },
4866
4929
  high_risk_requires_verification: false,
4867
4930
  stage_bindings: {
4868
- "\u9700\u6C42": { skills: ["requirement-analysis"] },
4869
- "\u5F00\u53D1": { skills: ["code-implement"], rules: ["coding-conventions"] },
4870
- "\u4EA4\u4ED8": { skills: ["code-verify", "code-commit"], rules: ["commit-conventions"] },
4871
- "\u5BA1\u67E5": { skills: ["code-review"] }
4931
+ "\u9700\u6C42": { skills: [bundled("requirement-analysis", "security-redlines")] },
4932
+ "\u5F00\u53D1": { skills: [bundled("code-implement", "coding-conventions", "security-redlines")] },
4933
+ "\u4EA4\u4ED8": { skills: [bundled("code-verify"), bundled("code-commit", "commit-conventions")] },
4934
+ "\u5BA1\u67E5": { skills: [bundled("code-review", "coding-conventions", "security-redlines")] }
4872
4935
  }
4873
4936
  };
4874
4937
  var MINIMAL = {
@@ -4886,9 +4949,14 @@ var MINIMAL = {
4886
4949
  file_scope: false
4887
4950
  },
4888
4951
  high_risk_requires_verification: false,
4952
+ // A fast-change flow checks each change once, not twice under two headings a
4953
+ // short change rarely distinguishes. The item still has to be reviewed — what
4954
+ // drops is the duplicated verdict, which is where the weight actually was: this
4955
+ // flow used to carry the same per-item audit burden as the full one.
4956
+ review_depth: "single",
4889
4957
  stage_bindings: {
4890
- "\u5F00\u53D1": { skills: ["code-implement"] },
4891
- "\u4EA4\u4ED8": { skills: ["code-commit"], rules: ["commit-conventions"] }
4958
+ "\u5F00\u53D1": { skills: [bundled("code-implement", "coding-conventions", "security-redlines")] },
4959
+ "\u4EA4\u4ED8": { skills: [bundled("code-commit", "commit-conventions")] }
4892
4960
  }
4893
4961
  };
4894
4962
  function deriveCapabilities(config) {
@@ -4904,9 +4972,16 @@ function preset(id, version, label2, description, config) {
4904
4972
  return { id, version, label: label2, description, capabilities: deriveCapabilities(config), config };
4905
4973
  }
4906
4974
  var FLOW_PRESETS = {
4907
- standard: preset("standard", 2, "\u6807\u51C6\u7814\u53D1", "\u9700\u6C42\u8BC4\u5BA1 \u2192 \u8BBE\u8BA1 \u2192 \u5F00\u53D1 \u2192 \u4EA4\u4ED8 \u2192 \u4EE3\u7801\u5BA1\u6838\uFF0C\u5BA1\u6838\u540E\u63D0\u4EA4", STANDARD),
4908
- agile: preset("agile", 1, "\u654F\u6377\u8F7B\u91CF", "\u9700\u6C42 \u2192 \u5F00\u53D1 \u2192 \u4EA4\u4ED8 \u2192 \u5BA1\u67E5\uFF0C\u56DB\u9636\u6BB5\u3001\u5C11\u4EA7\u7269", AGILE),
4909
- minimal: preset("minimal", 1, "\u7EAF\u4EE3\u7801", "\u5F00\u53D1 \u2192 \u4EA4\u4ED8\uFF0C\u4E24\u9636\u6BB5\uFF0C\u53EA\u7559\u63D0\u4EA4\u95E8\u7981", MINIMAL)
4975
+ standard: preset("standard", 2, "\u5B8C\u6574\u7814\u53D1", "\u65B0\u529F\u80FD\u3001\u67B6\u6784\u6216\u8DE8\u6A21\u5757\u6539\u52A8\u3001\u9AD8\u98CE\u9669\u4EFB\u52A1\uFF1A\u9700\u6C42\u786E\u8BA4 \u2192 \u65B9\u6848\u786E\u8BA4 \u2192 \u5B9E\u73B0 \u2192 \u9A8C\u8BC1 \u2192 \u5BA1\u6838 \u2192 \u63D0\u4EA4", STANDARD),
4976
+ // Version 2: the closing-commit label shape was unified with the message pattern,
4977
+ // and the `review` artifact the bound code-review skill writes was declared. Tasks
4978
+ // created before this keep their frozen version 1 config and are unaffected.
4979
+ agile: preset("agile", 2, "\u65E5\u5E38\u8FED\u4EE3", "\u76EE\u6807\u660E\u786E\u7684\u5E38\u89C4\u529F\u80FD\u4E0E\u7F3A\u9677\u4FEE\u590D\uFF1A\u76EE\u6807\u4E0E\u9A8C\u6536 \u2192 \u5B9E\u73B0 \u2192 \u9A8C\u6536\u4E0E\u5BA1\u67E5 \u2192 \u63D0\u4EA4", AGILE),
4980
+ // Version 2: per-item review depth is declared as `single` instead of inheriting
4981
+ // the full flow's two verdicts per item. Tasks created before this keep their
4982
+ // frozen version 1 config, which has no `review_depth` and therefore reads as
4983
+ // two-stage exactly as it behaved.
4984
+ minimal: preset("minimal", 2, "\u5FEB\u901F\u4FEE\u6539", "\u5C40\u90E8\u3001\u4F4E\u98CE\u9669\u3001\u65B9\u6848\u660E\u786E\u7684\u6539\u52A8\uFF1A\u4FEE\u6539 \u2192 \u68C0\u67E5\u4E0E\u63D0\u4EA4\uFF0C\u6BCF\u9879\u4E00\u6B21\u68C0\u67E5", MINIMAL)
4910
4985
  };
4911
4986
  function dedupe(names) {
4912
4987
  return [...new Set(names)];
@@ -4917,11 +4992,18 @@ function mergeBindings(base, override) {
4917
4992
  const merged = { ...base.stage_bindings };
4918
4993
  for (const [stage, addition] of Object.entries(override)) {
4919
4994
  const existing = merged[stage];
4920
- const skills = dedupe([...existing?.skills ?? [], ...addition.skills ?? []]);
4921
- const rules = dedupe([...existing?.rules ?? [], ...addition.rules ?? []]);
4995
+ const skills = [...existing?.skills ?? []];
4996
+ const seen = new Set(skills.map((skill) => formatResourceRef(skill.skill)));
4997
+ for (const skill of addition.skills ?? []) {
4998
+ const key = formatResourceRef(skill.skill);
4999
+ if (seen.has(key)) continue;
5000
+ seen.add(key);
5001
+ skills.push(skill);
5002
+ }
5003
+ const legacy = dedupe([...existing?.legacy_rules ?? [], ...addition.legacy_rules ?? []]);
4922
5004
  const binding = {};
4923
5005
  if (skills.length > 0) binding.skills = skills;
4924
- if (rules.length > 0) binding.rules = rules;
5006
+ if (legacy.length > 0) binding.legacy_rules = legacy;
4925
5007
  merged[stage] = binding;
4926
5008
  changed = true;
4927
5009
  }
@@ -4937,24 +5019,6 @@ function resolveFlow(flow, override) {
4937
5019
  }
4938
5020
 
4939
5021
  // src/client/shared.ts
4940
- function sourceLabel(source) {
4941
- switch (source) {
4942
- case "bundled":
4943
- return "\u5185\u7F6E";
4944
- case "project":
4945
- case "project-dsh":
4946
- case "project-agents":
4947
- return "\u9879\u76EE";
4948
- case "user":
4949
- case "user-dsh":
4950
- case "user-agents":
4951
- return "\u7528\u6237";
4952
- case "custom":
4953
- return "\u81EA\u5B9A\u4E49";
4954
- default:
4955
- return source;
4956
- }
4957
- }
4958
5022
  function describeError(error) {
4959
5023
  if (error instanceof Error) return error.message;
4960
5024
  if (typeof error === "string") return error;
@@ -4963,24 +5027,20 @@ function describeError(error) {
4963
5027
 
4964
5028
  // src/client/TaskEngineSection.tsx
4965
5029
  var import_jsx_runtime = require("react/jsx-runtime");
5030
+ function refFromText(text2) {
5031
+ return parseResourceRef(text2) ?? { source: "bundled", name: text2 };
5032
+ }
4966
5033
  function resolvedConfig(flow, stageBindings) {
4967
5034
  const resolved = resolveFlow(flow, { stage_bindings: stageBindings });
4968
5035
  return resolved.ok ? resolved.config : FLOW_PRESETS.standard.config;
4969
5036
  }
4970
- var cardV = {
4971
- display: "flex",
4972
- flexDirection: "column",
4973
- gap: 8,
4974
- border: `1px solid ${border}`,
4975
- borderRadius: 8,
4976
- padding: "10px 12px"
4977
- };
4978
5037
  function TaskEngineSection(props) {
4979
5038
  const { remote, workspace } = props;
4980
5039
  const [flow, setFlow] = (0, import_react.useState)("standard");
4981
5040
  const [stageBindings, setStageBindings] = (0, import_react.useState)({});
4982
5041
  const [source, setSource] = (0, import_react.useState)("default");
4983
5042
  const [savedAt, setSavedAt] = (0, import_react.useState)("");
5043
+ const [dirty, setDirty] = (0, import_react.useState)(false);
4984
5044
  const [loadError, setLoadError] = (0, import_react.useState)("");
4985
5045
  const [saving, setSaving] = (0, import_react.useState)(false);
4986
5046
  const [loading, setLoading] = (0, import_react.useState)(true);
@@ -5035,8 +5095,12 @@ function TaskEngineSection(props) {
5035
5095
  [flow, stageBindings]
5036
5096
  );
5037
5097
  const selectFlow = (id) => {
5098
+ if (id === flow) return;
5099
+ if (dirty && !window.confirm("\u5F53\u524D\u6D41\u7A0B\u914D\u7F6E\u6709\u672A\u4FDD\u5B58\u7684\u4FEE\u6539\uFF0C\u5207\u6362\u6D41\u7A0B\u4F1A\u4E22\u5F03\u5B83\u4EEC\u3002\u7EE7\u7EED\u5207\u6362\uFF1F")) return;
5038
5100
  setFlow(id);
5039
5101
  setStageBindings({ ...FLOW_PRESETS[id]?.config.stage_bindings ?? {} });
5102
+ setDirty(true);
5103
+ setSavedAt("");
5040
5104
  };
5041
5105
  if (loadError !== "") {
5042
5106
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.wrap, children: [
@@ -5067,7 +5131,7 @@ function TaskEngineSection(props) {
5067
5131
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.sourceText, children: sourceText(source) })
5068
5132
  ] }),
5069
5133
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.field, children: [
5070
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.fieldLabel, children: "\u6D41\u7A0B\u9884\u8BBE" }),
5134
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "\u6D41\u7A0B\u9884\u8BBE" }),
5071
5135
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.flowGrid, children: FLOW_OPTIONS.map((option) => {
5072
5136
  const active = flow === option.id;
5073
5137
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
@@ -5089,11 +5153,13 @@ function TaskEngineSection(props) {
5089
5153
  },
5090
5154
  option.id
5091
5155
  );
5092
- }) }),
5093
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.hint, children: FLOW_OPTIONS.find((option) => option.id === flow)?.description })
5156
+ }) })
5094
5157
  ] }),
5095
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionCard, { icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconBranchOutline16, { size: 16 }), title: "\u6D41\u7A0B\u8282\u70B9", hint: "\u8BE5\u6D41\u7A0B\u9884\u8BBE\u56FA\u5B9A\u7684\u9636\u6BB5\u987A\u5E8F\uFF1B\u6539\u53D8\u6D41\u7A0B\u8BF7\u5207\u6362\u4E0A\u9762\u7684\u9884\u8BBE\u3002", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.chips, children: stages.map((stage) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Pill, { active: stage === (FLOW_PRESETS[flow]?.config.start_stage ?? ""), title: stage === FLOW_PRESETS[flow]?.config.start_stage ? "\u8D77\u59CB\u9636\u6BB5" : void 0, children: stage }, stage)) }) }),
5096
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionCard, { icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconSettingsOutline16, { size: 16 }), title: "\u9636\u6BB5\u6280\u80FD / \u89C4\u5219", hint: "\u9009\u4E00\u4E2A\u8282\u70B9\uFF0C\u52FE\u9009\u5B83\u300C\u7528\u4EC0\u4E48 skill\uFF08\u6280\u80FD\uFF09\u300D\u548C\u300C\u5B88\u54EA\u6761 rule\uFF08\u89C4\u5219\uFF09\u300D\u3002\u6D41\u7A0B\u81EA\u5E26\u7684\u9ED8\u8BA4\u7ED1\u5B9A\u4E0D\u4F1A\u56E0\u53D6\u6D88\u52FE\u9009\u800C\u79FB\u9664\uFF08\u53EA\u4F1A\u4FDD\u7559\u5E76\u8FFD\u52A0\u4F60\u65B0\u52FE\u9009\u7684\uFF09\uFF1B\u8981\u65B0\u5EFA\u6216\u6539\u6B63\u6587\uFF0C\u5207\u5230\u4E0A\u65B9\u7684\u300C\u6280\u80FD\u300D/\u300C\u89C4\u5219\u300D\u6807\u7B7E\u9875\u3002", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BindingEditor, { stages, stageBindings, setStageBindings, skills, rules }) }),
5158
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionCard, { icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconSettingsOutline16, { size: 16 }), title: "\u9636\u6BB5\u6280\u80FD / \u89C4\u5219", hint: "\u9009\u62E9\u8282\u70B9\uFF0C\u518D\u52FE\u9009\u9644\u52A0\u6280\u80FD\u4E0E\u89C4\u5219\u3002\u9884\u8BBE\u9ED8\u8BA4\u7ED1\u5B9A\u56FA\u5B9A\u4FDD\u7559\uFF1B\u65B0\u5EFA\u6216\u7F16\u8F91\u5185\u5BB9\u8BF7\u524D\u5F80\u4E0A\u65B9\u201C\u6280\u80FD\u201D\u6216\u201C\u89C4\u5219\u201D\u9875\u3002", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BindingEditor, { stages, defaults: (FLOW_PRESETS[flow] ?? FLOW_PRESETS.standard).config.stage_bindings ?? {}, stageBindings, setStageBindings: (next) => {
5159
+ setStageBindings(next);
5160
+ setDirty(true);
5161
+ setSavedAt("");
5162
+ }, skills, rules }) }),
5097
5163
  problems.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.problems, children: [
5098
5164
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: styles.problemsTitle, children: [
5099
5165
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.StateDot, { state: "error" }),
@@ -5117,10 +5183,13 @@ function TaskEngineSection(props) {
5117
5183
  onClick: () => {
5118
5184
  setSaving(true);
5119
5185
  void save(remote, flow, stageBindings, workspace, setSavedAt, setSource).then((ok) => {
5120
- if (ok) setConfigProblems([]);
5186
+ if (ok) {
5187
+ setConfigProblems([]);
5188
+ setDirty(false);
5189
+ }
5121
5190
  }).finally(() => setSaving(false));
5122
5191
  },
5123
- children: saving ? "\u6B63\u5728\u4FDD\u5B58\u2026" : "\u4FDD\u5B58\u5230 .dsh/eng.json"
5192
+ children: saving ? "\u6B63\u5728\u4FDD\u5B58\u2026" : dirty ? "\u4FDD\u5B58\u5230 .dsh/eng.json" : "\u5DF2\u4FDD\u5B58 \xB7 \u65E0\u9700\u4FDD\u5B58"
5124
5193
  }
5125
5194
  ),
5126
5195
  savedAt !== "" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: styles.okText, children: [
@@ -5147,8 +5216,10 @@ function SectionCard({ icon, title, hint, children }) {
5147
5216
  {
5148
5217
  icon,
5149
5218
  title,
5219
+ titleClassName: "te-disclosure-title",
5150
5220
  open,
5151
5221
  expandable: true,
5222
+ expandOnRowClick: true,
5152
5223
  onToggle: () => {
5153
5224
  setOpen(!open);
5154
5225
  },
@@ -5160,63 +5231,113 @@ function SectionCard({ icon, title, hint, children }) {
5160
5231
  }
5161
5232
  ) });
5162
5233
  }
5163
- function BindingEditor({ stages, stageBindings, setStageBindings, skills, rules }) {
5234
+ function BindingEditor({ stages, defaults, stageBindings, setStageBindings, skills, rules }) {
5164
5235
  const [stage, setStage] = (0, import_react.useState)(stages[0] ?? "");
5236
+ const [expanded, setExpanded] = (0, import_react.useState)(null);
5165
5237
  const currentStage = stages.includes(stage) ? stage : stages[0] ?? "";
5166
- const toggleSkill = (skill) => {
5167
- if (currentStage === "") return;
5168
- const current = stageBindings[currentStage]?.skills ?? [];
5169
- const next = current.includes(skill) ? current.filter((s) => s !== skill) : [...current, skill].sort();
5170
- setStageBindings({ ...stageBindings, [currentStage]: { ...stageBindings[currentStage] ?? {}, skills: next } });
5238
+ const bindingFor = (name) => stageBindings[name] ?? {};
5239
+ const presetSkills = (name) => defaults[name]?.skills ?? [];
5240
+ const isPresetSkill = (name, raw) => presetSkills(name).some((entry) => formatResourceRef(entry.skill) === raw);
5241
+ const setSkills = (name, next) => {
5242
+ setStageBindings({ ...stageBindings, [name]: { ...bindingFor(name), skills: next } });
5243
+ };
5244
+ const toggleSkill = (raw) => {
5245
+ if (currentStage === "" || isPresetSkill(currentStage, raw)) return;
5246
+ const current = bindingFor(currentStage).skills ?? [];
5247
+ const next = current.some((entry) => formatResourceRef(entry.skill) === raw) ? current.filter((entry) => formatResourceRef(entry.skill) !== raw) : [...current, { skill: refFromText(raw), rules: [] }].sort((a, b) => formatResourceRef(a.skill).localeCompare(formatResourceRef(b.skill)));
5248
+ setSkills(currentStage, next);
5171
5249
  };
5172
- const toggleRule = (rule) => {
5250
+ const toggleRule = (skillRaw, ruleRaw) => {
5173
5251
  if (currentStage === "") return;
5174
- const current = stageBindings[currentStage]?.rules ?? [];
5175
- const next = current.includes(rule) ? current.filter((s) => s !== rule) : [...current, rule].sort();
5176
- setStageBindings({ ...stageBindings, [currentStage]: { ...stageBindings[currentStage] ?? {}, rules: next } });
5252
+ const current = bindingFor(currentStage).skills ?? [];
5253
+ setSkills(currentStage, current.map((entry) => {
5254
+ if (formatResourceRef(entry.skill) !== skillRaw) return entry;
5255
+ const has = entry.rules.some((rule) => formatResourceRef(rule) === ruleRaw);
5256
+ const next = has ? entry.rules.filter((rule) => formatResourceRef(rule) !== ruleRaw) : [...entry.rules, refFromText(ruleRaw)].sort((a, b) => formatResourceRef(a).localeCompare(formatResourceRef(b)));
5257
+ return { ...entry, rules: next };
5258
+ }));
5177
5259
  };
5178
5260
  if (currentStage === "") {
5179
5261
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.muted, children: "\u5F53\u524D\u6D41\u7A0B\u6CA1\u6709\u8282\u70B9\u3002" });
5180
5262
  }
5181
- const boundSkills = stageBindings[currentStage]?.skills ?? [];
5182
- const boundRules = stageBindings[currentStage]?.rules ?? [];
5263
+ const boundSkills = bindingFor(currentStage).skills ?? [];
5264
+ const unassigned = bindingFor(currentStage).legacy_rules ?? [];
5265
+ const presetCount = presetSkills(currentStage).length;
5183
5266
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.section, children: [
5184
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.hint, children: "\u7ED1\u5B9A\u6309\u540D\u79F0\u751F\u6548\uFF1B\u540C\u540D\u8D44\u6E90\u53EF\u5728\u8D44\u6E90\u7BA1\u7406\u4E2D\u6309\u9879\u76EE\uFF0F\u4E2A\u4EBA\u5206\u522B\u67E5\u770B\u3002\u5B8C\u6210\u8282\u70B9\u7684\u6280\u80FD\u5FC5\u987B\u5728\u8FDB\u5165\u5B8C\u6210\u524D\u6267\u884C\uFF0C\u6D4B\u8BD5\u6280\u80FD\u5EFA\u8BAE\u653E\u5728\u4EA4\u4ED8\u8282\u70B9\u3002" }),
5267
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.hint, children: "\u8282\u70B9\u53EA\u9009\u62E9\u6280\u80FD\uFF1B\u89C4\u5219\u5C5E\u4E8E\u6280\u80FD\u2014\u2014\u70B9\u5F00\u4E00\u4E2A\u6280\u80FD\u5C31\u80FD\u770B\u5230\u5B83\u5B8C\u6574\u7684\u89C4\u5219\u5217\u8868\uFF0C\u6302\u5230\u4EFB\u4F55\u8282\u70B9\u90FD\u662F\u540C\u4E00\u5957\u89C4\u5219\u3002 \u9700\u8981\u540C\u4E00\u6280\u80FD\u7528\u4E0D\u540C\u89C4\u5219\u65F6\uFF0C\u590D\u5236\u6210\u53E6\u4E00\u4E2A\u6280\u80FD\u518D\u5206\u522B\u914D\u7F6E\u3002" }),
5185
5268
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.field, children: [
5186
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.fieldLabel, children: "\u9009\u62E9\u8282\u70B9\uFF08\u6539\u54EA\u4E2A\u8282\u70B9\u7684\u6302\u8F7D\uFF09" }),
5269
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.bindingLabel, children: "\u9009\u62E9\u8282\u70B9" }),
5187
5270
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.chips, children: stages.map((s) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Pill, { active: s === currentStage, onClick: () => {
5188
5271
  setStage(s);
5189
5272
  }, children: s }, s)) })
5190
5273
  ] }),
5191
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: cardV, children: [
5192
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.bindingStage, children: [
5193
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.fieldLabel, children: "skill\uFF08\u8FD9\u4E2A\u8282\u70B9\u505A\u4EC0\u4E48\uFF09" }),
5194
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.chips, children: skills.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.sourceBadge, children: "\u6682\u65E0\u53EF\u7528 skill \u2014\u2014 \u5207\u5230\u4E0A\u65B9\u300C\u6280\u80FD\u300D\u6807\u7B7E\u9875\u65B0\u5EFA" }) : skills.filter((sk, index, all) => all.findIndex((other) => other.name === sk.name) === index).map((sk) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_dsh_client_ui_primitives.Pill, { active: boundSkills.includes(sk.name), onClick: () => {
5195
- toggleSkill(sk.name);
5196
- }, title: `${sk.description}\uFF08${sourceLabel(sk.source)}\uFF09`, children: [
5197
- sk.name,
5198
- sk.source !== "bundled" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: styles.sourceBadge, children: [
5199
- "\uFF08",
5200
- sourceLabel(sk.source),
5201
- "\uFF09"
5202
- ] }) : null
5203
- ] }, sk.name)) })
5204
- ] }),
5205
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.bindingStage, children: [
5206
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.fieldLabel, children: "rule\uFF08\u8FD9\u4E2A\u8282\u70B9\u5B88\u4EC0\u4E48\uFF09" }),
5207
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.chips, children: rules.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.sourceBadge, children: "\u6682\u65E0\u53EF\u7528 rule \u2014\u2014 \u5207\u5230\u4E0A\u65B9\u300C\u89C4\u5219\u300D\u6807\u7B7E\u9875\u65B0\u5EFA" }) : rules.filter((rule, index, all) => all.findIndex((other) => other.name === rule.name) === index).map((rule) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_dsh_client_ui_primitives.Pill, { active: boundRules.includes(rule.name), onClick: () => {
5208
- toggleRule(rule.name);
5209
- }, title: `\u6765\u6E90\uFF1A${sourceLabel(rule.source)}`, children: [
5210
- rule.name,
5211
- rule.source !== "bundled" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: styles.sourceBadge, children: [
5212
- "\uFF08",
5213
- sourceLabel(rule.source),
5214
- "\uFF09"
5215
- ] }) : null
5216
- ] }, rule.name)) })
5274
+ unassigned.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "te-alert", children: [
5275
+ "\u4EE5\u4E0B\u89C4\u5219\u5C5E\u4E8E",
5276
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "\u8282\u70B9" }),
5277
+ "\u800C\u4E0D\u662F\u4EFB\u4F55\u6280\u80FD\uFF08\u65E7\u914D\u7F6E\uFF09\uFF1A",
5278
+ unassigned.join("\u3001"),
5279
+ "\u3002 \u5B83\u4EEC\u4ECD\u7136\u751F\u6548\uFF0C\u4F46\u9700\u8981\u6307\u5B9A\u5F52\u5C5E\u2014\u2014\u628A\u6BCF\u6761\u89C4\u5219\u52A0\u5230\u4F60\u5E0C\u671B\u627F\u8F7D\u5B83\u7684\u6280\u80FD\u4E0B\uFF0C\u7136\u540E\u4ECE\u8FD9\u91CC\u79FB\u9664\uFF1B \u82E5\u540C\u4E00\u6280\u80FD\u5728\u4E0D\u540C\u8282\u70B9\u9700\u8981\u4E0D\u540C\u89C4\u5219\uFF0C\u8BF7\u590D\u5236\u8BE5\u6280\u80FD\u518D\u5206\u522B\u914D\u7F6E\u3002"
5280
+ ] }) : null,
5281
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "te-binding-grid", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "te-binding-picker", "aria-label": "\u6280\u80FD\u7ED1\u5B9A", children: [
5282
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "te-binding-heading", children: [
5283
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h4", { children: "\u6280\u80FD" }),
5284
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "te-binding-count", children: [
5285
+ "\u5DF2\u9009 ",
5286
+ boundSkills.length,
5287
+ presetCount > 0 ? `\uFF08\u542B ${presetCount} \u9879\u9884\u8BBE\uFF09` : ""
5288
+ ] })
5217
5289
  ] }),
5218
- boundSkills.length === 0 && boundRules.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.hint, children: "\u8FD9\u4E2A\u8282\u70B9\u8FD8\u6CA1\u6302\u4EFB\u4F55 skill / rule \u2014\u2014 \u70B9\u4E0A\u9762\u7684\u540D\u79F0\u52FE\u9009\u3002" }) : null
5219
- ] })
5290
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "te-binding-list", role: "group", "aria-label": "\u6280\u80FD\u5217\u8868", tabIndex: 0, children: [
5291
+ skills.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "te-binding-empty", children: "\u6682\u65E0\u6280\u80FD\uFF0C\u8BF7\u5728\u4E0A\u65B9\u201C\u6280\u80FD\u201D\u9875\u6DFB\u52A0\u3002" }) : null,
5292
+ skills.map((entry) => {
5293
+ const raw = formatResourceRef(entry.ref);
5294
+ const bound = boundSkills.find((candidate) => formatResourceRef(candidate.skill) === raw);
5295
+ const preset2 = isPresetSkill(currentStage, raw);
5296
+ const open = expanded === raw;
5297
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `te-binding-option${bound !== void 0 ? " is-selected" : ""}`, children: [
5298
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
5299
+ "input",
5300
+ {
5301
+ type: "checkbox",
5302
+ "aria-label": raw,
5303
+ checked: bound !== void 0,
5304
+ disabled: preset2,
5305
+ onChange: () => toggleSkill(raw)
5306
+ }
5307
+ ),
5308
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "te-binding-detail", children: [
5309
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "te-binding-name", children: [
5310
+ entry.name,
5311
+ preset2 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "te-source-badge", children: "\u9884\u8BBE" }) : null
5312
+ ] }),
5313
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "te-binding-meta", children: [
5314
+ entry.sourceLabel,
5315
+ " \xB7 ",
5316
+ bound === void 0 ? "\u672A\u7ED1\u5B9A" : `${bound.rules.length} \u6761\u89C4\u5219`
5317
+ ] }),
5318
+ bound !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "te-link", onClick: () => setExpanded(open ? null : raw), children: open ? "\u6536\u8D77\u89C4\u5219" : "\u89C4\u5219\u8BBE\u7F6E" }) : null,
5319
+ open && bound !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "te-rule-list", children: [
5320
+ rules.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "te-binding-meta", children: "\u89C4\u5219\u5E93\u4E3A\u7A7A\u3002" }) : null,
5321
+ rules.map((rule) => {
5322
+ const ruleRaw = formatResourceRef(rule.ref);
5323
+ const on = bound.rules.some((candidate) => formatResourceRef(candidate) === ruleRaw);
5324
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "te-binding-filter", children: [
5325
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", checked: on, onChange: () => toggleRule(raw, ruleRaw) }),
5326
+ " ",
5327
+ rule.name,
5328
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "te-binding-meta", children: [
5329
+ "\uFF08",
5330
+ rule.sourceLabel,
5331
+ "\uFF09"
5332
+ ] })
5333
+ ] }, ruleRaw);
5334
+ })
5335
+ ] }) : null
5336
+ ] })
5337
+ ] }, raw);
5338
+ })
5339
+ ] })
5340
+ ] }) }, currentStage)
5220
5341
  ] });
5221
5342
  }
5222
5343
  async function save(remote, flow, stageBindings, workspace, setSavedAt, setSource) {