@qingflow-tech/qingflow-cli 1.1.41 → 1.1.43-beta.1

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 (51) hide show
  1. package/README.md +4 -4
  2. package/package.json +2 -1
  3. package/pyproject.toml +3 -2
  4. package/skills/qingflow-cli/reference/QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md +1 -1
  5. package/skills/qingflow-cli/reference/task/QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md +1 -1
  6. package/src/qingflow_mcp/app_form_compat.py +115 -0
  7. package/src/qingflow_mcp/app_form_validator.py +199 -0
  8. package/src/qingflow_mcp/builder_facade/app_form.py +2836 -0
  9. package/src/qingflow_mcp/builder_facade/app_form_schema.py +269 -0
  10. package/src/qingflow_mcp/builder_facade/schemas/app_form/manifest.json +37 -0
  11. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/complete.json +1766 -0
  12. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/address.json +76 -0
  13. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/amount.json +76 -0
  14. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/attachment.json +76 -0
  15. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/code_block.json +223 -0
  16. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/data_relation.json +538 -0
  17. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/date.json +76 -0
  18. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/datetime.json +76 -0
  19. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/department.json +149 -0
  20. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/email.json +76 -0
  21. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/link.json +76 -0
  22. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/long_text.json +76 -0
  23. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/member.json +76 -0
  24. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/multi_select.json +92 -0
  25. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/number.json +76 -0
  26. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/phone.json +76 -0
  27. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/q_linker.json +338 -0
  28. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/relation.json +137 -0
  29. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/single_choice.json +92 -0
  30. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/single_select.json +92 -0
  31. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/subtable.json +810 -0
  32. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/text.json +76 -0
  33. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/summary.json +713 -0
  34. package/src/qingflow_mcp/builder_facade/service.py +5 -2
  35. package/src/qingflow_mcp/cli/commands/builder.py +91 -28
  36. package/src/qingflow_mcp/cli/json_io.py +21 -2
  37. package/src/qingflow_mcp/cli/main.py +3 -39
  38. package/src/qingflow_mcp/cloud_auth.py +11 -0
  39. package/src/qingflow_mcp/cloud_context.py +5 -0
  40. package/src/qingflow_mcp/cloud_oauth_proxy.py +453 -0
  41. package/src/qingflow_mcp/cloud_redis.py +25 -0
  42. package/src/qingflow_mcp/config.py +44 -0
  43. package/src/qingflow_mcp/public_surface.py +5 -2
  44. package/src/qingflow_mcp/response_trim.py +8 -0
  45. package/src/qingflow_mcp/server_app_builder.py +38 -86
  46. package/src/qingflow_mcp/server_app_user.py +2 -1
  47. package/src/qingflow_mcp/server_cloud.py +245 -22
  48. package/src/qingflow_mcp/tools/ai_builder_tools.py +93 -100
  49. package/src/qingflow_mcp/tools/app_tools.py +46 -65
  50. package/src/qingflow_mcp/tools/base.py +2 -1
  51. package/src/qingflow_mcp/tools/task_context_tools.py +6 -0
@@ -14,6 +14,8 @@ from ..builder_facade.button_style_catalog import (
14
14
  BUTTON_STYLE_PRESETS,
15
15
  BUTTON_TEXT_COLORS,
16
16
  )
17
+ from ..builder_facade.app_form import AppFormReconciler
18
+ from ..builder_facade.app_form_schema import app_form_schema_registry
17
19
  from ..public_surface import public_builder_contract_tool_names
18
20
  from ..config import DEFAULT_PROFILE
19
21
  from ..errors import QingflowApiError, backend_code_int, backend_code_value_int
@@ -130,10 +132,14 @@ def _inline_form_layout_was_handled(payload: JSONObject) -> bool:
130
132
 
131
133
  PUBLIC_STABLE_FLOW_NODE_TYPES = ["start", "approve", "fill", "copy", "webhook", "end"]
132
134
  BUILDER_APPLY_SCHEMA_VERSION = "builder.apply.v1"
