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.
- {hap_cli-0.8.11 → hap_cli-0.8.13}/PKG-INFO +1 -1
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/__init__.py +1 -1
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/post_cmd.py +2 -2
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/role_cmd.py +46 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/design/design.schema.json +22 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/selftest.py +39 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/steps.py +41 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/chat.py +128 -8
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/flow_node.py +21 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/post.py +24 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/role.py +48 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_core.py +361 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli.egg-info/PKG-INFO +1 -1
- {hap_cli-0.8.11 → hap_cli-0.8.13}/MANIFEST.in +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/README.md +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/app_live_tests/__init__.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/app_live_tests/__main__.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/app_live_tests/config.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/app_live_tests/harness.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/app_live_tests/smoke.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/app_live_tests/state.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/README.md +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/README_CN.md +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/__main__.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/__init__.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/app_cmd.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/app_creator_cmd.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/app_editor_cmd.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/auth_cmd.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/calendar_cmd.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/chart_cmd.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/chat_cmd.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/chatbot_cmd.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/config_cmd.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/contact_cmd.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/department_cmd.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/group_cmd.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/icon_cmd.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/instance_cmd.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/node_cmd.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/optionset_cmd.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/page_cmd.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/record_cmd.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/region_cmd.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/update_cmd.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/upload_cmd.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/v3_registry.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/workflow_cmd.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/commands/worksheet_cmd.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/context.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/__init__.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/action_spec_adapter.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/__init__.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/_hapmeta/__init__.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/_hapmeta/control_type_codes.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/_hapmeta/worksheet_templates.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/charts.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/cleanup.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/compiler.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/config.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/errors.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/examples/minimal.design.json +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/executor.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/fields.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/hap.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/merge.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/recording/__init__.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/recording/console.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/recording/jsonl.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/recording/mirror.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/recording/report.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/schema.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/seed/INSTRUCTIONS.md +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/seed/__init__.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/seed/cli.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/seed/executor.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/seed/resources/attachments.json +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/seed/resources/sample.docx +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/seed/resources/sample.pdf +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/seed/resources/sample_images.json +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/seed/template.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/smoke.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/store.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/validate.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/app_creator/workflow_dsl.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/auth.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/calendar_mod.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/chart.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/chatbot.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/cli_access.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/contact.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/control_type_codes.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/department.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/__init__.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/apply.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/componentlower.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/editspec.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/errors.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/fieldlower.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/jsonschema_mini.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/models.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/ops.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/planner.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/editor/reader.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/field_normalizer.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/field_spec_adapter.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/filter_translator.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/global_meta.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/group.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/icon_index.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/instance.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/logger.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/optionset.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/page.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/record.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/response_crypto.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/role_perm_builder.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/session.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/token_crypto.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/update.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/upload.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/v3/__init__.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/v3/dispatcher.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/v3/render.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/v3/schema.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/view_spec_adapter.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/workflow.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/workflow_node_dsl.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/worksheet.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/core/worksheet_templates.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/hap_cli.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/i18n.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/locale/__init__.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/locale/messages.json +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/locale/messages.schema.json +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/__init__.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/conftest.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_app_creator.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_app_editor.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_app_export_usage.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_auth_prerelease.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_bug_036430.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_chart.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_cli_access.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_config_cmd.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_destructive_confirm_cli.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_full_e2e.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_global_meta.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_i18n.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_instance_history_cli.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_integration.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_integration_approval.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_integration_approval_actions.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_integration_calendar.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_integration_destructive.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_integration_misc.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_integration_post.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_integration_social.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_integration_v3.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_integration_workflow.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_multi_profile.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_org_approval_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_org_id_cli.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_org_id_docs.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_parameter_conventions.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_record_batch_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_record_field_alias.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_record_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_semantic_types_registry.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_update.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_v3_dispatcher.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_v3_session.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_workflow_list_cli.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_workflow_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/tests/test_worksheet_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/utils/__init__.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/utils/formatting.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/utils/options.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli/utils/parameter_mapping.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli.egg-info/SOURCES.txt +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli.egg-info/dependency_links.txt +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli.egg-info/entry_points.txt +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli.egg-info/requires.txt +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/hap_cli.egg-info/top_level.txt +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/__init__.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/__main__.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/_wftest.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/charts.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/cleanup.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/compiler.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/config.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/errors.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/executor.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/fields.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/hap.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/recording/__init__.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/recording/console.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/recording/jsonl.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/recording/mirror.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/recording/report.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/schema.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/seed/__init__.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/seed/cli.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/seed/executor.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/seed/template.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/selftest.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/smoke.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/step.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/steps.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/store.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/live_tests/workflow_dsl.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/setup.cfg +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/setup.py +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/skills/cli/hap-cli-app-editor/scripts/editspec/component.schema.json +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/skills/cli/hap-cli-app-editor/scripts/editspec/custom-action.schema.json +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/skills/cli/hap-cli-app-editor/scripts/editspec/envelope.schema.json +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/skills/cli/hap-cli-app-editor/scripts/editspec/field.schema.json +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/INDEX.json +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/create_optionset.yaml +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/create_role.yaml +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/delete_optionset.yaml +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/delete_record.yaml +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/delete_role.yaml +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_app_info.yaml +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_record_details.yaml +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_record_list.yaml +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_record_logs.yaml +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_record_relations.yaml +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_regions.yaml +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_role_details.yaml +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/get_role_list.yaml +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/knowledge_search.yaml +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
- {hap_cli-0.8.11 → hap_cli-0.8.13}/sources/v3-api-schema/update_optionset.yaml +0 -0
|
@@ -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="
|
|
429
|
-
@click.option("--group-id", default="", help="
|
|
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
|
-
|
|
585
|
-
|
|
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
|
|
651
|
-
"""
|
|
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
|
-
|
|
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
|
|
1069
|
-
|
|
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]:
|