diff --git a/CHANGELOG.md b/CHANGELOG.md index 38b10b9..319c46a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ## Unreleased +- 查询专业软件是否可用时不再误提交真实任务;ANSYS 静力分析会要求使用几何中已有的 Named Selection,并在输入缺少命名选择时返回明确错误。 + - Origin 专业软件任务现在会在原 Windows Node 上保留可继续加工的工程;后续调整直接打开上一版工程,过程只提供轻量预览,工程和其他大文件仅在明确要求下载或交付时才传回并登记为正式产物。 - 右侧工作区改为“文件 / 软件作业”双面板,标准页面和 embed 模式保持一致,折叠后仍可查看作业数量与完成提示;文件面板新增“当前对话 / 全部文件”切换、当前目录搜索、排序和修改时间展示。 diff --git a/software-contracts/ansys.mechanical.static_structural.v1.json b/software-contracts/ansys.mechanical.static_structural.v1.json index a60563c..2bd2361 100644 --- a/software-contracts/ansys.mechanical.static_structural.v1.json +++ b/software-contracts/ansys.mechanical.static_structural.v1.json @@ -179,7 +179,8 @@ "type": "string", "minLength": 1, "maxLength": 128, - "pattern": "^[^/\\\\:*?\"<>|]+$" + "pattern": "^[^/\\\\:*?\"<>|]+$", + "description": "Name of an existing Named Selection imported from the input geometry. Never invent or infer this name; ask the user when the geometry does not provide a known name." }, "vector3": { "type": "array", diff --git a/tests/test_ansys_adapter.py b/tests/test_ansys_adapter.py index 2436284..3f26219 100644 --- a/tests/test_ansys_adapter.py +++ b/tests/test_ansys_adapter.py @@ -97,6 +97,12 @@ class AnsysContractTests(unittest.TestCase): with self.assertRaises(SoftwareContractError): contract.normalize_request(request) + def test_contract_requires_existing_named_selection_names(self) -> None: + contract = get_contract("ansys.mechanical.static_structural@v1") + schema = contract.request_schema["$defs"]["named_selection"] + self.assertIn("existing Named Selection", schema["description"]) + self.assertIn("Never invent", schema["description"]) + class AnsysWorkerTests(unittest.TestCase): @classmethod @@ -171,6 +177,15 @@ class AnsysWorkerTests(unittest.TestCase): with self.assertRaisesRegex(RuntimeError, "NAMED_SELECTION_NOT_FOUND:missing"): self.worker._named_selections(model, ["missing"]) + def test_named_selection_resolution_reports_empty_mechanical_container(self) -> None: + model = mock.Mock() + model.NamedSelections = None + with self.assertRaisesRegex( + RuntimeError, + "NAMED_SELECTION_NOT_FOUND:fixed_face,load_face", + ): + self.worker._named_selections(model, ["fixed_face", "load_face"]) + def test_acceptance_entry_bypasses_gate_without_enabling_scheduled_execution(self) -> None: with tempfile.TemporaryDirectory() as temporary: job_dir = Path(temporary) diff --git a/tests/test_software_job_tools.py b/tests/test_software_job_tools.py index 1de10b3..13bf46d 100644 --- a/tests/test_software_job_tools.py +++ b/tests/test_software_job_tools.py @@ -114,6 +114,11 @@ class SoftwareJobToolTests(unittest.TestCase): ) self.assertIn("terminal action for the current run", SoftwareJobSubmitTool.description) self.assertIn("automatically", SoftwareJobSubmitTool.description) + self.assertIn("never submit a job merely to check", SoftwareJobSubmitTool.description) + self.assertIn( + "checking availability must not submit", + SoftwareCapabilityListTool.description, + ) def test_submit_can_request_automatic_analysis(self): artifact_id = uuid4() diff --git a/tools/software_jobs.py b/tools/software_jobs.py index 11daa9d..ec786ae 100644 --- a/tools/software_jobs.py +++ b/tools/software_jobs.py @@ -5,8 +5,8 @@ import json from uuid import UUID, uuid4 from core.software_contracts import ( - get_contracts, get_contract, + get_contracts, node_available_slots, supported_capabilities, ) @@ -44,7 +44,11 @@ class _SoftwareJobTool(Tool): class SoftwareCapabilityListTool(_SoftwareJobTool): name = "software_capability_list" - description = "List professional software capabilities available through managed Windows nodes." + description = ( + "List professional software capabilities available through managed Windows nodes. " + "Use this tool to answer whether software is available; checking availability must " + "not submit a software job." + ) parameters = {"type": "object", "properties": {}, "additionalProperties": False} def execute(self) -> str: @@ -68,7 +72,9 @@ class SoftwareJobSubmitTool(_SoftwareJobTool): name = "software_job_submit" description = ( "Submit a managed professional-software job using registered artifacts and a " - "capability contract. Call register_artifact first for workspace files. A successful " + "capability contract only when the user requested actual software execution; never " + "submit a job merely to check whether software is available. Call register_artifact " + "first for workspace files. A successful " "submission is the terminal action for the current run: report the queued job_id once " "and end the turn. Completion delivery and artifact publication happen automatically " "in a later system-managed message." diff --git a/windows-node/adapters/ansys.mechanical.static_structural@v1/worker.py b/windows-node/adapters/ansys.mechanical.static_structural@v1/worker.py index 3630b40..c0abab5 100644 --- a/windows-node/adapters/ansys.mechanical.static_structural@v1/worker.py +++ b/windows-node/adapters/ansys.mechanical.static_structural@v1/worker.py @@ -201,7 +201,9 @@ def _write_result_table(path: Path, rows: list[dict[str, Any]]) -> None: def _named_selections(model: Any, required: list[str]) -> dict[str, Any]: available: dict[str, Any] = {} duplicates: set[str] = set() - for item in model.NamedSelections.Children: + container = model.NamedSelections + children = () if container is None else (container.Children or ()) + for item in children: key = str(item.Name).casefold() if key in available: duplicates.add(key)