133
- BUILDER_APPLY_TOOL_NAMES = {
134
- "package_apply",
135
+ # TODO(deprecation): Remove these names and their imperative adapters after internal AppForm migration completes.
136
+ LEGACY_IMPERATIVE_APP_FORM_TOOL_NAMES = {
135
137
  "app_schema_apply",
136
138
  "app_layout_apply",
139
+ }
140
+ BUILDER_APPLY_TOOL_NAMES = {
141
+ "package_apply",
142
+ *LEGACY_IMPERATIVE_APP_FORM_TOOL_NAMES,
137
143
  "app_flow_apply",
138
144
  "app_views_apply",
139
145
  "app_custom_buttons_apply",
@@ -161,10 +167,12 @@ class AiBuilderTools(ToolBase):
161
167
  def __init__(self, sessions, backend) -> None:
162
168
  """执行内部辅助逻辑。"""
163
169
  super().__init__(sessions, backend)
170
+ apps = AppTools(sessions, backend)
171
+ packages = PackageTools(sessions, backend)
164
172
  self._facade = AiBuilderFacade(
165
- apps=AppTools(sessions, backend),
173
+ apps=apps,
166
174
  buttons=CustomButtonTools(sessions, backend),
167
- packages=PackageTools(sessions, backend),
175
+ packages=packages,
168
176
  views=ViewTools(sessions, backend),
169
177
  workflows=WorkflowTools(sessions, backend),
170
178
  portals=PortalTools(sessions, backend),
@@ -173,6 +181,11 @@ class AiBuilderTools(ToolBase):
173
181
  directory=DirectoryTools(sessions, backend),
174
182
  solutions=SolutionTools(sessions, backend),
175
183
  )
184
+ self._app_form = AppFormReconciler(
185
+ apps=apps,
186
+ packages=packages,
187
+ registry=app_form_schema_registry(),
188
+ )
176
189
 
177
190
  def _apply_app_batch(self, *, tool_name: str, profile: str, apps: list[JSONObject], apply_one) -> JSONObject:
178
191
  started_at = time.perf_counter()
@@ -367,6 +380,41 @@ class AiBuilderTools(ToolBase):
367
380
  def builder_tool_contract(tool_name: str = "") -> JSONObject:
368
381
  return self.builder_tool_contract(tool_name=tool_name)
369
382
 
383
+ @mcp.tool()
384
+ def app_form_schema(
385
+ schema_version: str | None = None,
386
+ field_type: str | None = None,
387
+ ) -> JSONObject:
388
+ return self.app_form_schema(schema_version=schema_version, field_type=field_type)
389
+
390
+ @mcp.tool()
391
+ def app_form_get(
392
+ profile: str = DEFAULT_PROFILE,
393
+ app_key: str = "",
394
+ schema_version: str | None = None,
395
+ being_draft: bool = False,
396
+ ) -> JSONObject:
397
+ return self.app_form_get(
398
+ profile=profile,
399
+ app_key=app_key,
400
+ schema_version=schema_version,
401
+ being_draft=being_draft,
402
+ )
403
+
404
+ @mcp.tool()
405
+ def app_form_apply(
406
+ profile: str = DEFAULT_PROFILE,
407
+ spec: JSONObject | None = None,
408
+ ) -> JSONObject:
409
+ return self.app_form_apply(profile=profile, spec=spec or {})
410
+
411
+ @mcp.tool(description=self._high_risk_tool_description(operation="delete", target="app configuration"))
412
+ def app_delete(
413
+ profile: str = DEFAULT_PROFILE,
414
+ app_key: str = "",
415
+ ) -> JSONObject:
416
+ return self.app_delete(profile=profile, app_key=app_key)
417
+
370
418
  @mcp.tool()
371
419
  def workspace_icon_catalog_get(profile: str = DEFAULT_PROFILE) -> JSONObject:
372
420
  return self.workspace_icon_catalog_get(profile=profile)
@@ -612,98 +660,6 @@ class AiBuilderTools(ToolBase):
612
660
  ) -> JSONObject:
613
661
  return self.chart_get(profile=profile, chart_id=chart_id)
614
662
 
615
- @mcp.tool()
616
- def app_schema_apply(
617
- profile: str = DEFAULT_PROFILE,
618
- app_key: str = "",
619
- package_id: int | None = None,
620
- app_name: str = "",
621
- app_title: str = "",
622
- icon: str | JSONObject = "",
623
- color: str = "",
624
- icon_name: str | None = None,
625
- icon_color: str | None = None,
626
- icon_config: JSONObject | None = None,
627
- visibility: JSONObject | None = None,
628
- publish: bool = True,
629
- form: list[JSONObject] | None = None,
630
- add_fields: list[JSONObject] | None = None,
631
- update_fields: list[JSONObject] | None = None,
632
- remove_fields: list[JSONObject] | None = None,
633
- apps: list[JSONObject] | None = None,
634
- ) -> JSONObject:
635
- if apps is not None:
636
- if app_key or app_name or app_title or form or add_fields or update_fields or remove_fields:
637
- return _config_failure(
638
- tool_name="app_schema_apply",
639
- message="app_schema_apply multi-app mode accepts package_id plus apps only.",
640
- fix_hint="Put per-app form in apps[].form when using batch mode.",
641
- )
642
- if package_id is None:
643
- return _config_failure(
644
- tool_name="app_schema_apply",
645
- message="app_schema_apply multi-app mode requires package_id.",
646
- fix_hint="Pass package_id and apps[].app_name for new apps, or apps[].app_key for existing apps.",
647
- )
648
- return self.app_schema_apply(
649
- profile=profile,
650
- package_id=package_id,
651
- visibility=visibility,
652
- publish=publish,
653
- apps=apps,
654
- form=None,
655
- add_fields=[],
656
- update_fields=[],
657
- remove_fields=[],
658
- )
659
- has_app_key = bool((app_key or "").strip())
660
- has_app_name = bool((app_name or "").strip())
661
- has_app_title = bool((app_title or "").strip())
662
- has_package_id = package_id is not None
663
- if has_app_key:
664
- if has_package_id:
665
- return _config_failure(
666
- tool_name="app_schema_apply",
667
- message="app_schema_apply edit mode accepts app_key and optional app_name rename only.",
668
- fix_hint="For existing apps, use `app_key` and optionally `app_name`. For create mode, use `package_id + app_name`.",
669
- )
670
- elif not (has_package_id and (has_app_name or has_app_title)):
671
- return _config_failure(
672
- tool_name="app_schema_apply",
673
- message="app_schema_apply create mode requires package_id and app_name.",
674
- fix_hint="Use `app_key` for existing apps, or pass `package_id + app_name` to create a new app.",
675
- )
676
- return self.app_schema_apply(
677
- profile=profile,
678
- app_key=app_key,
679
- package_id=package_id,
680
- app_name=app_name,
681
- app_title=app_title,
682
- icon=icon,
683
- color=color,
684
- icon_name=icon_name,
685
- icon_color=icon_color,
686
- icon_config=icon_config,
687
- visibility=visibility,
688
- publish=publish,
689
- form=form or [],
690
- add_fields=add_fields or [],
691
- update_fields=update_fields or [],
692
- remove_fields=remove_fields or [],
693
- apps=None,
694
- )
695
-
696
- @mcp.tool()
697
- def app_layout_apply(
698
- profile: str = DEFAULT_PROFILE,
699
- app_key: str = "",
700
- mode: str = "merge",
701
- publish: bool = True,
702
- sections: list[JSONObject] | None = None,
703
- apps: list[JSONObject] | None = None,
704
- ) -> JSONObject:
705
- return self.app_layout_apply(profile=profile, app_key=app_key, mode=mode, publish=publish, sections=sections or [], apps=apps)
706
-
707
663
  @mcp.tool()
708
664
  def app_flow_get(
709
665
  profile: str = DEFAULT_PROFILE,
@@ -857,6 +813,39 @@ class AiBuilderTools(ToolBase):
857
813
  expected_package_id=expected_package_id,
858
814
  )
859
815
 
816
+ def app_form_schema(
817
+ self,
818
+ *,
819
+ schema_version: str | None = None,
820
+ field_type: str | None = None,
821
+ ) -> JSONObject:
822
+ """Return the versioned AppForm summary or one field-type schema."""
823
+ return self._app_form.schema(schema_version=schema_version, field_type=field_type)
824
+
825
+ def app_form_get(
826
+ self,
827
+ *,
828
+ profile: str,
829
+ app_key: str,
830
+ schema_version: str | None = None,
831
+ being_draft: bool = False,
832
+ ) -> JSONObject:
833
+ """Read a canonical draft or published AppForm resource."""
834
+ return self._app_form.get(
835
+ profile=profile,
836
+ app_key=app_key,
837
+ schema_version=schema_version,
838
+ being_draft=being_draft,
839
+ )
840
+
841
+ def app_form_apply(self, *, profile: str, spec: JSONObject) -> JSONObject:
842
+ """Reconcile one complete declarative AppForm target."""
843
+ return self._app_form.apply(profile=profile, spec=spec)
844
+
845
+ def app_delete(self, *, profile: str, app_key: str) -> JSONObject:
846
+ """Delete an application explicitly."""
847
+ return self._app_form.delete(profile=profile, app_key=app_key)
848
+
860
849
  def package_list(self, *, profile: str, trial_status: str = "all", query: str = "") -> JSONObject:
861
850
  """执行分组与包相关逻辑。"""
862
851
  normalized_args = {"trial_status": trial_status, "query": query}
@@ -2092,7 +2081,7 @@ class AiBuilderTools(ToolBase):
2092
2081
  remove_fields: list[JSONObject],
2093
2082
  apps: list[JSONObject] | None = None,
2094
2083
  ) -> JSONObject:
2095
- """执行应用相关逻辑。"""
2084
+ """Legacy imperative schema adapter retained only for internal callers pending deletion."""
2096
2085
  icon, color = _normalize_builder_icon_args(
2097
2086
  icon=icon,
2098
2087
  color=color,
@@ -3435,7 +3424,7 @@ class AiBuilderTools(ToolBase):
3435
3424
  sections: list[JSONObject],
3436
3425
  apps: list[JSONObject] | None = None,
3437
3426
  ) -> JSONObject:
3438
- """执行应用相关逻辑。"""
3427
+ """Legacy imperative layout adapter retained only for internal callers pending deletion."""
3439
3428
  if apps is not None:
3440
3429
  return self._apply_app_batch(
3441
3430
  tool_name="app_layout_apply",
@@ -7282,6 +7271,8 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
7282
7271
  "remove_fields": [],
7283
7272
  },
7284
7273
  },
7274
+ # TODO(deprecation): Retained only for internal imperative adapter validation. This contract
7275
+ # is deliberately excluded from builder_tool_contract and must be deleted with the adapter.
7285
7276
  "app_schema_apply": {
7286
7277
  "allowed_keys": [
7287
7278
  "app_key",
@@ -7638,6 +7629,8 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
7638
7629
  },
7639
7630
  "preset_example": {"profile": "default", "app_key": "APP_KEY", "mode": "merge", "preset": "balanced", "sections": []},
7640
7631
  },
