hap-cli 0.8.11__tar.gz → 0.8.13__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 (253) hide show
  1. {hap_cli-0.8.11 → hap_cli-0.8.13}/PKG-INFO +1 -1
  2. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/__init__.py +1 -1
  3. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/post_cmd.py +2 -2
  4. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/role_cmd.py +46 -0
  5. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/design/design.schema.json +22 -0
  6. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/selftest.py +39 -0
  7. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/steps.py +41 -0
  8. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/chat.py +128 -8
  9. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/flow_node.py +21 -0
  10. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/post.py +24 -0
  11. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/role.py +48 -0
  12. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_core.py +361 -0
  13. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli.egg-info/PKG-INFO +1 -1
  14. {hap_cli-0.8.11 → hap_cli-0.8.13}/MANIFEST.in +0 -0
  15. {hap_cli-0.8.11 → hap_cli-0.8.13}/README.md +0 -0
  16. {hap_cli-0.8.11 → hap_cli-0.8.13}/app_live_tests/__init__.py +0 -0
  17. {hap_cli-0.8.11 → hap_cli-0.8.13}/app_live_tests/__main__.py +0 -0
  18. {hap_cli-0.8.11 → hap_cli-0.8.13}/app_live_tests/config.py +0 -0
  19. {hap_cli-0.8.11 → hap_cli-0.8.13}/app_live_tests/harness.py +0 -0
  20. {hap_cli-0.8.11 → hap_cli-0.8.13}/app_live_tests/smoke.py +0 -0
  21. {hap_cli-0.8.11 → hap_cli-0.8.13}/app_live_tests/state.py +0 -0
  22. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/README.md +0 -0
  23. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/README_CN.md +0 -0
  24. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/__main__.py +0 -0
  25. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/__init__.py +0 -0
  26. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/app_cmd.py +0 -0
  27. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/app_creator_cmd.py +0 -0
  28. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/app_editor_cmd.py +0 -0
  29. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/auth_cmd.py +0 -0
  30. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/calendar_cmd.py +0 -0
  31. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/chart_cmd.py +0 -0
  32. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/chat_cmd.py +0 -0
  33. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/chatbot_cmd.py +0 -0
  34. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/config_cmd.py +0 -0
  35. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/contact_cmd.py +0 -0
  36. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/department_cmd.py +0 -0
  37. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/group_cmd.py +0 -0
  38. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/icon_cmd.py +0 -0
  39. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/instance_cmd.py +0 -0
  40. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/node_cmd.py +0 -0
  41. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/optionset_cmd.py +0 -0
  42. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/page_cmd.py +0 -0
  43. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/record_cmd.py +0 -0
  44. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/region_cmd.py +0 -0
  45. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/update_cmd.py +0 -0
  46. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/upload_cmd.py +0 -0
  47. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/v3_registry.py +0 -0
  48. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/workflow_cmd.py +0 -0
  49. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/worksheet_cmd.py +0 -0
  50. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/context.py +0 -0
  51. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/__init__.py +0 -0
  52. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/action_spec_adapter.py +0 -0
  53. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app.py +0 -0
  54. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/__init__.py +0 -0
  55. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/_hapmeta/__init__.py +0 -0
  56. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/_hapmeta/control_type_codes.py +0 -0
  57. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/_hapmeta/worksheet_templates.py +0 -0
  58. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/charts.py +0 -0
  59. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/cleanup.py +0 -0
  60. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/compiler.py +0 -0
  61. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/config.py +0 -0
  62. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/errors.py +0 -0
  63. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/examples/minimal.design.json +0 -0
  64. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/executor.py +0 -0
  65. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/fields.py +0 -0
  66. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/hap.py +0 -0
  67. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/merge.py +0 -0
  68. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/recording/__init__.py +0 -0
  69. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/recording/console.py +0 -0
  70. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/recording/jsonl.py +0 -0
  71. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/recording/mirror.py +0 -0
  72. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/recording/report.py +0 -0
  73. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/schema.py +0 -0
  74. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/seed/INSTRUCTIONS.md +0 -0
  75. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/seed/__init__.py +0 -0
  76. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/seed/cli.py +0 -0
  77. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/seed/executor.py +0 -0
  78. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/seed/resources/attachments.json +0 -0
  79. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/seed/resources/sample.docx +0 -0
  80. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/seed/resources/sample.pdf +0 -0
  81. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/seed/resources/sample_images.json +0 -0
  82. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/seed/template.py +0 -0
  83. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/smoke.py +0 -0
  84. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/store.py +0 -0
  85. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/validate.py +0 -0
  86. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/workflow_dsl.py +0 -0
  87. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/auth.py +0 -0
  88. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/calendar_mod.py +0 -0
  89. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/chart.py +0 -0
  90. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/chatbot.py +0 -0
  91. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/cli_access.py +0 -0
  92. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/contact.py +0 -0
  93. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/control_type_codes.py +0 -0
  94. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/department.py +0 -0
  95. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/__init__.py +0 -0
  96. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
  97. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
  98. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/apply.py +0 -0
  99. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/componentlower.py +0 -0
  100. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/editspec.py +0 -0
  101. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/errors.py +0 -0
  102. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/fieldlower.py +0 -0
  103. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/jsonschema_mini.py +0 -0
  104. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/models.py +0 -0
  105. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/ops.py +0 -0
  106. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/planner.py +0 -0
  107. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/reader.py +0 -0
  108. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/field_normalizer.py +0 -0
  109. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/field_spec_adapter.py +0 -0
  110. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/filter_translator.py +0 -0
  111. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/global_meta.py +0 -0
  112. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/group.py +0 -0
  113. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/icon_index.py +0 -0
  114. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/instance.py +0 -0
  115. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/logger.py +0 -0
  116. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/optionset.py +0 -0
  117. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/page.py +0 -0
  118. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/record.py +0 -0
  119. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/response_crypto.py +0 -0
  120. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/role_perm_builder.py +0 -0
  121. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/session.py +0 -0
  122. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/token_crypto.py +0 -0
  123. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/update.py +0 -0
  124. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/upload.py +0 -0
  125. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/v3/__init__.py +0 -0
  126. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/v3/dispatcher.py +0 -0
  127. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/v3/render.py +0 -0
  128. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/v3/schema.py +0 -0
  129. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/view_spec_adapter.py +0 -0
  130. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/workflow.py +0 -0
  131. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/workflow_node_dsl.py +0 -0
  132. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/worksheet.py +0 -0
  133. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/worksheet_templates.py +0 -0
  134. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/hap_cli.py +0 -0
  135. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/i18n.py +0 -0
  136. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/locale/__init__.py +0 -0
  137. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/locale/messages.json +0 -0
  138. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/locale/messages.schema.json +0 -0
  139. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/__init__.py +0 -0
  140. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/conftest.py +0 -0
  141. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_app_creator.py +0 -0
  142. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_app_editor.py +0 -0
  143. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_app_export_usage.py +0 -0
  144. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_auth_prerelease.py +0 -0
  145. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_bug_036430.py +0 -0
  146. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_chart.py +0 -0
  147. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_cli_access.py +0 -0
  148. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_config_cmd.py +0 -0
  149. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_destructive_confirm_cli.py +0 -0
  150. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_full_e2e.py +0 -0
  151. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_global_meta.py +0 -0
  152. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_i18n.py +0 -0
  153. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_instance_history_cli.py +0 -0
  154. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_integration.py +0 -0
  155. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_integration_approval.py +0 -0
  156. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_integration_approval_actions.py +0 -0
  157. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_integration_calendar.py +0 -0
  158. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_integration_destructive.py +0 -0
  159. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_integration_misc.py +0 -0
  160. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_integration_post.py +0 -0
  161. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_integration_social.py +0 -0
  162. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_integration_v3.py +0 -0
  163. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_integration_workflow.py +0 -0
  164. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
  165. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_multi_profile.py +0 -0
  166. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_org_approval_v3_semantic_cli.py +0 -0
  167. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_org_id_cli.py +0 -0
  168. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_org_id_docs.py +0 -0
  169. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_parameter_conventions.py +0 -0
  170. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
  171. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_record_batch_v3_semantic_cli.py +0 -0
  172. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_record_field_alias.py +0 -0
  173. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_record_v3_semantic_cli.py +0 -0
  174. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_semantic_types_registry.py +0 -0
  175. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_update.py +0 -0
  176. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
  177. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_v3_dispatcher.py +0 -0
  178. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
  179. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_v3_session.py +0 -0
  180. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
  181. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_workflow_list_cli.py +0 -0
  182. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_workflow_v3_semantic_cli.py +0 -0
  183. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
  184. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_worksheet_v3_semantic_cli.py +0 -0
  185. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/utils/__init__.py +0 -0
  186. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/utils/formatting.py +0 -0
  187. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/utils/options.py +0 -0
  188. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/utils/parameter_mapping.py +0 -0
  189. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli.egg-info/SOURCES.txt +0 -0
  190. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli.egg-info/dependency_links.txt +0 -0
  191. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli.egg-info/entry_points.txt +0 -0
  192. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli.egg-info/requires.txt +0 -0
  193. {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli.egg-info/top_level.txt +0 -0
  194. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/__init__.py +0 -0
  195. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/__main__.py +0 -0
  196. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/_wftest.py +0 -0
  197. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/charts.py +0 -0
  198. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/cleanup.py +0 -0
  199. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/compiler.py +0 -0
  200. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/config.py +0 -0
  201. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/errors.py +0 -0
  202. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/executor.py +0 -0
  203. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/fields.py +0 -0
  204. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/hap.py +0 -0
  205. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/recording/__init__.py +0 -0
  206. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/recording/console.py +0 -0
  207. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/recording/jsonl.py +0 -0
  208. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/recording/mirror.py +0 -0
  209. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/recording/report.py +0 -0
  210. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/schema.py +0 -0
  211. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/seed/__init__.py +0 -0
  212. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/seed/cli.py +0 -0
  213. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/seed/executor.py +0 -0
  214. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/seed/template.py +0 -0
  215. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/selftest.py +0 -0
  216. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/smoke.py +0 -0
  217. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/step.py +0 -0
  218. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/steps.py +0 -0
  219. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/store.py +0 -0
  220. {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/workflow_dsl.py +0 -0
  221. {hap_cli-0.8.11 → hap_cli-0.8.13}/setup.cfg +0 -0
  222. {hap_cli-0.8.11 → hap_cli-0.8.13}/setup.py +0 -0
  223. {hap_cli-0.8.11 → hap_cli-0.8.13}/skills/cli/hap-cli-app-editor/scripts/editspec/component.schema.json +0 -0
  224. {hap_cli-0.8.11 → hap_cli-0.8.13}/skills/cli/hap-cli-app-editor/scripts/editspec/custom-action.schema.json +0 -0
  225. {hap_cli-0.8.11 → hap_cli-0.8.13}/skills/cli/hap-cli-app-editor/scripts/editspec/envelope.schema.json +0 -0
  226. {hap_cli-0.8.11 → hap_cli-0.8.13}/skills/cli/hap-cli-app-editor/scripts/editspec/field.schema.json +0 -0
  227. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/INDEX.json +0 -0
  228. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
  229. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
  230. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/create_optionset.yaml +0 -0
  231. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/create_role.yaml +0 -0
  232. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/delete_optionset.yaml +0 -0
  233. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/delete_record.yaml +0 -0
  234. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/delete_role.yaml +0 -0
  235. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
  236. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_app_info.yaml +0 -0
  237. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
  238. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
  239. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
  240. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_record_details.yaml +0 -0
  241. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
  242. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_record_list.yaml +0 -0
  243. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_record_logs.yaml +0 -0
  244. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
  245. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_record_relations.yaml +0 -0
  246. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
  247. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_regions.yaml +0 -0
  248. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_role_details.yaml +0 -0
  249. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_role_list.yaml +0 -0
  250. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/knowledge_search.yaml +0 -0
  251. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
  252. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
  253. {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/update_optionset.yaml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hap-cli
3
- Version: 0.8.11
3
+ Version: 0.8.13
4
4
  Summary: CLI harness for Mingdao HAP - Enterprise no-code platform
5
5
  Author: hap-cli
6
6
  License: Apache-2.0
@@ -1,3 +1,3 @@
1
1
  """CLI harness for Mingdao HAP (hap-cli)."""
2
2
 
3
- __version__ = "0.8.11"
3
+ __version__ = "0.8.13"
@@ -425,8 +425,8 @@ def _normalize_scope(scope: str) -> str:
425
425
  "fav=your favorites, group=posts in a group (requires --group-id)."
426
426
  ),
427
427
  )
428
- @click.option("--account-id", default="", help="Member id (required when --scope=user)")
429
- @click.option("--group-id", default="", help="Group id (required when --scope=group)")
428
+ @click.option("--account-id", default="", help="Show only this member's posts")
429
+ @click.option("--group-id", default="", help="Show only this group's posts")
430
430
  @click.option("-k", "--keywords", default="", help="Keyword filter")
431
431
  @click.option("--start-date", default="", help="Only posts on or after this date (YYYY-MM-DD)")
432
432
  @click.option("--end-date", default="", help="Only posts on or before this date (YYYY-MM-DD)")
@@ -208,6 +208,52 @@ def role_set_permissions(ctx, app_id, role_id, permissions, permission_way):
208
208
  ctx.handle_error(e)
209
209
 
210
210
 
211
+ @role.command("set-chatbots")
212
+ @click.argument("app_id")
213
+ @click.argument("role_id")
214
+ @click.option("--enable-all", is_flag=True,
215
+ help="Grant this role access to every AI assistant in the app.")
216
+ @click.option("--disable-all", is_flag=True,
217
+ help="Remove this role's access to every AI assistant in the app.")
218
+ @click.option("--chatbots-json", default="",
219
+ help='Per-assistant access as a JSON array, e.g. '
220
+ '[{"id":"<assistant-id>","enable":true}].')
221
+ @pass_context
222
+ def role_set_chatbots(ctx, app_id, role_id, enable_all, disable_all, chatbots_json):
223
+ """Set which AI assistants a role can access.
224
+
225
+ A newly created fine-grained role has no AI-assistant access until it
226
+ is granted here. Provide exactly one of --enable-all, --disable-all,
227
+ or --chatbots-json.
228
+ """
229
+ try:
230
+ chosen = sum(bool(x) for x in (enable_all, disable_all, chatbots_json))
231
+ if chosen != 1:
232
+ raise click.UsageError(
233
+ "Provide exactly one of --enable-all, --disable-all, "
234
+ "or --chatbots-json."
235
+ )
236
+ session = ctx.get_session()
237
+ enables = None
238
+ default = None
239
+ if enable_all:
240
+ default = True
241
+ elif disable_all:
242
+ default = False
243
+ else:
244
+ entries = json.loads(chatbots_json)
245
+ enables = {
246
+ e["id"]: bool(e.get("enable", True))
247
+ for e in entries if e.get("id")
248
+ }
249
+ data = role_mod.set_role_chatbots(
250
+ session, app_id, role_id, enables=enables, default=default,
251
+ )
252
+ ctx.output(data, lambda d: click.echo("AI assistant access updated."))
253
+ except Exception as e:
254
+ ctx.handle_error(e)
255
+
256
+
211
257
  @role.command("rename")
212
258
  @click.argument("app_id")
213
259
  @click.argument("role_id")
@@ -3485,6 +3485,28 @@
3485
3485
  },
