@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/.acceptance.mjs +89 -0
- package/.assessment-batch1.mjs +403 -0
- package/.e2e-presets.mjs +195 -0
- package/.freeze-test.mjs +90 -0
- package/.hook-consistency.mjs +60 -0
- package/.p0-test.mjs +11 -4
- package/.preset-test.mjs +2 -2
- package/.revision-test.mjs +99 -0
- package/.workflow-test.mjs +64 -62
- package/defaults/eng.json +45 -6
- package/docs/BRIEF-FOR-REVIEW.md +163 -0
- package/docs/CHANGELOG.md +72 -0
- package/docs/configuration.md +31 -6
- package/hooks/commit-msg +104 -25
- package/lib/client.js +227 -106
- package/lib/client.js.map +2 -3
- package/lib/controller.d.ts +9 -0
- package/lib/controller.js +37 -3
- package/lib/controller.js.map +1 -1
- package/lib/dev-task.js +285 -43
- package/lib/dev-task.js.map +1 -1
- package/lib/engine.d.ts +278 -6
- package/lib/engine.js +264 -13
- package/lib/engine.js.map +1 -1
- package/lib/seed-preset.d.ts +5 -5
- package/lib/seed-preset.js +5 -5
- package/lib/skill-audit.js +5 -1
- package/lib/skill-audit.js.map +1 -1
- package/lib/workflows.js +87 -27
- package/lib/workflows.js.map +1 -1
- package/package.json +9 -3
- package/scripts/verify-package.mjs +3 -3
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:
|
|
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
|
|
4783
|
-
if (typeof skill !== "string" || skill.trim() === "") {
|
|
4784
|
-
problems.push(`stage_bindings "${stage}":
|
|
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
|
-
|
|
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: "\
|
|
4814
|
-
{ id: "agile", label: "\
|
|
4815
|
-
{ id: "minimal", label: "\
|
|
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"
|
|
4842
|
-
"\u8BBE\u8BA1": { skills: ["solution-design"] },
|
|
4843
|
-
"\u5F00\u53D1": { skills: ["code-implement"
|
|
4844
|
-
"\u4EA4\u4ED8": { skills: ["code-verify"]
|
|
4845
|
-
"\u4EE3\u7801\u5BA1\u6838": {
|
|
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\
|
|
4862
|
-
message_hint: "\u3010<task_id>\u3011\
|
|
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"
|
|
4870
|
-
"\u4EA4\u4ED8": { skills: ["code-verify", "code-commit"
|
|
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"
|
|
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, "\
|
|
4908
|
-
|
|
4909
|
-
|
|
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 =
|
|
4921
|
-
const
|
|
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 (
|
|
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)("
|
|
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.
|
|
5096
|
-
|
|
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)
|
|
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
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
setStageBindings({ ...stageBindings, [
|
|
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 = (
|
|
5250
|
+
const toggleRule = (skillRaw, ruleRaw) => {
|
|
5173
5251
|
if (currentStage === "") return;
|
|
5174
|
-
const current =
|
|
5175
|
-
|
|
5176
|
-
|
|
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 =
|
|
5182
|
-
const
|
|
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: "\
|
|
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.
|
|
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", {
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
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
|
-
|
|
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) {
|