7632
+ # TODO(deprecation): Retained only for internal imperative adapter validation. This contract
7633
+ # is deliberately excluded from builder_tool_contract and must be deleted with the adapter.
7641
7634
  "app_layout_apply": {
7642
7635
  "allowed_keys": ["app_key", "mode", "publish", "sections"],
7643
7636
  "aliases": {"overwrite": "replace", "sectionId": "section_id"},
@@ -8172,7 +8165,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
8172
8165
  "allowed_values": {},
8173
8166
  "execution_notes": [
8174
8167
  "returns compact current field configuration for one app",
8175
- "use this before app_schema_apply when you need exact field definitions",
8168
+ "use app_form_get through the qingflow-app-form Skill for editable form definitions",
8176
8169
  "also returns chart_fields from QingBI datasource fields; app_charts_apply field selectors should use chart_fields because record/schema-visible fields and QingBI fields are not the same schema",
8177
8170
  "chart_fields[].field_id supports field_<queId> selectors, while chart_fields[].bi_field_id is the raw QingBI fieldId accepted by report configs",
8178
8171
  "chart_fields[].chart_apply_examples contains copyable semantic app_charts_apply snippets such as count_by_field, filtered_count, and numeric sum_metric",
@@ -8189,7 +8182,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
8189
8182
  "allowed_values": {},
8190
8183
  "execution_notes": [
8191
8184
  "returns compact current layout configuration for one app",
8192
- "use this before app_layout_apply when you need paragraph and row structure",
8185
+ "use app_form_get through the qingflow-app-form Skill for editable form layout",
8193
8186
  ],
8194
8187
  "minimal_example": {
8195
8188
  "profile": "default",
@@ -20,8 +20,11 @@ class AppTools(ToolBase):
20
20
  类型:应用元数据与配置工具。
21
21
  主要职责:
22
22
  1. 查询应用列表、读取应用详情;
23
- 2. 读取与更新应用基础配置、表单结构与发布状态;
24
- 3. 提供应用创建、删除、发布等管理能力。
23
+ 2. 为声明式 AppForm 和 Solution 编排保留底层应用适配器;
24
+ 3. 仅将应用列表与详情注册为本类的 MCP 入口。
25
+
26
+ TODO(deprecation): Remove the imperative app-form adapters after AppForm and Solution
27
+ no longer call them. They are implementation details and must not be registered as MCP tools.
25
28
  """
26
29
 
27
30
  def register(self, mcp: FastMCP) -> None:
@@ -34,58 +37,6 @@ class AppTools(ToolBase):
34
37
  def app_get(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
35
38
  return self.app_get(profile=profile, app_key=app_key)
36
39
 
37
- @mcp.tool()
38
- def app_get_base(profile: str = DEFAULT_PROFILE, app_key: str = "", include_raw: bool = False) -> JSONObject:
39
- return self.app_get_base(profile=profile, app_key=app_key, include_raw=include_raw)
40
-
41
- @mcp.tool(description=self._high_risk_tool_description(operation="update", target="app base settings"))
42
- def app_update_base(profile: str = DEFAULT_PROFILE, app_key: str = "", payload: JSONObject | None = None) -> JSONObject:
43
- return self.app_update_base(profile=profile, app_key=app_key, payload=payload or {})
44
-
45
- @mcp.tool()
46
- def app_get_form_schema(
47
- profile: str = DEFAULT_PROFILE,
48
- app_key: str = "",
49
- form_type: int | str = 1,
50
- being_draft: bool | None = None,
51
- being_apply: bool | None = None,
52
- audit_node_id: int | None = None,
53
- include_raw: bool = False,
54
- ) -> JSONObject:
55
- return self.app_get_form_schema(
56
- profile=profile,
57
- app_key=app_key,
58
- form_type=form_type,
59
- being_draft=being_draft,
60
- being_apply=being_apply,
61
- audit_node_id=audit_node_id,
62
- include_raw=include_raw,
63
- )
64
-
65
- @mcp.tool()
66
- def app_get_edit_version_no(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
67
- return self.app_get_edit_version_no(profile=profile, app_key=app_key)
68
-
69
- @mcp.tool()
70
- def app_edit_finished(profile: str = DEFAULT_PROFILE, app_key: str = "", payload: JSONObject | None = None) -> JSONObject:
71
- return self.app_edit_finished(profile=profile, app_key=app_key, payload=payload or {})
72
-
73
- @mcp.tool()
74
- def app_create(profile: str = DEFAULT_PROFILE, payload: JSONObject | None = None) -> JSONObject:
75
- return self.app_create(profile=profile, payload=payload or {})
76
-
77
- @mcp.tool(description=self._high_risk_tool_description(operation="delete", target="app configuration"))
78
- def app_delete(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
79
- return self.app_delete(profile=profile, app_key=app_key)
80
-
81
- @mcp.tool()
82
- def app_publish(
83
- profile: str = DEFAULT_PROFILE,
84
- app_key: str = "",
85
- payload: JSONObject | None = None,
86
- ) -> JSONObject:
87
- return self.app_publish(profile=profile, app_key=app_key, payload=payload or {})
88
-
89
40
  def app_list(self, *, profile: str, query: str = "", keyword: str = "", ship_auth: bool = False) -> JSONObject:
90
41
  """List current-user visible apps in the selected workspace."""
91
42
  def runner(session_profile, context):
@@ -294,7 +245,7 @@ class AppTools(ToolBase):
294
245
  return self._run(profile, runner, tool_name='应用详情')
295
246
 
296
247
  def app_get_base(self, *, profile: str, app_key: str, include_raw: bool = False) -> JSONObject:
297
- """执行应用相关逻辑。"""
248
+ """Legacy low-level app-form adapter retained for internal callers pending deletion."""
298
249
  self._require_app_key(app_key)
299
250
 
300
251
  def runner(session_profile, context):
@@ -343,7 +294,7 @@ class AppTools(ToolBase):
343
294
  return self._run(profile, runner, tool_name='应用基础信息')
344
295
 
345
296
  def app_update_base(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
346
- """执行应用相关逻辑。"""
297
+ """Legacy low-level app-form adapter retained for internal callers pending deletion."""
347
298
  self._require_app_key(app_key)
348
299
  body = self._require_dict(payload)
349
300
 
@@ -368,7 +319,7 @@ class AppTools(ToolBase):
368
319
  audit_node_id: int | None,
369
320
  include_raw: bool = False,
370
321
  ) -> JSONObject:
371
- """执行应用相关逻辑。"""
322
+ """Legacy low-level app-form adapter retained for internal callers pending deletion."""
372
323
  self._require_app_key(app_key)
373
324
  resolved_form_type = _normalize_form_type(form_type)
374
325
 
@@ -398,7 +349,7 @@ class AppTools(ToolBase):
398
349
  return self._run(profile, runner, tool_name='应用表单结构')
399
350
 
400
351
  def app_get_edit_version_no(self, *, profile: str, app_key: str) -> JSONObject:
401
- """执行应用相关逻辑。"""
352
+ """Legacy low-level app-form adapter retained for internal callers pending deletion."""
402
353
  self._require_app_key(app_key)
403
354
 
404
355
  def runner(session_profile, context):
@@ -446,7 +397,7 @@ class AppTools(ToolBase):
446
397
  return self._run(profile, runner, tool_name='更新应用流程配置')
447
398
 
448
399
  def app_update_form_schema(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
449
- """执行应用相关逻辑。"""
400
+ """Legacy low-level app-form adapter retained for internal callers pending deletion."""
450
401
  self._require_app_key(app_key)
451
402
  body = self._require_dict(payload)
452
403
 
@@ -461,7 +412,7 @@ class AppTools(ToolBase):
461
412
  return self._run(profile, runner, tool_name='更新应用表单结构')
462
413
 
463
414
  def app_edit_finished(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
464
- """执行应用相关逻辑。"""
415
+ """Legacy low-level app-form adapter retained for internal callers pending deletion."""
465
416
  self._require_app_key(app_key)
466
417
  body = self._require_dict(payload)
467
418
 
@@ -471,13 +422,19 @@ class AppTools(ToolBase):
471
422
 
472
423
  return self._run(profile, runner, tool_name='应用编辑完成')
473
424
 
474
- def app_create(self, *, profile: str, payload: JSONObject) -> JSONObject:
475
- """执行应用相关逻辑。"""
425
+ def app_create(
426
+ self,
427
+ *,
428
+ profile: str,
429
+ payload: JSONObject,
430
+ retry_without_qf_version: bool = True,
431
+ ) -> JSONObject:
432
+ """Legacy low-level app-form adapter retained for internal callers pending deletion."""
476
433
  body = self._require_dict(payload)
477
434
 
478
435
  def runner(session_profile, context):
479
436
  attempted_contexts = [context]
480
- if context.qf_version is not None:
437
+ if retry_without_qf_version and context.qf_version is not None:
481
438
  attempted_contexts.append(
482
439
  BackendRequestContext(
483
440
  base_url=context.base_url,
@@ -519,7 +476,7 @@ class AppTools(ToolBase):
519
476
  return self._run(profile, runner, tool_name='创建应用')
520
477
 
521
478
  def app_delete(self, *, profile: str, app_key: str) -> JSONObject:
522
- """执行应用相关逻辑。"""
479
+ """Legacy low-level app-form adapter retained for the declarative delete path pending deletion."""
523
480
  self._require_app_key(app_key)
524
481
 
525
482
  def runner(session_profile, context):
@@ -533,7 +490,7 @@ class AppTools(ToolBase):
533
490
  return self._run(profile, runner, tool_name='删除应用')
534
491
 
535
492
  def app_publish(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
536
- """执行应用相关逻辑。"""
493
+ """Legacy low-level app-form adapter retained for internal callers pending deletion."""
537
494
  self._require_app_key(app_key)
538
495
  body = self._require_dict(payload)
539
496
 
@@ -543,6 +500,30 @@ class AppTools(ToolBase):
543
500
 
544
501
  return self._run(profile, runner, tool_name='发布应用')
545
502
 
503
+ def app_publish_raw(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
504
+ """Legacy low-level publish adapter retained for internal callers pending deletion."""
505
+ self._require_app_key(app_key)
506
+ body = self._require_dict(payload)
507
+
508
+ def runner(session_profile, context):
509
+ response = self.backend.request_with_meta(
510
+ "POST",
511
+ context,
512
+ f"/app/{app_key}/publish",
513
+ json_body=body,
514
+ unwrap=False,
515
+ )
516
+ return {
517
+ "profile": profile,
518
+ "ws_id": session_profile.selected_ws_id,
519
+ "app_key": app_key,
520
+ "http_status": response.http_status,
521
+ "request_id": response.request_id,
522
+ "response": response.data,
523
+ }
524
+
525
+ return self._run(profile, runner, tool_name='发布应用')
526
+
546
527
  def _require_app_key(self, app_key: str) -> None:
547
528
  """执行内部辅助逻辑。"""
548
529
  if not app_key:
@@ -5,7 +5,7 @@ from contextvars import ContextVar
5
5
  from typing import Any, Callable, TypeVar
6
6
 
7
7
  from ..backend_client import BackendRequestContext, BackendClient
8
- from ..cloud_context import current_cloud_context
8
+ from ..cloud_context import current_call_platform, current_cloud_context
9
9
  from ..config import (
10
10
  get_credit_meter_enabled,
11
11
  get_credit_usage_base_url,
@@ -323,6 +323,7 @@ class ToolBase:
323
323
  "scene": "MCP",
324
324
  "aiBiz": "MCP",
325
325
  "extraInfo": tool_name,
326
+ "callPlatform": current_call_platform.get(),
326
327
  },
327
328
  )
328
329
 
@@ -535,6 +535,10 @@ class TaskContextTools(ToolBase):
535
535
  task_context=task_context,
536
536
  fields=field_updates,
537
537
  )
538
+ elif normalized_action == "transfer" and body.get("answers") is None:
539
+ record = task_context.get("record") if isinstance(task_context.get("record"), dict) else {}
540
+ current_answers = record.get("answers") if isinstance(record.get("answers"), list) else []
541
+ prepared_fields = {"merged_answers": current_answers}
538
542
  before_apply_status = ((task_context.get("record") or {}).get("apply_status"))
539
543
  runtime_baseline = None
540
544
  if normalized_action != "save_only":
@@ -704,6 +708,8 @@ class TaskContextTools(ToolBase):
704
708
  action_payload["auditFeedback"] = audit_feedback
705
709
  if merged_answers is not None:
706
710
  action_payload["answers"] = merged_answers
711
+ elif payload.get("answers") is not None:
712
+ action_payload["answers"] = payload["answers"]
707
713
  return self._approval_tools.record_transfer(
708
714
  profile=profile,
709
715
  app_key=app_key,