3486
3486
  "description": "permission_scope=0 时的自定义页面权限明细(页面按逻辑名)。"
3487
3487
  },
3488
+ "chatbot_permissions": {
3489
+ "type": "array",
3490
+ "items": {
3491
+ "type": "object",
3492
+ "required": [
3493
+ "chatbot"
3494
+ ],
3495
+ "additionalProperties": false,
3496
+ "properties": {
3497
+ "chatbot": {
3498
+ "type": "string",
3499
+ "description": "AI 助手逻辑名 -> id。"
3500
+ },
3501
+ "enable": {
3502
+ "type": "boolean",
3503
+ "default": true,
3504
+ "description": "是否可访问该 AI 助手。"
3505
+ }
3506
+ }
3507
+ },
3508
+ "description": "可访问的 AI 助手(chatbot)明细,按 chatbot 逻辑名引用。省略该键 = 该角色可访问应用内全部 AI 助手(默认开放)。一旦提供本数组,则只有列出且 enable=true 的助手可访问,其余助手一律不可访问。"
3509
+ },
3488
3510
  "members": {
3489
3511
  "type": "object",
3490
3512
  "additionalProperties": false,
@@ -67,6 +67,45 @@ def test_store() -> None:
67
67
  pass
68
68
 
69
69
 
70
+ def test_role_chatbot_enables() -> None:
71
+ from hap_cli.core.app_creator.store import Store
72
+ from hap_cli.core.app_creator.steps import _role_chatbot_enables
73
+
74
+ d = Path(tempfile.mkdtemp()) / "appCB"
75
+ s = Store(d)
76
+ s.put_app("appCB", "Demo", "org1", [{"id": "sec1", "name": "分析"}])
77
+ # No chatbots in the app -> nothing to grant.
78
+ assert _role_chatbot_enables(s, {"name": "r"}) == {}
79
+
80
+ s.put_entity("chatbot", "cb1", "订单助手", {"name": "订单助手"})
81
+ s.put_entity("chatbot", "cb2", "客服助手", {"name": "客服助手"})
82
+ # chatbot_permissions omitted -> every assistant is granted (the fix).
83
+ assert _role_chatbot_enables(s, {"name": "r"}) == {"cb1": True, "cb2": True}
84
+ # Provided -> whitelist: only listed enable:true granted, the rest off.
85
+ role = {"name": "r",
86
+ "chatbot_permissions": [{"chatbot": "订单助手", "enable": True}]}
87
+ assert _role_chatbot_enables(s, role) == {"cb1": True, "cb2": False}
88
+ # enable defaults true when omitted; explicit false revokes a listed one.
89
+ role2 = {"name": "r", "chatbot_permissions": [
90
+ {"chatbot": "订单助手", "enable": False},
91
+ {"chatbot": "客服助手"},
92
+ ]}
93
+ assert _role_chatbot_enables(s, role2) == {"cb1": False, "cb2": True}
94
+
95
+
96
+ def test_role_chatbot_permissions_schema() -> None:
97
+ design = {
98
+ "app": {"name": "x"},
99
+ "worksheets": [{"name": "t", "fields": [
100
+ {"type": "Text", "name": "标题", "is_title": True}]}],
101
+ "chatbots": [{"name": "助手"}],
102
+ "roles": [{"name": "r", "permission_scope": "0",
103
+ "chatbot_permissions": [{"chatbot": "助手", "enable": True}]}],
104
+ }
105
+ errs = _vd(design)
106
+ assert not any("chatbot_permissions" in e for e in errs), errs
107
+
108
+
70
109
  def test_schema() -> None:
71
110
  from hap_cli.core.app_creator import schema
72
111
 
@@ -962,6 +962,33 @@ def _build_ws_perm(store: Store, wp: dict[str, Any]) -> dict[str, Any]:
962
962
  }
