python-hwpx-automation 7.3.2__tar.gz → 7.3.3__tar.gz

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.
Files changed (234) hide show
  1. {python_hwpx_automation-7.3.2/src/python_hwpx_automation.egg-info → python_hwpx_automation-7.3.3}/PKG-INFO +3 -3
  2. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/README.md +2 -2
  3. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/pyproject.toml +1 -1
  4. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/core/content.py +18 -2
  5. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/core/document.py +2 -3
  6. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/core/transactions.py +15 -3
  7. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/errors.py +54 -1
  8. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/handlers/agent_document.py +5 -0
  9. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/handlers/read_export.py +14 -10
  10. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/identity.json +2 -2
  11. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/mixed_form.py +3 -1
  12. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/rendering/oracle.py +15 -0
  13. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/ops_services/context.py +49 -3
  14. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/ops_services/form_fields.py +3 -0
  15. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/ops_services/package_validation.py +1 -1
  16. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/ops_services/planning.py +1 -1
  17. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/ops_services/read_query.py +21 -16
  18. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/ops_services/save_policy.py +3 -0
  19. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/ops_services/transactions.py +1 -0
  20. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/runtime.py +26 -3
  21. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/storage.py +116 -10
  22. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/upstream.py +56 -2
  23. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3/src/python_hwpx_automation.egg-info}/PKG-INFO +3 -3
  24. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/LICENSE +0 -0
  25. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/MANIFEST.in +0 -0
  26. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/NOTICE +0 -0
  27. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/setup.cfg +0 -0
  28. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/__init__.py +0 -0
  29. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/__init__.pyi +0 -0
  30. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/__main__.py +0 -0
  31. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/agent_document.py +0 -0
  32. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/api.py +0 -0
  33. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/blind_eval.py +0 -0
  34. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/capabilities.py +0 -0
  35. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/compat.py +0 -0
  36. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/configuration.py +0 -0
  37. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/core/__init__.py +0 -0
  38. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/core/context.py +0 -0
  39. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/core/diff.py +0 -0
  40. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/core/formatting.py +0 -0
  41. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/core/handles.py +0 -0
  42. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/core/locations.py +0 -0
  43. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/core/locator.py +0 -0
  44. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/core/plan.py +0 -0
  45. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/core/resources.py +0 -0
  46. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/core/search.py +0 -0
  47. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/core/txn.py +0 -0
  48. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/document_state.py +0 -0
  49. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/execution_lock.py +0 -0
  50. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/fastmcp_adapter.py +0 -0
  51. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/form_fill.py +0 -0
  52. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/form_output_models.py +0 -0
  53. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/handlers/__init__.py +0 -0
  54. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/handlers/_shared.py +0 -0
  55. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/handlers/authoring.py +0 -0
  56. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/handlers/content_edit.py +0 -0
  57. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/handlers/form_fill.py +0 -0
  58. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/handlers/layout_style.py +0 -0
  59. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/handlers/quality_render.py +0 -0
  60. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/handlers/specialized.py +0 -0
  61. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/handlers/tracked_changes.py +0 -0
  62. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/handlers/workflow.py +0 -0
  63. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/hwp_converter.py +0 -0
  64. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/hwp_support.py +0 -0
  65. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/hwpx_ops.py +0 -0
  66. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/identity.py +0 -0
  67. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/ingest_adapters.py +0 -0
  68. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/markdown_plan.py +0 -0
  69. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/mcp_cli.py +0 -0
  70. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/metadata/tools_meta.py +0 -0
  71. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/mutation_models.py +0 -0
  72. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/network_policy.py +0 -0
  73. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/__init__.py +0 -0
  74. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/agent/__init__.py +0 -0
  75. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/agent/_batch_publication.py +0 -0
  76. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/agent/_batch_verification.py +0 -0
  77. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/agent/_text_scope.py +0 -0
  78. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/agent/blueprint/__init__.py +0 -0
  79. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/agent/blueprint/bundle.py +0 -0
  80. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/agent/blueprint/catalog.py +0 -0
  81. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/agent/blueprint/dump.py +0 -0
  82. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/agent/blueprint/mapping.py +0 -0
  83. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/agent/blueprint/model.py +0 -0
  84. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/agent/blueprint/native.py +0 -0
  85. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/agent/blueprint/replay.py +0 -0
  86. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/agent/catalog.py +0 -0
  87. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/agent/cli.py +0 -0
  88. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/agent/commands.py +0 -0
  89. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/agent/document.py +0 -0
  90. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/agent/form_plan.py +0 -0
  91. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/agent/model.py +0 -0
  92. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/agent/path.py +0 -0
  93. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/agent/query.py +0 -0
  94. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/agent/story.py +0 -0
  95. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/__init__.py +0 -0
  96. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/advanced_generators.py +0 -0
  97. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/builder/__init__.py +0 -0
  98. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/builder/core.py +0 -0
  99. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/builder/report.py +0 -0
  100. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/__init__.py +0 -0
  101. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/_support.py +0 -0
  102. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/composer.py +0 -0
  103. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/harvest.py +0 -0
  104. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/plan.py +0 -0
  105. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/profile.py +0 -0
  106. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/profiles/application_form/fragments/body.xml +0 -0
  107. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/profiles/application_form/fragments/heading.xml +0 -0
  108. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/profiles/application_form/fragments/info_table.xml +0 -0
  109. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/profiles/application_form/fragments/title.xml +0 -0
  110. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/profiles/application_form/profile.json +0 -0
  111. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/profiles/application_form/template.hwpx +0 -0
  112. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/profiles/home_notice/fragments/body.xml +0 -0
  113. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/profiles/home_notice/fragments/heading.xml +0 -0
  114. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/profiles/home_notice/fragments/title.xml +0 -0
  115. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/profiles/home_notice/profile.json +0 -0
  116. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/profiles/home_notice/template.hwpx +0 -0
  117. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/profiles/official_notice/fragments/body.xml +0 -0
  118. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/profiles/official_notice/fragments/heading.xml +0 -0
  119. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/profiles/official_notice/fragments/info_table.xml +0 -0
  120. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/profiles/official_notice/fragments/title.xml +0 -0
  121. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/profiles/official_notice/profile.json +0 -0
  122. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/profiles/official_notice/template.hwpx +0 -0
  123. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/profiles/report/fragments/body.xml +0 -0
  124. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/profiles/report/fragments/heading.xml +0 -0
  125. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/profiles/report/fragments/info_table.xml +0 -0
  126. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/profiles/report/fragments/title.xml +0 -0
  127. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/profiles/report/profile.json +0 -0
  128. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/profiles/report/template.hwpx +0 -0
  129. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/design/validator.py +0 -0
  130. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/presets/__init__.py +0 -0
  131. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/presets/proposal.py +0 -0
  132. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/report_parser.py +0 -0
  133. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/style_profile.py +0 -0
  134. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/authoring/template_analyzer.py +0 -0
  135. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/charting/__init__.py +0 -0
  136. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/compliance/__init__.py +0 -0
  137. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/compliance/official_lint.py +0 -0
  138. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/compliance/pii.py +0 -0
  139. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/document_ops/__init__.py +0 -0
  140. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/document_ops/comparison.py +0 -0
  141. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/document_ops/mail_merge.py +0 -0
  142. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/document_ops/redline.py +0 -0
  143. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/evalplan/__init__.py +0 -0
  144. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/evalplan/runtime.py +0 -0
  145. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/exam/__init__.py +0 -0
  146. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/exam/compose.py +0 -0
  147. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/exam/ir.py +0 -0
  148. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/exam/measure.py +0 -0
  149. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/exam/parser.py +0 -0
  150. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/exam/profile.py +0 -0
  151. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/form_fill/__init__.py +0 -0
  152. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/form_fill/classification.py +0 -0
  153. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/form_fill/fill_residue.py +0 -0
  154. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/form_fill/fit/__init__.py +0 -0
  155. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/form_fill/fit/apply.py +0 -0
  156. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/form_fill/fit/engine.py +0 -0
  157. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/form_fill/fit/measure.py +0 -0
  158. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/form_fill/fit/policy.py +0 -0
  159. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/form_fill/fit/report.py +0 -0
  160. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/form_fill/fit/seal.py +0 -0
  161. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/form_fill/fit/wordbox.py +0 -0
  162. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/form_fill/guidance.py +0 -0
  163. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/form_fill/quality.py +0 -0
  164. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/form_fill/split_run.py +0 -0
  165. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/form_fill/template_formfit.py +0 -0
  166. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/house_style/__init__.py +0 -0
  167. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/house_style/composition.py +0 -0
  168. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/house_style/data/bank.json +0 -0
  169. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/house_style/data/genres.json +0 -0
  170. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/house_style/gianmun.py +0 -0
  171. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/house_style/org_chart.py +0 -0
  172. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/quality/__init__.py +0 -0
  173. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/quality/page_guard.py +0 -0
  174. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/rendering/__init__.py +0 -0
  175. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/rendering/__main__.py +0 -0
  176. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/rendering/_hancom_open_rate.ps1 +0 -0
  177. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/rendering/_refresh_hwpx_mac.applescript +0 -0
  178. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/rendering/_render_hwpx.ps1 +0 -0
  179. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/rendering/_render_hwpx_mac.applescript +0 -0
  180. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/rendering/block_splits.py +0 -0
  181. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/rendering/cli.py +0 -0
  182. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/rendering/detectors.py +0 -0
  183. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/rendering/diff.py +0 -0
  184. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/rendering/fixture_corpus.py +0 -0
  185. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/rendering/mac_session.py +0 -0
  186. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/rendering/page_qa.py +0 -0
  187. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/rendering/qa_contracts.py +0 -0
  188. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/rendering/qa_metrics.py +0 -0
  189. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/rendering/worker.py +0 -0
  190. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/utilities/__init__.py +0 -0
  191. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/office/utilities/table_compute.py +0 -0
  192. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/ops_services/__init__.py +0 -0
  193. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/ops_services/_border_fill.py +0 -0
  194. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/ops_services/composition.py +0 -0
  195. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/ops_services/content_layout.py +0 -0
  196. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/ops_services/media.py +0 -0
  197. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/ops_services/memo_style.py +0 -0
  198. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/ops_services/preview_export.py +0 -0
  199. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/ops_services/tables.py +0 -0
  200. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/preview_output_models.py +0 -0
  201. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/public-modules.json +0 -0
  202. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/py.typed +0 -0
  203. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/quality.py +0 -0
  204. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/quality_generation.py +0 -0
  205. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/resources.py +0 -0
  206. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/runtime_services.py +0 -0
  207. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/server.py +0 -0
  208. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/tool_bindings.py +0 -0
  209. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/tool_contract.py +0 -0
  210. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/utils/__init__.py +0 -0
  211. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/utils/helpers.py +0 -0
  212. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/utils/read_budget.py +0 -0
  213. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/visual_qa.py +0 -0
  214. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/workflow/__init__.py +0 -0
  215. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/workflow/adapters.py +0 -0
  216. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/workflow/dispatcher.py +0 -0
  217. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/workflow/evidence_lineage.py +0 -0
  218. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/workflow/models.py +0 -0
  219. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/workflow/policy.py +0 -0
  220. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/workflow/render_contracts.py +0 -0
  221. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/workflow/render_metrics.py +0 -0
  222. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/workflow/render_queue.py +0 -0
  223. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/workflow/render_security.py +0 -0
  224. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/workflow/render_transport.py +0 -0
  225. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/workflow/rendering.py +0 -0
  226. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/workflow/service.py +0 -0
  227. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/workflow/state_machine.py +0 -0
  228. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/workflow/store.py +0 -0
  229. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/hwpx_automation/workspace.py +0 -0
  230. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/python_hwpx_automation.egg-info/SOURCES.txt +0 -0
  231. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/python_hwpx_automation.egg-info/dependency_links.txt +0 -0
  232. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/python_hwpx_automation.egg-info/entry_points.txt +0 -0
  233. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/python_hwpx_automation.egg-info/requires.txt +0 -0
  234. {python_hwpx_automation-7.3.2 → python_hwpx_automation-7.3.3}/src/python_hwpx_automation.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-hwpx-automation
3
- Version: 7.3.2
3
+ Version: 7.3.3
4
4
  Summary: Task-oriented HWPX document automation for Python, with an optional MCP adapter.
5
5
  Author: Kohkyuhyun
6
6
  License-Expression: Apache-2.0
@@ -74,7 +74,7 @@ Dynamic: license-file
74
74
 
75
75
  <!-- release-state: release-approved -->
76
76
  > [!NOTE]
77
- > 발행이 승인된 7.3.2 후보 (release-approved; 원격 발행 대기): `python-hwpx 6.6.0 → python-hwpx-automation 7.3.2 → hwpx-plugin 2.4.0` (`5e5c23651f92785a`).
77
+ > 발행이 승인된 7.3.3 후보 (release-approved; 원격 발행 대기): `python-hwpx 6.6.0 → python-hwpx-automation 7.3.3 → hwpx-plugin 2.4.0` (`5e5c23651f92785a`).
78
78
  > 공개 트레인: `python-hwpx 6.6.0 → python-hwpx-automation 7.3.1 → hwpx-plugin 2.4.0`.
79
79
  > 공개 좌표는 core·automation PyPI와 plugin GitHub
80
80
  > Release·marketplace·실제 marketplace 설치 관찰 후에만 승격합니다.
@@ -162,7 +162,7 @@ PDF와 쪽 PNG로 렌더합니다. 별도 환경에서 실행하므로 호출하
162
162
  고정 버전을 건드리지 않습니다.
163
163
 
164
164
  ```bash
165
- uvx --from "python-hwpx-automation[oracle]==7.3.2" \
165
+ uvx --from "python-hwpx-automation[oracle]==7.3.3" \
166
166
  hwpx render-pdf in.hwpx out.pdf --png out/page --json
167
167
  ```
168
168
 
@@ -14,7 +14,7 @@
14
14
 
15
15
  <!-- release-state: release-approved -->
16
16
  > [!NOTE]
17
- > 발행이 승인된 7.3.2 후보 (release-approved; 원격 발행 대기): `python-hwpx 6.6.0 → python-hwpx-automation 7.3.2 → hwpx-plugin 2.4.0` (`5e5c23651f92785a`).
17
+ > 발행이 승인된 7.3.3 후보 (release-approved; 원격 발행 대기): `python-hwpx 6.6.0 → python-hwpx-automation 7.3.3 → hwpx-plugin 2.4.0` (`5e5c23651f92785a`).
18
18
  > 공개 트레인: `python-hwpx 6.6.0 → python-hwpx-automation 7.3.1 → hwpx-plugin 2.4.0`.
19
19
  > 공개 좌표는 core·automation PyPI와 plugin GitHub
20
20
  > Release·marketplace·실제 marketplace 설치 관찰 후에만 승격합니다.
@@ -102,7 +102,7 @@ PDF와 쪽 PNG로 렌더합니다. 별도 환경에서 실행하므로 호출하
102
102
  고정 버전을 건드리지 않습니다.
103
103
 
104
104
  ```bash
105
- uvx --from "python-hwpx-automation[oracle]==7.3.2" \
105
+ uvx --from "python-hwpx-automation[oracle]==7.3.3" \
106
106
  hwpx render-pdf in.hwpx out.pdf --png out/page --json
107
107
  ```
108
108
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-hwpx-automation"
7
- version = "7.3.2"
7
+ version = "7.3.3"
8
8
  description = "Task-oriented HWPX document automation for Python, with an optional MCP adapter."
9
9
  readme = "README.md"
10
10
  authors = [{ name = "Kohkyuhyun" }]
@@ -14,8 +14,15 @@ from uuid import uuid4
14
14
  from xml.etree import ElementTree as ET
15
15
 
16
16
  from ..compat import patch_python_hwpx
17
- from ..storage import build_hwpx_open_safety_report
18
- from ..upstream import HP_NS as _HP_NS, HwpxDocument, repair_pathological_text_spacing
17
+ from ..storage import build_hwpx_open_safety_report, open_local_document
18
+ from ..upstream import (
19
+ HP_NS as _HP_NS,
20
+ HwpxDocument,
21
+ is_hwp5_source,
22
+ is_hwp5_target,
23
+ repair_pathological_text_spacing,
24
+ )
25
+ from .document import save_doc
19
26
  from .formatting import resolve_style_id
20
27
  from .locations import resolve_paragraph_reference
21
28
 
@@ -500,6 +507,15 @@ def copy_document_file(source: str, destination: str = None) -> str:
500
507
  destination = f"{stem}_copy.{ext}"
501
508
  source_path = Path(source)
502
509
  destination_path = Path(destination)
510
+ if is_hwp5_source(source_path) != is_hwp5_target(destination_path):
511
+ # The destination extension picks the format, so a copy between .hwp
512
+ # and .hwpx is written through the document model, not byte-copied.
513
+ document = open_local_document(source_path, role="copy source")
514
+ try:
515
+ save_doc(document, str(destination_path))
516
+ finally:
517
+ document.close()
518
+ return str(destination_path)
503
519
  if source_path.suffix.lower() != ".hwpx" and destination_path.suffix.lower() != ".hwpx":
504
520
  shutil.copy2(source_path, destination_path)
505
521
  return str(destination_path)
@@ -5,7 +5,7 @@ from pathlib import Path
5
5
  from typing import Any
6
6
 
7
7
  from ..configuration import env_value
8
- from ..storage import LocalDocumentStorage, require_hwpx_editor_open_safe
8
+ from ..storage import LocalDocumentStorage, open_local_document
9
9
  from ..upstream import HwpxDocument, blank_document_template_bytes, open_document
10
10
 
11
11
 
@@ -18,8 +18,7 @@ def _local_storage() -> LocalDocumentStorage:
18
18
  def open_doc(path: str) -> HwpxDocument:
19
19
  if not os.path.exists(path):
20
20
  raise FileNotFoundError(f"파일을 찾을 수 없습니다: {path}")
21
- require_hwpx_editor_open_safe(Path(path), role="local HWPX open")
22
- return open_document(path)
21
+ return open_local_document(Path(path), role="local HWPX open")
23
22
 
24
23
 
25
24
  def save_doc(doc: HwpxDocument, path: str, *, quality: Any = None) -> dict[str, Any]:
@@ -12,8 +12,12 @@ from pathlib import Path
12
12
  from typing import Any, Iterable
13
13
 
14
14
  from .. import quality as quality_contract
15
- from ..storage import build_hwpx_verification_report, require_hwpx_editor_open_safe
16
- from ..upstream import HwpxDocument, open_document
15
+ from ..storage import (
16
+ build_hwpx_verification_report,
17
+ require_hwpx_editor_open_safe,
18
+ require_native_hwp5,
19
+ )
20
+ from ..upstream import HwpxDocument, hwp5_document_bytes, is_hwp5_target, open_document
17
21
 
18
22
  _MAX_SUMMARY_ITEMS = 20
19
23
  _MAX_SNIPPET = 80
@@ -341,9 +345,13 @@ def save_dry_run(
341
345
  document: HwpxDocument, target: str | Path, *, quality: Any = None
342
346
  ) -> dict[str, Any]:
343
347
  target_path = Path(target)
348
+ hwp_target = is_hwp5_target(target_path)
349
+ if hwp_target:
350
+ require_native_hwp5()
351
+ # The gate checks HWPX parts, so an .hwp target is staged as HWPX.
344
352
  fd, tmp_name = tempfile.mkstemp(
345
353
  prefix=f".{target_path.stem}.dry-run.",
346
- suffix=target_path.suffix or ".hwpx",
354
+ suffix=".hwpx" if hwp_target else (target_path.suffix or ".hwpx"),
347
355
  dir=str(target_path.parent),
348
356
  )
349
357
  tmp_path = Path(tmp_name)
@@ -366,6 +374,10 @@ def save_dry_run(
366
374
  "dry-run HWPX failed open-safety verification: "
367
375
  + verification["openSafety"]["summary"]
368
376
  )
377
+ if hwp_target:
378
+ # Raises Hwp5Error (hwp5-write-unsupported) as the real save would.
379
+ hwp5_document_bytes(document)
380
+ verification["format"] = "hwp"
369
381
  diff = semantic_diff(target_path, tmp_path) if target_path.exists() else None
370
382
  verification["filePath"] = str(target_path)
371
383
  verification["dryRunTempDeleted"] = True
@@ -73,6 +73,7 @@ DEFAULT_ERROR_SUGGESTIONS: dict[str, str] = {
73
73
  "EMPTY_MATCH": "찾을 텍스트나 앵커 조건을 실제 문서 내용에 맞게 수정하세요.",
74
74
  "HWP_CONVERSION_FAILED": "원본 HWP가 손상되지 않았는지 확인하고 변환 로그의 원인을 기준으로 다시 시도하세요.",
75
75
  "HWP_TEXT_EXTRACT_FAILED": "HWP 파일을 다른 뷰어에서 열어 유효성을 확인한 뒤 텍스트 추출을 다시 실행하세요.",
76
+ "HWPX_PACKAGE_REQUIRED": "copy_document로 .hwpx 사본을 만들어 이 도구를 쓰고, .hwp가 필요하면 결과를 copy_document로 .hwp 경로에 복사하세요. search_and_replace·set_table_cell_text 같은 문서 편집 도구는 .hwp를 그대로 편집합니다.",
76
77
  "IDEMPOTENT_REPLAY": "이미 적용된 planId입니다. 최신 preview/plan을 새로 만든 뒤 apply를 다시 호출하세요.",
77
78
  "MEMO_NOT_FOUND": "list_memos 또는 문서 리소스에서 memoId를 다시 확인한 뒤 호출하세요.",
78
79
  "MISSING_NODE": "대상 섹션, 문단, 표, 셀이 아직 존재하는지 조회한 뒤 새 앵커로 다시 실행하세요.",
@@ -81,7 +82,10 @@ DEFAULT_ERROR_SUGGESTIONS: dict[str, str] = {
81
82
  "PIPELINE_ERROR": "details.pipelineCode와 hint를 확인한 뒤 preview부터 다시 생성하세요.",
82
83
  "PLAN_RECORD_MISSING": "preview_edit 또는 plan_edit으로 planId를 새로 발급받은 뒤 apply를 다시 호출하세요.",
83
84
  "PREVIEW_REQUIRED": "preview_edit으로 변경 내용을 확인한 뒤 반환된 planId로 apply_edit을 호출하세요.",
84
- "READ_ONLY_HWP_DOCUMENT": "HWP 바이너리는 직접 저장할 수 없습니다. 먼저 HWPX로 변환한 뒤 편집하세요.",
85
+ "HWP_DOCUMENT_UNREADABLE": "한/글에서 문서를 열어 다시 저장한 뒤 사용하세요. details.hwp5Code가 원인입니다.",
86
+ "HWP_ENCRYPTED_DOCUMENT": "한/글에서 문서 암호·배포용 보호를 풀고 다시 저장한 뒤 사용하세요. DRM 문서는 열 수 없습니다.",
87
+ "HWP_WRITE_UNSUPPORTED": "HWP 5.0으로 쓸 수 없는 내용이 있습니다. 출력 경로를 .hwpx로 지정해 저장하세요.",
88
+ "READ_ONLY_HWP_DOCUMENT": "python-hwpx 6.6.0 이상을 설치하면 .hwp 문서를 그대로 열고 편집·저장할 수 있습니다.",
85
89
  "RENDER_PREVIEW_INVALID_MODE": "mode는 'pages' 또는 'long'으로 지정한 뒤 다시 실행하세요.",
86
90
  "RENDER_PREVIEW_INVALID_SCREENSHOT_MODE": "screenshot은 'auto', 'require', 'off' 중 하나로 지정하세요.",
87
91
  "RENDER_PREVIEW_UNAVAILABLE": "matching python-hwpx 버전을 설치하고 MCP 서버를 새로 시작한 뒤 다시 실행하세요.",
@@ -175,3 +179,52 @@ def build_error_payload(
175
179
  suggestion=suggestion
176
180
  or suggestion_for_error(code, details=details, hint=hint),
177
181
  ).model_dump(exclude_none=True, by_alias=True)
182
+
183
+
184
+ class HwpDocumentError(RuntimeError):
185
+ """An ``.hwp`` request the installed engine cannot carry out."""
186
+
187
+ def __init__(
188
+ self, message: str, *, code: str, details: Optional[Dict[str, Any]] = None
189
+ ) -> None:
190
+ super().__init__(message)
191
+ self.code = code
192
+ self.message = message
193
+ self.details = details
194
+
195
+ def to_payload(self) -> Dict[str, Any]:
196
+ return build_error_payload(
197
+ code=self.code, message=self.message, details=self.details
198
+ )
199
+
200
+
201
+ _HWP5_ENCRYPTED_CODES = frozenset({"hwp5-password", "hwp5-distribution", "hwp5-drm"})
202
+
203
+
204
+ def hwp5_error_payload(exc: BaseException) -> Optional[Dict[str, Any]]:
205
+ """The MCP error for python-hwpx's ``Hwp5Error``; None for any other exception.
206
+
207
+ Matched by its ``hwp5-*`` code so this module needs no core import.
208
+ """
209
+
210
+ hwp5_code = getattr(exc, "code", None)
211
+ if not isinstance(hwp5_code, str) or not hwp5_code.startswith("hwp5-"):
212
+ return None
213
+ details = {"hwp5Code": hwp5_code}
214
+ if hwp5_code == "hwp5-write-unsupported":
215
+ return build_error_payload(
216
+ code="HWP_WRITE_UNSUPPORTED",
217
+ message="문서에 HWP 5.0(.hwp)으로 쓸 수 없는 내용이 있어 아무것도 저장하지 않았습니다.",
218
+ details=details,
219
+ )
220
+ if hwp5_code in _HWP5_ENCRYPTED_CODES:
221
+ return build_error_payload(
222
+ code="HWP_ENCRYPTED_DOCUMENT",
223
+ message="암호·배포용·DRM으로 보호된 HWP 문서는 열 수 없습니다.",
224
+ details=details,
225
+ )
226
+ return build_error_payload(
227
+ code="HWP_DOCUMENT_UNREADABLE",
228
+ message="HWP 5.0(.hwp) 문서를 읽을 수 없습니다.",
229
+ details=details,
230
+ )
@@ -9,6 +9,8 @@ from typing import Any
9
9
 
10
10
 
11
11
  from ..document_state import document_state_payload
12
+ from ..errors import HwpDocumentError
13
+ from ..storage import require_hwpx_package
12
14
  from ..agent_document import (
13
15
  AgentChildLimit,
14
16
  AgentCommandList,
@@ -100,6 +102,7 @@ def apply_document_commands(
100
102
  try:
101
103
  resolved_input = resolve_path(filename)
102
104
  resolved_output = resolve_path(output)
105
+ require_hwpx_package(resolved_input, output=resolved_output)
103
106
  quality_contract.assert_write_capability()
104
107
  store = ScopedIdempotencyStore(
105
108
  RUNTIME_SERVICES.idempotency_cache,
@@ -132,6 +135,8 @@ def apply_document_commands(
132
135
  if Path(state_path).exists():
133
136
  payload.update(document_state_payload(state_path))
134
137
  return payload
138
+ except HwpDocumentError:
139
+ raise
135
140
  except Exception as exc:
136
141
  return agent_batch_error_payload(
137
142
  exc,
@@ -35,6 +35,7 @@ from ..office.compliance import DEFAULT_POLICY, mask_pii
35
35
  from ..runtime_services import RUNTIME_SERVICES
36
36
  from ..upstream import (
37
37
  HP_NS,
38
+ hwp5_conversion_report,
38
39
  open_document,
39
40
  )
40
41
  from ..utils.helpers import default_max_chars, resolve_path, truncate_response
@@ -777,16 +778,19 @@ def get_document_info(filename: str) -> dict:
777
778
  path = resolve_path(filename)
778
779
  doc = open_doc(path)
779
780
  file_size = Path(path).stat().st_size
780
- return _with_document_state(
781
- {
782
- "filename": filename,
783
- "sections": len(doc.sections),
784
- "paragraphs": _paragraph_count(doc),
785
- "tables": _table_count(doc),
786
- "file_size": str(file_size),
787
- },
788
- path,
789
- )
781
+ info: dict[str, Any] = {
782
+ "filename": filename,
783
+ "sections": len(doc.sections),
784
+ "paragraphs": _paragraph_count(doc),
785
+ "tables": _table_count(doc),
786
+ "file_size": str(file_size),
787
+ }
788
+ conversion = hwp5_conversion_report(doc)
789
+ if conversion is not None:
790
+ # An .hwp is edited through the document model; this is what it left out.
791
+ info["format"] = "hwp"
792
+ info["hwpConversion"] = conversion
793
+ return _with_document_state(info, path)
790
794
 
791
795
 
792
796
  def get_document_text(
@@ -7,9 +7,9 @@
7
7
  "candidate": {
8
8
  "pythonHwpx": "6.6.0",
9
9
  "canonicalDistribution": "python-hwpx-automation",
10
- "canonicalAutomation": "7.3.2",
10
+ "canonicalAutomation": "7.3.3",
11
11
  "compatibilityDistribution": "hwpx-mcp-server",
12
- "compatibility": "7.3.2",
12
+ "compatibility": "7.3.3",
13
13
  "plugin": "2.4.0",
14
14
  "contractHash": "5e5c23651f92785a"
15
15
  },
@@ -41,7 +41,7 @@ from pydantic import (
41
41
  from .execution_lock import PUBLIC_MUTATION_LOCK
42
42
  from .office.agent._batch_publication import GuardedSavePipeline
43
43
  from .office.rendering import resolve_hancom_backend
44
- from .storage import build_hwpx_open_safety_report
44
+ from .storage import build_hwpx_open_safety_report, require_hwpx_package
45
45
  from .utils.helpers import resolve_path
46
46
  from .workspace import (
47
47
  WorkspaceMissingParentGuard,
@@ -1374,6 +1374,7 @@ def analyze_mixed_form_plan(
1374
1374
 
1375
1375
  request = _authorize_public_plan(_payload(plan, apply=False))
1376
1376
  source = Path(request["source"])
1377
+ require_hwpx_package(source, output=request["output"])
1377
1378
  source_snapshot = _capture_path_snapshot(source)
1378
1379
  if source_snapshot.data is None:
1379
1380
  raise FileNotFoundError(source)
@@ -1494,6 +1495,7 @@ def _apply_canonical_mixed_form_plan_locked(
1494
1495
 
1495
1496
  compiled = validate_mixed_form_plan(compiled).to_dict()
1496
1497
  source, output = _authorize_compiled_plan(compiled)
1498
+ require_hwpx_package(source, output=output)
1497
1499
  source_snapshot = _capture_path_snapshot(source)
1498
1500
  if source_snapshot.data is None: # pragma: no cover - compiled plan invariant
1499
1501
  raise FileNotFoundError(source)
@@ -149,6 +149,16 @@ def _complete_package(path: str) -> bool:
149
149
  return False
150
150
 
151
151
 
152
+ def _unchanged(path: str, before: os.stat_result) -> bool:
153
+ """True when ``path`` still has the size and modification time in ``before``."""
154
+
155
+ try:
156
+ now = os.stat(path)
157
+ except OSError:
158
+ return False
159
+ return (now.st_size, now.st_mtime_ns) == (before.st_size, before.st_mtime_ns)
160
+
161
+
152
162
  def _promote_file(staged: str, target: str) -> None:
153
163
  """Move a finished file to ``target`` without leaving a partial ``target``.
154
164
 
@@ -442,6 +452,8 @@ class WindowsComOracle(RenderBackend):
442
452
  by a complete package: a failed refresh leaves it as it was. Returns
443
453
  True when the file was re-saved. Only an ``.hwpx`` file is refreshed
444
454
  (Hancom saves it as HWPX), and a link is followed to the file it names.
455
+ A document that changed while Hancom ran (size or modification time)
456
+ is not overwritten: the refresh returns False and the change stays.
445
457
  """
446
458
 
447
459
  src = os.path.realpath(hwpx_path)
@@ -459,6 +471,7 @@ class WindowsComOracle(RenderBackend):
459
471
  try:
460
472
  os.makedirs(os.path.dirname(job_src))
461
473
  os.makedirs(os.path.dirname(job_out))
474
+ before = os.stat(src)
462
475
  shutil.copyfile(src, job_src)
463
476
  except OSError:
464
477
  return False
@@ -467,6 +480,8 @@ class WindowsComOracle(RenderBackend):
467
480
  entry = entries[0] if entries else None
468
481
  if not (isinstance(entry, dict) and entry.get("saved") and _complete_package(job_out)):
469
482
  return False
483
+ if not _unchanged(src, before):
484
+ return False
470
485
  try:
471
486
  _promote_file(job_out, src)
472
487
  except OSError:
@@ -4,13 +4,16 @@ from __future__ import annotations
4
4
 
5
5
  import hashlib
6
6
  import logging
7
+ from io import BytesIO
7
8
  from pathlib import Path
9
+ from zipfile import ZipFile
8
10
  from typing import Any, Callable, Dict, List, Optional, Tuple
9
11
 
12
+ from ..errors import HwpDocumentError, hwp5_error_payload
10
13
  from ..hwp_support import HwpBinaryError, extract_hwp_text
11
14
  from ..core.locator import RegisteredHandle
12
15
  from ..core.context import default_session_lifecycle_policy
13
- from ..storage import DocumentStorage, LocalDocumentStorage
16
+ from ..storage import DocumentStorage, LocalDocumentStorage, require_hwpx_package
14
17
  from ..workspace import (
15
18
  WorkspacePathError,
16
19
  )
@@ -18,6 +21,8 @@ from ..upstream import (
18
21
  HwpxDocument,
19
22
  HwpxOxmlParagraph,
20
23
  HwpxOxmlTable,
24
+ hwpx_view_bytes,
25
+ native_hwp5_supported,
21
26
  )
22
27
 
23
28
  logger = logging.getLogger("hwpx_automation.hwpx_ops")
@@ -158,12 +163,50 @@ class DocumentContext:
158
163
  def _relative_path(self, path: Path) -> str:
159
164
  return self.storage.relative_path(path)
160
165
 
166
+ def _is_legacy_hwp(self, resolved: Path) -> bool:
167
+ """An ``.hwp`` the installed core cannot open; only its preview text is readable."""
168
+
169
+ return resolved.suffix.lower() == ".hwp" and not native_hwp5_supported()
170
+
171
+ def _text_source(self, resolved: Path) -> Path | ZipFile:
172
+ """What the text extractor reads: the HWPX file, or an ``.hwp``'s HWPX model."""
173
+
174
+ if resolved.suffix.lower() != ".hwp":
175
+ return resolved
176
+ try:
177
+ return ZipFile(BytesIO(hwpx_view_bytes(resolved)))
178
+ except Exception as exc:
179
+ hwp_error = self._hwp_error(exc)
180
+ if hwp_error is None:
181
+ raise
182
+ raise hwp_error from exc
183
+
184
+ def _hwp_error(self, exc: BaseException) -> Optional[RuntimeError]:
185
+ """The operation error for an ``.hwp`` refusal, or None for any other failure."""
186
+
187
+ if isinstance(exc, HwpDocumentError):
188
+ return self._new_error(exc.code, exc.message, details=exc.details)
189
+ payload = hwp5_error_payload(exc)
190
+ if payload is None:
191
+ return None
192
+ return self._new_error(
193
+ payload["code"], payload["message"], details=payload.get("details")
194
+ )
195
+
196
+ def _require_hwpx_package(self, source: Path, output: Optional[str] = None) -> None:
197
+ """Refuse ``.hwp`` for editors that patch HWPX package bytes."""
198
+
199
+ try:
200
+ require_hwpx_package(source, output=output)
201
+ except HwpDocumentError as exc:
202
+ raise self._new_error(exc.code, exc.message, details=exc.details) from exc
203
+
161
204
  def _open_document(self, path: str) -> Tuple[HwpxDocument, Path]:
162
205
  resolved = self._resolve_path(path)
163
- if resolved.suffix.lower() == ".hwp":
206
+ if self._is_legacy_hwp(resolved):
164
207
  raise self._new_error(
165
208
  "READ_ONLY_HWP_DOCUMENT",
166
- "HWP 파일은 편집이 불가합니다. 먼저 convert_hwp_to_hwpx 도구로 HWPX 변환 후 편집하세요.",
209
+ "설치된 python-hwpx는 HWP 5.0(.hwp) 문서를 열거나 저장하지 못합니다.",
167
210
  )
168
211
  try:
169
212
  document, resolved = self.storage.open_document(path)
@@ -186,6 +229,9 @@ class DocumentContext:
186
229
  details={"requestedName": Path(path).name},
187
230
  ) from exc
188
231
  except Exception as exc: # pragma: no cover - delegated to backend
232
+ hwp_error = self._hwp_error(exc)
233
+ if hwp_error is not None:
234
+ raise hwp_error from exc
189
235
  raise self._new_error(
190
236
  "DOCUMENT_OPEN_FAILED",
191
237
  f"failed to open '{path}': {exc}",
@@ -299,6 +299,7 @@ class FormFieldService:
299
299
  ) from exc
300
300
 
301
301
  source_path = self._context._resolve_path(path)
302
+ self._context._require_hwpx_package(source_path, output)
302
303
  output_precondition = None
303
304
  if isinstance(self._context.storage, LocalDocumentStorage):
304
305
  output_precondition = (
@@ -468,6 +469,7 @@ class FormFieldService:
468
469
  "installed python-hwpx does not provide hwpx.body_patch.apply_body_ops",
469
470
  ) from exc
470
471
  source_path = self._context._resolve_path(path)
472
+ self._context._require_hwpx_package(source_path, output)
471
473
  output_precondition = None
472
474
  if isinstance(self._context.storage, LocalDocumentStorage):
473
475
  output_precondition = (
@@ -633,6 +635,7 @@ class FormFieldService:
633
635
  ) from exc
634
636
 
635
637
  blank = self._context._resolve_path(path)
638
+ self._context._require_hwpx_package(blank, output)
636
639
  md = self._context._resolve_path(review_md)
637
640
  output_precondition = None
638
641
  if isinstance(self._context.storage, LocalDocumentStorage):
@@ -135,7 +135,7 @@ class PackageValidationService:
135
135
  resolved = self._context._resolve_path(path)
136
136
  patterns = [re.compile(pat) for pat in (forbid_patterns or [])]
137
137
  warnings: List[Dict[str, Any]] = []
138
- with create_text_extractor(resolved) as extractor:
138
+ with create_text_extractor(self._context._text_source(resolved)) as extractor:
139
139
  for paragraph in extractor.iter_document_paragraphs():
140
140
  text = paragraph.text()
141
141
  if max_line_len is not None and len(text) > max_line_len:
@@ -39,7 +39,7 @@ class PlanningService:
39
39
  def _ensure_planner_document(self, doc_id: str, path: str) -> None:
40
40
  resolved = self._context._resolve_path(path)
41
41
  paragraphs: List[str] = []
42
- with create_text_extractor(resolved) as extractor:
42
+ with create_text_extractor(self._context._text_source(resolved)) as extractor:
43
43
  for paragraph in extractor.iter_document_paragraphs():
44
44
  paragraphs.append(paragraph.text(preserve_breaks=True))
45
45
  self._manager.register_document(doc_id, "\n".join(paragraphs))
@@ -18,6 +18,7 @@ from ..upstream import (
18
18
  AnnotationOptions,
19
19
  create_object_finder,
20
20
  create_text_extractor,
21
+ hwp5_conversion_report,
21
22
  )
22
23
 
23
24
  from .context import DocumentContext
@@ -45,7 +46,7 @@ class ReadQueryService:
45
46
  def get_paragraphs_by_handle(self, handle_id: str) -> Dict[str, Any]:
46
47
  handle = self._context.get_registered_handle(handle_id)
47
48
  resolved = self._context._resolve_path(handle.path)
48
- if resolved.suffix.lower() == ".hwp":
49
+ if self._context._is_legacy_hwp(resolved):
49
50
  paragraphs, _, _ = self._context._read_only_hwp_paragraphs(handle.path)
50
51
  hwp_serialized = [
51
52
  ParagraphResourceEntry(paragraphIndex=index, text=text)
@@ -57,7 +58,7 @@ class ReadQueryService:
57
58
  return model.model_dump(by_alias=True)
58
59
 
59
60
  serialized: List[ParagraphResourceEntry] = []
60
- with create_text_extractor(resolved) as extractor:
61
+ with create_text_extractor(self._context._text_source(resolved)) as extractor:
61
62
  for paragraph in extractor.iter_document_paragraphs():
62
63
  serialized.append(
63
64
  ParagraphResourceEntry(
@@ -73,7 +74,7 @@ class ReadQueryService:
73
74
  def get_tables_by_handle(self, handle_id: str) -> Dict[str, Any]:
74
75
  handle = self._context.get_registered_handle(handle_id)
75
76
  resolved = self._context._resolve_path(handle.path)
76
- if resolved.suffix.lower() == ".hwp":
77
+ if self._context._is_legacy_hwp(resolved):
77
78
  model = DocumentTablesResource(handleId=handle.handle_id, tables=[])
78
79
  return model.model_dump(by_alias=True)
79
80
 
@@ -92,7 +93,7 @@ class ReadQueryService:
92
93
 
93
94
  def open_info(self, path: str) -> Dict[str, Any]:
94
95
  resolved = self._context._resolve_path(path)
95
- if resolved.suffix.lower() == ".hwp":
96
+ if self._context._is_legacy_hwp(resolved):
96
97
  paragraphs, _, source = self._context._read_only_hwp_paragraphs(path)
97
98
  stat = resolved.stat()
98
99
  meta = {
@@ -123,6 +124,10 @@ class ReadQueryService:
123
124
  "size": stat.st_size,
124
125
  "modified": datetime.fromtimestamp(stat.st_mtime).isoformat(),
125
126
  }
127
+ conversion = hwp5_conversion_report(document)
128
+ if conversion is not None:
129
+ meta["format"] = "hwp"
130
+ meta["hwpConversion"] = conversion
126
131
  return {
127
132
  "meta": meta,
128
133
  "sectionCount": section_count,
@@ -169,7 +174,7 @@ class ReadQueryService:
169
174
  with_footnotes: bool = False,
170
175
  ) -> Dict[str, Any]:
171
176
  resolved = self._context._resolve_path(path)
172
- if resolved.suffix.lower() == ".hwp":
177
+ if self._context._is_legacy_hwp(resolved):
173
178
  hwp_paragraphs, _, _ = self._context._read_only_hwp_paragraphs(path)
174
179
  effective_limit = (
175
180
  self._context.paging_limit if limit is None else max(1, limit)
@@ -195,7 +200,7 @@ class ReadQueryService:
195
200
  paragraphs: List[str] = []
196
201
  next_offset: Optional[int] = None
197
202
  start = max(0, offset)
198
- with create_text_extractor(resolved) as extractor:
203
+ with create_text_extractor(self._context._text_source(resolved)) as extractor:
199
204
  paragraph_iter = extractor.iter_document_paragraphs()
200
205
  sentinel = object()
201
206
 
@@ -239,7 +244,7 @@ class ReadQueryService:
239
244
  unique_indexes.add(int(index))
240
245
 
241
246
  resolved = self._context._resolve_path(path)
242
- if resolved.suffix.lower() == ".hwp":
247
+ if self._context._is_legacy_hwp(resolved):
243
248
  paragraphs, _, _ = self._context._read_only_hwp_paragraphs(path)
244
249
  hwp_collected = {
245
250
  idx: paragraphs[idx] for idx in unique_indexes if idx < len(paragraphs)
@@ -266,7 +271,7 @@ class ReadQueryService:
266
271
  )
267
272
 
268
273
  collected: Dict[int, str] = {}
269
- with create_text_extractor(resolved) as extractor:
274
+ with create_text_extractor(self._context._text_source(resolved)) as extractor:
270
275
  for paragraph in extractor.iter_document_paragraphs():
271
276
  para_index = paragraph.index
272
277
  if para_index in unique_indexes and para_index not in collected:
@@ -292,7 +297,7 @@ class ReadQueryService:
292
297
 
293
298
  def text_extract_report(self, path: str, mode: str = "plain") -> Dict[str, Any]:
294
299
  resolved = self._context._resolve_path(path)
295
- if resolved.suffix.lower() == ".hwp":
300
+ if self._context._is_legacy_hwp(resolved):
296
301
  paragraphs, _, source = self._context._read_only_hwp_paragraphs(path)
297
302
  return {
298
303
  "content": "\n".join(paragraphs)
@@ -307,7 +312,7 @@ class ReadQueryService:
307
312
  endnote="inline",
308
313
  control="placeholder",
309
314
  )
310
- with create_text_extractor(resolved) as extractor:
315
+ with create_text_extractor(self._context._text_source(resolved)) as extractor:
311
316
  content = extractor.extract_text(
312
317
  annotations=annotations,
313
318
  include_nested=True,
@@ -322,11 +327,11 @@ class ReadQueryService:
322
327
  lock_keywords: Optional[Sequence[str]] = None,
323
328
  ) -> Dict[str, Any]:
324
329
  resolved = self._context._resolve_path(path)
325
- if resolved.suffix.lower() == ".hwp":
330
+ if self._context._is_legacy_hwp(resolved):
326
331
  paragraphs, _, source = self._context._read_only_hwp_paragraphs(path)
327
332
  else:
328
333
  paragraphs = []
329
- with create_text_extractor(resolved) as extractor:
334
+ with create_text_extractor(self._context._text_source(resolved)) as extractor:
330
335
  for paragraph in extractor.iter_document_paragraphs():
331
336
  paragraphs.append(paragraph.text(preserve_breaks=True))
332
337
  source = "hwpx.text_extractor"
@@ -474,7 +479,7 @@ class ReadQueryService:
474
479
  return snippet
475
480
 
476
481
  pattern = re.compile(query) if is_regex else None
477
- if resolved.suffix.lower() == ".hwp":
482
+ if self._context._is_legacy_hwp(resolved):
478
483
  paragraphs, _, _ = self._context._read_only_hwp_paragraphs(path)
479
484
  for para_index, text in enumerate(paragraphs):
480
485
  if is_regex:
@@ -512,7 +517,7 @@ class ReadQueryService:
512
517
  start = found + len(query)
513
518
  return {"matches": matches}
514
519
 
515
- with create_text_extractor(resolved) as extractor:
520
+ with create_text_extractor(self._context._text_source(resolved)) as extractor:
516
521
  for paragraph in extractor.iter_document_paragraphs():
517
522
  text = paragraph.text()
518
523
  if is_regex:
@@ -558,7 +563,7 @@ class ReadQueryService:
558
563
  max_results: int = 200,
559
564
  ) -> Dict[str, Any]:
560
565
  resolved = self._context._resolve_path(path)
561
- finder = create_object_finder(resolved)
566
+ finder = create_object_finder(self._context._text_source(resolved))
562
567
  objects = []
563
568
  for found in finder.iter(tag=tag_name, limit=max_results):
564
569
  element = found.element
@@ -582,7 +587,7 @@ class ReadQueryService:
582
587
  max_results: int = 200,
583
588
  ) -> Dict[str, Any]:
584
589
  resolved = self._context._resolve_path(path)
585
- finder = create_object_finder(resolved)
590
+ finder = create_object_finder(self._context._text_source(resolved))
586
591
  tag_filter = None if element_type in {None, "", "*"} else element_type
587
592
  attr_matcher: Any = value if value is not None else (lambda _: True)
588
593
  objects = []