963
963
 
964
964
 
965
+ def _role_chatbot_enables(store: Store, role: dict[str, Any]) -> dict[str, bool]:
966
+ """Map every app chatbot id -> whether this role may access it.
967
+
968
+ The fine-grained role-create endpoint can't grant AI-assistant
969
+ access, so the role handler patches it in afterwards (via
970
+ ``app role set-chatbots`` -> EditAppRole). Default: every assistant in
971
+ the app is accessible to the role. A role narrows this with
972
+ ``chatbot_permissions`` (a list of ``{chatbot, enable}``); when that
973
+ key is present only the listed assistants with ``enable: true`` are
974
+ granted and all other assistants are revoked.
975
+
976
+ Returns an empty mapping when the app has no chatbots (nothing to do).
977
+ """
978
+ ids = [c["id"] for c in store.list("chatbot") if c.get("id")]
979
+ if not ids:
980
+ return {}
981
+ cp = role.get("chatbot_permissions")
982
+ if cp is None:
983
+ return {cid: True for cid in ids}
984
+ wanted: dict[str, bool] = {}
985
+ for item in cp:
986
+ wanted[store.resolve("chatbot", item["chatbot"])] = bool(
987
+ item.get("enable", True)
988
+ )
989
+ return {cid: bool(wanted.get(cid, False)) for cid in ids}
990
+
991
+
965
992
  @handler("role")
966
993
  def _h_role(ctx: ExecCtx, step: Step) -> StepOutcome:
967
994
  store = _require_store(ctx)
@@ -1021,6 +1048,20 @@ def _h_role(ctx: ExecCtx, step: Step) -> StepOutcome:
1021
1048
  hap.run(m_argv)
1022
1049
  commands.append(m_argv)
1023
1050
 
1051
+ # Grant AI-assistant (chatbot) access. The fine-grained role-create
1052
+ # endpoint has no field for it, so every role would otherwise be unable
1053
+ # to reach the app's assistants. Chatbots are built before roles, so
1054
+ # they already exist in the store here.
1055
+ enables = _role_chatbot_enables(store, role)
1056
+ if role_id and enables:
1057
+ cb_argv = ["app", "role", "set-chatbots", ctx.store.app_id, role_id,
1058
+ "--chatbots-json",
1059
+ json.dumps([{"id": cid, "enable": en}
1060
+ for cid, en in enables.items()],
1061
+ ensure_ascii=False)]
1062
+ hap.run(cb_argv)
1063
+ commands.append(cb_argv)
1064
+
1024
1065
  return StepOutcome(
1025
1066
  created_id=role_id, summary=f"role {name!r} scope={scope}",
1026
1067
  commands=commands, capture_files=[f"role_{role_id}.json", "roles.json"],
@@ -107,11 +107,35 @@ def _paginate_chat_list_for_cli(
107
107
  return sessions[start:end]
108
108
 
109
109
 
110
+ # chatmq session `type` (pd-openweb src/pages/chat/utils/constant.js
111
+ # SESSIONTYPE_*) → the value a caller passes next to reach that session's
112
+ # detail. For inbox buckets this is the `chat messages --category` key
113
+ # (note: type 9 worksheet → `app`, type 7 knowledge → `kc`, matching
114
+ # INBOX_CATEGORY_TYPES). For direct chats it is `user` / `group`, a hint
115
+ # that the detail fetch uses `--with-user` / `--group-id` with this
116
+ # session's `value` instead of `--category`.
117
+ CHAT_SESSION_TYPE_TO_CATEGORY: dict[int, str] = {
118
+ 1: "user",
119
+ 2: "group",
120
+ 3: "system",
121
+ 4: "post",
122
+ 5: "calendar",
123
+ 6: "task",
124
+ 7: "kc",
125
+ 8: "hr",
126
+ 9: "app",
127
+ 10: "workflow",
128
+ }
129
+
130
+
110
131
  def _simplify_chat_session(item: Any) -> Any:
111
132
  """Rename a couple of cryptic chatmq keys to CLI-friendly names.
112
133
 
113
134
  - ``count`` → ``unread`` (unread message count for the session)
114
135
  - ``pid`` → ``orgId`` (the network/organization id this session belongs to)
136
+ - ``category`` is *added* (see ``CHAT_SESSION_TYPE_TO_CATEGORY``) so a
137
+ caller can chain from a session to its message detail without
138
+ re-deriving the mapping.
115
139
 
116
140
  Everything else is passed through unchanged so callers that depend
117
141
  on the wider session shape (msg, top_info, isPush, etc.) keep
@@ -124,6 +148,9 @@ def _simplify_chat_session(item: Any) -> Any:
124
148
  out["unread"] = out.pop("count")
125
149
  if "pid" in out:
126
150
  out["orgId"] = out.pop("pid")
151
+ category = CHAT_SESSION_TYPE_TO_CATEGORY.get(out.get("type"))
152
+ if category:
153
+ out["category"] = category
127
154
  return out
128
155
 
129
156
 
@@ -580,9 +607,12 @@ INBOX_CATEGORY_TYPES: dict[str, str] = {
580
607
 
581
608
  # Discussion `inboxType` values that encode the source reference in
582
609
  # `sourceId` differently. Kept central so adding a new discussion type is a
583
- # one-line change. Source: pd-openweb MSGTYPES enum.
584
- _DISCUSSION_WORKSHEET_TYPES = {41, 42} # WorkSheetRowReply / WorkSheetRowComment
585
- _DISCUSSION_CALENDAR_TYPES = {26, 28} # CalendarMentioned / CalendarComment (AttendanceReply)
610
+ # one-line change. Source: pd-openweb MSGTYPES enum
611
+ # (src/pages/chat/components/Inbox/constants.js).
612
+ # Worksheet-ROW discussions span four subtypes system(40)/mentioned(41)/
613
+ # reply(42)/comment(43) — all carrying sourceId=`<worksheetId>|<rowId>`.
614
+ _DISCUSSION_WORKSHEET_TYPES = {40, 41, 42, 43}
615
+ _DISCUSSION_CALENDAR_TYPES = {26, 28} # CalendarMentioned / CalendarReply
586
616
  _DISCUSSION_TASK_TYPES = {12} # TaskReply
587
617
 
588
618
 
@@ -647,10 +677,28 @@ def _normalize_attachment(att: Any) -> Optional[dict[str, Any]]:
647
677
  return out
648
678
 
649
679
 
650
- def _split_discussion_source(inbox_type: int, source_id: str) -> dict[str, Any]:
651
- """Resolve `discussion.sourceId` into CLI-native ID fields per type.
680
+ def _is_usable_id(value: str) -> bool:
681
+ """An id segment is usable unless it is empty or a literal undefined/null.
682
+
683
+ Mirrors ``buildSourceLink`` (Inbox/util.js), which discards an
684
+ ``extendsId`` containing ``undefined`` / ``null`` rather than building a
685
+ broken link from it.
686
+ """
687
+ return bool(value) and value not in ("undefined", "null")
688
+
652
689
 
653
- - Worksheet-row (41/42): `<worksheetId>|<rowId>`.
690
+ def _split_discussion_source(
691
+ inbox_type: int, source_id: str, extends_id: str = ""
692
+ ) -> dict[str, Any]:
693
+ """Resolve `discussion.sourceId` (+ `extendsId`) into CLI-native ID fields.
694
+
695
+ - Worksheet-row (40/41/42/43): `sourceId` = `<worksheetId>|<rowId>` and
696
+ `extendsId` = `<appId>|<viewId>` (pd-openweb
697
+ src/api/discussion.js, commentListItem.jsx:176, Inbox/util.js
698
+ buildSourceLink). The item-level `appId` on these notifications is the
699
+ stale notification-feed app (often deleted), so the *record's* real app
700
+ must come from `extendsId` — surfacing it here lets the returned dict
701
+ override that stale value at the call site.
654
702
  - Calendar (26/28): `<calendarId>|` (second half always empty).
655
703
  - Task (12): no `|`, whole string is the task id.
656
704
  - Other: keep the raw `sourceId` so future enums don't silently lose info.
@@ -658,11 +706,16 @@ def _split_discussion_source(inbox_type: int, source_id: str) -> dict[str, Any]:
658
706
  source_id = source_id or ""
659
707
  if inbox_type in _DISCUSSION_WORKSHEET_TYPES:
660
708
  worksheet_id, _, row_id = source_id.partition("|")
709
+ app_id, _, view_id = (extends_id or "").partition("|")
661
710
  result: dict[str, Any] = {}
711
+ if _is_usable_id(app_id):
712
+ result["appId"] = app_id
662
713
  if worksheet_id:
663
714
  result["worksheetId"] = worksheet_id
664
715
  if row_id:
665
716
  result["rowId"] = row_id
717
+ if _is_usable_id(view_id):
718
+ result["viewId"] = view_id
666
719
  return result
667
720
  if inbox_type in _DISCUSSION_CALENDAR_TYPES:
668
721
  calendar_id, _, _rest = source_id.partition("|")
@@ -776,6 +829,54 @@ def _extract_hr_ids(text: Any) -> tuple[Optional[str], Optional[str]]:
776
829
  return (aid_m.group(1) if aid_m else None), org_m.group(1)
777
830
 
778
831
 
832
+ # Worksheet-row links embedded in `Message.content` HTML for notifications
833
+ # that carry no `discussion` object — workflow notifications (CC/抄送,
834
+ # fill, approval) and worksheet system messages ("被工作流添加为参与者").
835
+ # Three shapes occur (pd-openweb src/pages/workflow/socket/index.js:124 and
836
+ # Inbox/util.js buildSourceLink); an id segment runs until the next `/`, `?`,
837
+ # quote, or angle bracket. Ordered most-specific first.
838
+ _APP_ROW_RE = re.compile(
839
+ r"/app/([^/?\"'<>\s]+)/([^/?\"'<>\s]+)/([^/?\"'<>\s]+)/row/([^/?\"'<>\s]+)"
840
+ )
841
+ _MOBILE_RECORD_RE = re.compile(
842
+ r"/mobile/record/([^/?\"'<>\s]+)/([^/?\"'<>\s]+)/([^/?\"'<>\s]+)/([^/?\"'<>\s]+)"
843
+ )
844
+ _WS_ROW_RE = re.compile(r"/worksheet/([^/?\"'<>\s]+)/row/([^/?\"'<>\s]+)")
845
+
846
+
847
+ def _extract_record_ref(text: Any) -> dict[str, Any]:
848
+ """Pull the first worksheet-row reference out of notification HTML.
849
+
850
+ Returns any of ``appId`` / ``worksheetId`` / ``viewId`` / ``rowId`` that
851
+ the embedded record link encodes, so Message-carrier notifications
852
+ (which have no structured ``discussion`` block) still surface a record
853
+ the user can jump to. Empty dict when no record link is present.
854
+ """
855
+ if not text:
856
+ return {}
857
+ s = str(text)
858
+ m = _APP_ROW_RE.search(s)
859
+ if m:
860
+ return {
861
+ "appId": m.group(1),
862
+ "worksheetId": m.group(2),
863
+ "viewId": m.group(3),
864
+ "rowId": m.group(4),
865
+ }
866
+ m = _MOBILE_RECORD_RE.search(s)
867
+ if m:
868
+ return {
869
+ "appId": m.group(1),
870
+ "worksheetId": m.group(2),
871
+ "viewId": m.group(3),
872
+ "rowId": m.group(4),
873
+ }
874
+ m = _WS_ROW_RE.search(s)
875
+ if m:
876
+ return {"worksheetId": m.group(1), "rowId": m.group(2)}
877
+ return {}
878
+
879
+
779
880
  def _simplify_scope(scope: Any) -> Optional[dict[str, Any]]:
780
881
  """Reduce ``Post.scope`` to ``{"shareGroups": [{id, name}]}``.
781
882
 
@@ -1065,8 +1166,17 @@ def _simplify_inbox_item(item: dict[str, Any]) -> dict[str, Any]:
1065
1166
  message, comment_simplified = _simplify_comment_payload(discussion, inbox_type)
1066
1167
  if comment_simplified:
1067
1168
  simplified["comment"] = comment_simplified
1068
- # Split the discussion.sourceId into CLI-native id fields.
1069
- simplified.update(_split_discussion_source(inbox_type, discussion.get("sourceId") or ""))
1169
+ # Split the discussion.sourceId (+ extendsId) into CLI-native id
1170
+ # fields. For worksheet-row discussions this yields the record's real
1171
+ # appId, which intentionally overrides the stale item-level appId set
1172
+ # above.
1173
+ simplified.update(
1174
+ _split_discussion_source(
1175
+ inbox_type,
1176
+ discussion.get("sourceId") or "",
1177
+ discussion.get("extendsId") or "",
1178
+ )
1179
+ )
1070
1180
  elif isinstance(message_obj, dict):
1071
1181
  # System-message carrier across all categories (system / task /
1072
1182
  # calendar / kc / hr / app / workflow): the HAP server pre-renders
@@ -1092,6 +1202,16 @@ def _simplify_inbox_item(item: dict[str, Any]) -> dict[str, Any]:
1092
1202
  simplified["accountId"] = hr_account_id
1093
1203
  if hr_org_id:
1094
1204
  simplified["orgId"] = hr_org_id
1205
+ # Workflow (CC/抄送, fill, approval) and worksheet system messages
1206
+ # embed the triggering record as a row link in the HTML — surface
1207
+ # appId/worksheetId/viewId/rowId so the record is directly reachable.
1208
+ # `app.id` (when present) is authoritative for the app, so don't let
1209
+ # the link's appId duplicate it; never clobber ids already set.
1210
+ record_ref = _extract_record_ref(raw_content)
1211
+ if "app" in simplified:
1212
+ record_ref.pop("appId", None)
1213
+ for key, value in record_ref.items():
1214
+ simplified.setdefault(key, value)
1095
1215
 
1096
1216
  # `message` is always present — user-facing primary text. Empty-string OK.
1097
1217
  simplified["message"] = message
@@ -5,11 +5,14 @@ data processing nodes (create/update/delete/search records),
5
5
  code test, webhook test, AIGC test, code templates, and JSON-to-controls.
6
6
  """
7
7
 
8
+ import logging
8
9
  from typing import Any, Optional
9
10
 
10
11
  from hap_cli.core.session import Session
11
12
  from hap_cli.utils.parameter_mapping import parameter_mapping
12
13
 
14
+ logger = logging.getLogger("hap_cli.workflow")
15
+
13
16
 
14
17
  # ── Node Type Constants ──────────────────────────────────────────────────
15
18
 
@@ -391,6 +394,24 @@ def save_node(
391
394
  if select_node_id:
392
395
  data["selectNodeId"] = select_node_id
393
396
  data.update(config)
397
+ # Branch items (flowNodeType 2) have an asymmetric read/write key for
398
+ # their filter: getNodeDetail returns it as ``conditions`` (see
399
+ # pd-openweb Branch/index.jsx, which reads ``result.conditions``), but
400
+ # saveNode only persists it under ``operateCondition`` (the same file
401
+ # sends ``saveNode({ operateCondition: data })``). The value shape is
402
+ # identical. Saving with the read-side ``conditions`` key is silently
403
+ # ignored by the server — the save still reports success but the
404
+ # condition is dropped. Accept the read key so copying a node's
405
+ # read structure straight back round-trips correctly.
406
+ if flow_node_type == 2 and "conditions" in data:
407
+ if "operateCondition" not in data:
408
+ data["operateCondition"] = data["conditions"]
409
+ logger.info(
410
+ "save_node: branch item %s — mapped read-side key "
411
+ "'conditions' to write-side 'operateCondition'", node_id)
412
+ # ``conditions`` is not a saveNode key; drop it so it doesn't ride
413
+ # along as dead weight.
414
+ data.pop("conditions", None)
394
415
  return session.workflow_call("flowNode/saveNode", data)
395
416
 
396
417
 
@@ -72,7 +72,31 @@ def get_post_list(
72
72
  For ``lType == "project"`` (org-wide feed), ``range`` follows the
73
73
  frontend convention when not overridden: no projectId → 0
74
74
  (cross-network), concrete projectId → 2.
75
+
76
+ Effective-scope derivation (mirrors ``postActions.loadPosts``): the
77
+ server keys off ``lType`` to decide which feed to read, and silently
78
+ ignores ``aId`` / ``gID`` when they don't match the declared scope.
79
+ The frontend therefore derives the effective ``lType`` from the
80
+ presence of the ids — a group id forces ``group``, an account id
81
+ forces ``user`` — rather than trusting the nominal list type. We do
82
+ the same so that e.g. ``--account-id`` filters the author feed even
83
+ when the caller left the scope at its org-wide default.
84
+
85
+ ``myself`` is handled specially: the bare ``lType=myself`` scope
86
+ lists the caller's posts but the server drops keyword / date filters
87
+ on it. The product's "我的"(My) tab actually routes through
88
+ ``lType=user`` with the caller's own accountId, so we resolve
89
+ ``myself`` to that shape when the session knows the caller's id
90
+ (falling back to the raw scope otherwise).
75
91
  """
92
+ if list_type == "myself" and not account_id and not group_id and session.account_id:
93
+ account_id = session.account_id
94
+
95
+ if group_id:
96
+ list_type = "group"
97
+ elif account_id:
98
+ list_type = "user"
99
+
76
100
  data: dict[str, Any] = {
77
101
  "fType": source_type or "feed",
78
102
  "lType": list_type,
@@ -238,6 +238,54 @@ def set_role_permissions(
238
238
  )
239
239
 
240
240
 
241
+ def set_role_chatbots(
242
+ session: Session,
243
+ app_id: str,
244
+ role_id: str,
245
+ enables: Optional[dict[str, bool]] = None,
246
+ default: Optional[bool] = None,
247
+ project_id: str = "",
248
+ ) -> dict[str, Any]:
249
+ """Grant or revoke a role's access to the app's AI assistants.
250
+
251
+ The fine-grained role-create endpoint (V3 ``/v3/app/roles``) carries
252
+ no field for AI-assistant access, so a freshly created role can never
253
+ reach the app's assistants. The legacy role model *does* carry a
254
+ ``chatbots`` array (each entry ``{id, name, checked, navigateHide,
255
+ sortIndex, iconUrl}``); the UI saves it through GetRoleDetail ->
256
+ EditAppRole as part of the full ``appRoleModel``.
257
+
258
+ Evidence: pd-openweb ``src/pages/Role/component/RoleSet/SheetTable.jsx``
259
+ (the AI-assistant checkbox toggles ``checked``) and ``SettingForm.jsx``
260
+ (chatbots are saved alongside sheets/pages in the role model).
261
+
262
+ ``enables`` maps a chatbot id to its desired ``checked`` value.
263
+ ``default`` (when not None) is applied to every chatbot in the model
264
+ that is *not* named in ``enables`` — pass ``default=True`` to grant
265
+ all, ``default=False`` to revoke all. Chatbot ids in ``enables`` that
266
+ do not exist in the role model are ignored.
267
+ """
268
+ enables = enables or {}
269
+ detail = get_role_detail(session, app_id, role_id)
270
+ model = dict(detail) if isinstance(detail, dict) else {}
271
+ chatbots = model.get("chatbots") or []
272
+ for cb in chatbots:
273
+ if not isinstance(cb, dict):
274
+ continue
275
+ cid = cb.get("id")
276
+ if cid in enables:
277
+ cb["checked"] = bool(enables[cid])
278
+ elif default is not None:
279
+ cb["checked"] = bool(default)
280
+ model["chatbots"] = chatbots
281
+ data: dict[str, Any] = {
282
+ "appId": app_id, "roleId": role_id, "appRoleModel": model,
283
+ }
284
+ if project_id:
285
+ data["projectId"] = project_id
286
+ return session.api_call("AppManagement", "EditAppRole", data)
287
+
288
+
241
289
  def apply_role(
242
290
  session: Session, app_id: str, remark: str = "",
243
291
  ) -> dict[str, Any]: