@josephyan/qingflow-app-builder-mcp 1.1.24 → 1.1.26

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 (98) hide show
  1. package/README.md +5 -6
  2. package/docs/local-agent-install.md +6 -57
  3. package/entry_point.py +1 -1
  4. package/npm/bin/qingflow-app-builder-mcp.mjs +33 -2
  5. package/npm/lib/runtime.mjs +101 -21
  6. package/npm/scripts/postinstall.mjs +10 -1
  7. package/package.json +2 -3
  8. package/pyproject.toml +1 -1
  9. package/skills/qingflow-app-builder/SKILL.md +42 -104
  10. package/skills/qingflow-app-builder/references/build-complete-system.md +428 -0
  11. package/skills/qingflow-app-builder/references/build-single-app.md +530 -0
  12. package/skills/qingflow-app-builder/references/create-app.md +11 -30
  13. package/skills/qingflow-app-builder/references/environments.md +1 -1
  14. package/skills/qingflow-app-builder/references/gotchas.md +9 -31
  15. package/skills/qingflow-app-builder/references/match-rules.md +0 -15
  16. package/skills/qingflow-app-builder/references/solution-playbooks.md +0 -10
  17. package/skills/qingflow-app-builder/references/tool-selection.md +23 -38
  18. package/skills/qingflow-app-builder/references/update-flow.md +38 -22
  19. package/skills/qingflow-app-builder/references/update-schema.md +0 -14
  20. package/skills/qingflow-app-builder/references/update-views.md +14 -83
  21. package/skills/qingflow-app-builder-code-integrations/SKILL.md +3 -5
  22. package/skills/qingflow-app-builder-code-integrations/references/code-block.md +1 -1
  23. package/skills/qingflow-app-builder-code-integrations/references/q-linker.md +1 -1
  24. package/skills/qingflow-mcp-setup/SKILL.md +111 -0
  25. package/skills/qingflow-mcp-setup/agents/openai.yaml +4 -0
  26. package/skills/qingflow-mcp-setup/references/claude-desktop.md +34 -0
  27. package/skills/qingflow-mcp-setup/references/environments.md +61 -0
  28. package/skills/qingflow-mcp-setup/references/generic-stdio.md +31 -0
  29. package/skills/qingflow-mcp-setup/scripts/check_local_server.sh +38 -0
  30. package/skills/qingflow-workflow-builder/SKILL.md +96 -0
  31. package/skills/qingflow-workflow-builder/manifest.yaml +8 -0
  32. package/skills/qingflow-workflow-builder/references/01-overview.md +45 -0
  33. package/skills/qingflow-workflow-builder/references/02-update-mode.md +53 -0
  34. package/skills/qingflow-workflow-builder/references/03-flow-patterns.md +57 -0
  35. package/skills/qingflow-workflow-builder/references/04-stage1-business-modeling.md +131 -0
  36. package/skills/qingflow-workflow-builder/references/05-stage2-members-roles.md +29 -0
  37. package/skills/qingflow-workflow-builder/references/06-stage3-build-spec.md +165 -0
  38. package/skills/qingflow-workflow-builder/references/07-stage4-validate-spec.md +33 -0
  39. package/skills/qingflow-workflow-builder/references/08-stage5-apply-verify.md +51 -0
  40. package/skills/qingflow-workflow-builder/references/09-stage6-summary.md +88 -0
  41. package/skills/qingflow-workflow-builder/references/10-node-config-reference.md +93 -0
  42. package/skills/qingflow-workflow-builder/references/11-troubleshooting.md +15 -0
  43. package/skills/qingflow-workflow-builder/scripts/diff_flow_spec.py +275 -0
  44. package/skills/qingflow-workflow-builder/scripts/validate_flow_spec.py +605 -0
  45. package/src/qingflow_mcp/__init__.py +1 -1
  46. package/src/qingflow_mcp/__main__.py +2 -6
  47. package/src/qingflow_mcp/builder_facade/models.py +108 -485
  48. package/src/qingflow_mcp/builder_facade/service.py +1686 -6674
  49. package/src/qingflow_mcp/cli/commands/builder.py +364 -322
  50. package/src/qingflow_mcp/cli/commands/chart.py +1 -1
  51. package/src/qingflow_mcp/cli/commands/common.py +3 -12
  52. package/src/qingflow_mcp/cli/commands/exports.py +2 -2
  53. package/src/qingflow_mcp/cli/commands/imports.py +3 -3
  54. package/src/qingflow_mcp/cli/commands/portal.py +2 -2
  55. package/src/qingflow_mcp/cli/commands/record.py +27 -101
  56. package/src/qingflow_mcp/cli/commands/task.py +47 -28
  57. package/src/qingflow_mcp/cli/commands/view.py +1 -1
  58. package/src/qingflow_mcp/cli/context.py +3 -0
  59. package/src/qingflow_mcp/cli/formatters.py +16 -784
  60. package/src/qingflow_mcp/cli/main.py +33 -117
  61. package/src/qingflow_mcp/errors.py +2 -43
  62. package/src/qingflow_mcp/public_surface.py +17 -26
  63. package/src/qingflow_mcp/response_trim.py +17 -81
  64. package/src/qingflow_mcp/server.py +12 -14
  65. package/src/qingflow_mcp/server_app_builder.py +36 -79
  66. package/src/qingflow_mcp/server_app_user.py +16 -22
  67. package/src/qingflow_mcp/session_store.py +7 -11
  68. package/src/qingflow_mcp/solution/compiler/__init__.py +1 -3
  69. package/src/qingflow_mcp/solution/executor.py +18 -245
  70. package/src/qingflow_mcp/tools/ai_builder_tools.py +1396 -3100
  71. package/src/qingflow_mcp/tools/app_tools.py +43 -184
  72. package/src/qingflow_mcp/tools/approval_tools.py +35 -197
  73. package/src/qingflow_mcp/tools/auth_tools.py +16 -92
  74. package/src/qingflow_mcp/tools/code_block_tools.py +40 -298
  75. package/src/qingflow_mcp/tools/custom_button_tools.py +10 -64
  76. package/src/qingflow_mcp/tools/directory_tools.py +72 -236
  77. package/src/qingflow_mcp/tools/export_tools.py +34 -244
  78. package/src/qingflow_mcp/tools/feedback_tools.py +0 -9
  79. package/src/qingflow_mcp/tools/file_tools.py +3 -9
  80. package/src/qingflow_mcp/tools/import_tools.py +49 -336
  81. package/src/qingflow_mcp/tools/navigation_tools.py +12 -91
  82. package/src/qingflow_mcp/tools/package_tools.py +6 -118
  83. package/src/qingflow_mcp/tools/portal_tools.py +3 -39
  84. package/src/qingflow_mcp/tools/qingbi_report_tools.py +7 -116
  85. package/src/qingflow_mcp/tools/record_tools.py +360 -1145
  86. package/src/qingflow_mcp/tools/resource_read_tools.py +39 -188
  87. package/src/qingflow_mcp/tools/role_tools.py +9 -80
  88. package/src/qingflow_mcp/tools/solution_tools.py +45 -59
  89. package/src/qingflow_mcp/tools/task_context_tools.py +158 -662
  90. package/src/qingflow_mcp/tools/task_tools.py +29 -113
  91. package/src/qingflow_mcp/tools/view_tools.py +3 -106
  92. package/src/qingflow_mcp/tools/workflow_tools.py +4 -48
  93. package/src/qingflow_mcp/tools/workspace_tools.py +3 -71
  94. package/npm/bin/qingflow-skills.mjs +0 -5
  95. package/skills/qingflow-app-builder/references/complete-system-development-guide.md +0 -172
  96. package/skills/qingflow-app-builder/references/single-app-development-guide.md +0 -87
  97. package/skills/qingflow-app-builder/scripts/validate_system_build_summary.py +0 -124
  98. package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +0 -173
@@ -5,7 +5,7 @@ from typing import cast
5
5
  from mcp.server.fastmcp import FastMCP
6
6
 
7
7
  from ..config import DEFAULT_PROFILE, DEFAULT_RECORD_LIST_TYPE
8
- from ..errors import QingflowApiError, backend_code_int, backend_code_value_int, is_auth_like_error, message_looks_like_invalid_token, raise_tool_error
8
+ from ..errors import QingflowApiError, raise_tool_error
9
9
  from ..json_types import JSONObject, JSONValue
10
10
  from .base import tool_cn_name
11
11
  from .record_tools import (
@@ -19,11 +19,9 @@ from .record_tools import (
19
19
  FieldIndex,
20
20
  FormField,
21
21
  RecordTools,
22
- _build_answer_backed_field_index,
23
22
  _coerce_count,
24
23
  _collect_question_relations,
25
24
  _field_ref_payload,
26
- _merge_field_indexes,
27
25
  _normalize_optional_text,
28
26
  _relation_ids_from_answer,
29
27
  _stringify_json,
@@ -33,7 +31,6 @@ from .record_tools import (
33
31
  CODE_BLOCK_QUE_TYPE = 26
34
32
  CODE_BLOCK_RELATION_TYPE = 3
35
33
  SUPPORTED_CODE_BLOCK_ROLES = {1, 2, 3, 5}
36
- _CODE_BLOCK_SCHEMA_PERMISSION_CODES = {40002, 40027, 404}
37
34
 
38
35
 
39
36
  class CodeBlockTools(RecordTools):
@@ -68,48 +65,17 @@ class CodeBlockTools(RecordTools):
68
65
  self._form_cache[cache_key] = normalized
69
66
  return normalized
70
67
 
71
- def _get_code_block_relation_schema_optional(
72
- self,
73
- profile: str,
74
- context, # type: ignore[no-untyped-def]
75
- app_key: str,
76
- *,
77
- force_refresh: bool,
78
- warnings: list[JSONObject],
79
- ) -> JSONObject:
80
- try:
81
- return self._get_code_block_relation_schema(
82
- profile,
83
- context,
84
- app_key,
85
- force_refresh=force_refresh,
86
- )
87
- except QingflowApiError as exc:
88
- if not _is_optional_code_block_schema_error(exc):
89
- raise
90
- warnings.append(
91
- {
92
- "code": "CODE_BLOCK_SCHEMA_UNAVAILABLE",
93
- "message": "applicant form schema was not readable in this permission context; code-block execution will use record/task answers and skip schema-bound relation writeback.",
94
- "backend_code": exc.backend_code,
95
- "http_status": exc.http_status,
96
- "request_id": exc.request_id,
97
- }
98
- )
99
- return {}
100
-
101
68
  def register(self, mcp: FastMCP) -> None:
102
69
  """注册当前工具到 MCP 服务。"""
103
70
  super().register(mcp)
104
71
 
105
72
  @mcp.tool()
106
73
  def record_code_block_schema_get(
107
- profile: str = DEFAULT_PROFILE,
108
74
  app_key: str = "",
109
75
  output_profile: str = "normal",
110
76
  ) -> JSONObject:
111
77
  return self.record_code_block_schema_get_public(
112
- profile=profile,
78
+ profile=DEFAULT_PROFILE,
113
79
  app_key=app_key,
114
80
  output_profile=output_profile,
115
81
  )
@@ -118,8 +84,7 @@ class CodeBlockTools(RecordTools):
118
84
  description=(
119
85
  "Run a form code-block field against the current record data, parse alias results, and optionally "
120
86
  "reuse Qingflow's existing relation-calculation chain to compute bound outputs and write them back. "
121
- "Use record_code_block_schema_get when field selection or binding diagnostics are unclear; "
122
- "if the exact code-block field id is known from record/task detail, run directly. "
87
+ "Use record_code_block_schema_get first and choose an exact code-block field selector. "
123
88
  "For safe debugging, pass apply_writeback=false to inspect parsed results without writing back."
124
89
  )
125
90
  )
@@ -128,7 +93,6 @@ class CodeBlockTools(RecordTools):
128
93
  app_key: str = "",
129
94
  record_id: str = "",
130
95
  code_block_field: str = "",
131
- view_id: str | None = None,
132
96
  role: int = 1,
133
97
  workflow_node_id: int | None = None,
134
98
  answers: list[JSONObject] | None = None,
@@ -144,7 +108,6 @@ class CodeBlockTools(RecordTools):
144
108
  app_key=app_key,
145
109
  record_id=record_id,
146
110
  code_block_field=code_block_field,
147
- view_id=view_id,
148
111
  role=role,
149
112
  workflow_node_id=workflow_node_id,
150
113
  answers=answers or [],
@@ -170,45 +133,8 @@ class CodeBlockTools(RecordTools):
170
133
  normalized_output_profile = self._normalize_public_output_profile(output_profile)
171
134
 
172
135
  def runner(session_profile, context):
173
- try:
174
- relation_schema = self._get_code_block_relation_schema(profile, context, app_key, force_refresh=False)
175
- index = self._get_applicant_top_level_field_index(profile, context, app_key, force_refresh=False)
176
- except QingflowApiError as exc:
177
- if not _is_optional_code_block_schema_error(exc):
178
- raise
179
- return {
180
- "profile": profile,
181
- "ws_id": session_profile.selected_ws_id,
182
- "ok": False,
183
- "status": "failed",
184
- "error_code": "CODE_BLOCK_SCHEMA_UNAVAILABLE",
185
- "message": (
186
- "applicant form schema was not readable in this permission context; "
187
- "record schema code-block is only a diagnostic helper. "
188
- "If the code-block field is known from record/task detail, run record code-block-run directly."
189
- ),
190
- "backend_code": exc.backend_code,
191
- "http_status": exc.http_status,
192
- "request_id": exc.request_id,
193
- "request_route": self._request_route_payload(context),
194
- "warnings": [
195
- {
196
- "code": "CODE_BLOCK_SCHEMA_UNAVAILABLE",
197
- "message": "schema diagnostic unavailable; code-block run can still use record/task answers when code_block_field is known.",
198
- "backend_code": exc.backend_code,
199
- "http_status": exc.http_status,
200
- "request_id": exc.request_id,
201
- }
202
- ],
203
- "app_key": app_key,
204
- "schema_scope": "code_block_ready",
205
- "code_block_fields": [],
206
- "input_fields": [],
207
- "suggested_next_call": {
208
- "tool_name": "record_code_block_run",
209
- "required": ["app_key", "record_id", "code_block_field"],
210
- },
211
- }
136
+ relation_schema = self._get_code_block_relation_schema(profile, context, app_key, force_refresh=False)
137
+ index = self._get_applicant_top_level_field_index(profile, context, app_key, force_refresh=False)
212
138
  input_fields = [
213
139
  self._ready_schema_field_payload(
214
140
  profile,
@@ -273,7 +199,6 @@ class CodeBlockTools(RecordTools):
273
199
  app_key: str,
274
200
  record_id: int | str,
275
201
  code_block_field: str,
276
- view_id: str | None = None,
277
202
  role: int = 1,
278
203
  workflow_node_id: int | None = None,
279
204
  answers: list[JSONObject] | None = None,
@@ -295,42 +220,14 @@ class CodeBlockTools(RecordTools):
295
220
  raise_tool_error(QingflowApiError.config_error("code_block_field is required"))
296
221
 
297
222
  def runner(session_profile, context):
298
- warnings: list[JSONObject] = []
299
- current_answers = self._load_record_answers_for_code_block(
300
- context,
301
- profile=profile,
302
- app_key=app_key,
303
- apply_id=normalized_record_id,
304
- view_id=view_id,
305
- role=role,
306
- audit_node_id=workflow_node_id,
307
- )
308
- answer_index = _build_answer_backed_field_index(current_answers)
309
- schema_index: FieldIndex | None = None
310
- relation_schema = self._get_code_block_relation_schema_optional(
223
+ relation_schema = self._get_code_block_relation_schema(
311
224
  profile,
312
225
  context,
313
226
  app_key,
314
227
  force_refresh=force_refresh_form,
315
- warnings=warnings,
316
228
  )
317
- try:
318
- schema_index = self._get_field_index(profile, context, app_key, force_refresh=force_refresh_form)
319
- except QingflowApiError as exc:
320
- if not _is_optional_code_block_schema_error(exc):
321
- raise
322
- if not any(item.get("code") == "CODE_BLOCK_SCHEMA_UNAVAILABLE" for item in warnings):
323
- warnings.append(
324
- {
325
- "code": "CODE_BLOCK_SCHEMA_UNAVAILABLE",
326
- "message": "applicant form schema was not readable in this permission context; code-block execution will use record/task answers and skip schema-bound relation writeback.",
327
- "backend_code": exc.backend_code,
328
- "http_status": exc.http_status,
329
- "request_id": exc.request_id,
330
- }
331
- )
332
- index = _merge_field_indexes(schema_index, answer_index) if schema_index is not None else answer_index
333
- code_block = self._resolve_code_block_field_for_run(code_block_field, index)
229
+ index = self._get_field_index(profile, context, app_key, force_refresh=force_refresh_form)
230
+ code_block = self._resolve_field_selector(code_block_field, index, location="code_block_field")
334
231
  if code_block.que_type != CODE_BLOCK_QUE_TYPE:
335
232
  raise_tool_error(
336
233
  QingflowApiError(
@@ -344,6 +241,14 @@ class CodeBlockTools(RecordTools):
344
241
  },
345
242
  )
346
243
  )
244
+
245
+ current_answers = self._load_record_answers_for_code_block(
246
+ context,
247
+ app_key=app_key,
248
+ apply_id=normalized_record_id,
249
+ role=role,
250
+ audit_node_id=workflow_node_id,
251
+ )
347
252
  override_answers = (
348
253
  self._resolve_answers(
349
254
  profile,
@@ -352,13 +257,12 @@ class CodeBlockTools(RecordTools):
352
257
  answers=answers or [],
353
258
  fields=fields or {},
354
259
  force_refresh_form=force_refresh_form,
355
- field_index_override=index,
356
260
  )
357
261
  if answers or fields
358
262
  else []
359
263
  )
360
264
  merged_answers = self._merge_record_answers(current_answers, override_answers) if override_answers else current_answers
361
- key_que_values = self._answers_to_open_match_values(merged_answers, index, exclude_que_ids={code_block.que_id})
265
+ key_que_values = self._answers_to_open_match_values(merged_answers, index)
362
266
  run_body: JSONObject = {
363
267
  "role": role,
364
268
  "manual": bool(manual),
@@ -385,7 +289,6 @@ class CodeBlockTools(RecordTools):
385
289
  relation_items: list[JSONObject] = []
386
290
  calculated_answers: list[JSONObject] = []
387
291
  relation_result: JSONObject | None = None
388
- relation_transport_error: JSONObject | None = None
389
292
  if relation_target_fields:
390
293
  relation_body: JSONObject = {
391
294
  "role": role,
@@ -403,38 +306,26 @@ class CodeBlockTools(RecordTools):
403
306
  relation_result = self.backend.request("POST", context, relation_route, json_body=relation_body)
404
307
  relation_items = _relation_result_items(relation_result)
405
308
  except QingflowApiError as exc:
406
- if exc.http_status == 404:
407
- relation_route = "/que/actuator"
408
- try:
409
- relation_result = self.backend.request("POST", context, relation_route, json_body=relation_body)
410
- relation_items = _relation_result_items(relation_result)
411
- except QingflowApiError as fallback_exc:
412
- relation_transport_error = _code_block_transport_error(fallback_exc)
413
- else:
414
- relation_transport_error = _code_block_transport_error(exc)
415
- if relation_transport_error is None and not relation_items:
309
+ if exc.http_status != 404:
310
+ raise
311
+ relation_route = "/que/actuator"
312
+ relation_result = self.backend.request("POST", context, relation_route, json_body=relation_body)
313
+ relation_items = _relation_result_items(relation_result)
314
+ if not relation_items:
416
315
  # Keep compatibility with legacy runtime deployments and lightweight test doubles
417
316
  # that still stub the older relation-calculation route only.
418
317
  relation_route = "/que/actuator"
419
- try:
420
- relation_result = self.backend.request("POST", context, relation_route, json_body=relation_body)
421
- relation_items = _relation_result_items(relation_result)
422
- relation_transport_error = None
423
- except QingflowApiError as exc:
424
- relation_transport_error = relation_transport_error or _code_block_transport_error(exc)
318
+ relation_result = self.backend.request("POST", context, relation_route, json_body=relation_body)
319
+ relation_items = _relation_result_items(relation_result)
425
320
  relation_errors = _relation_result_errors(relation_items)
426
321
  calculated_answers = _relation_result_answers(relation_items)
427
322
  write_result: JSONObject | None = None
428
- write_error: JSONObject | None = None
429
323
  verification: JSONObject | None = None
430
324
  writeback_attempted = False
431
325
  writeback_applied = False
432
326
  status = "completed"
433
327
  ok = True
434
- if relation_transport_error is not None:
435
- status = "relation_failed"
436
- ok = False
437
- elif relation_errors:
328
+ if relation_errors:
438
329
  status = "relation_failed"
439
330
  ok = False
440
331
  elif not apply_writeback:
@@ -444,28 +335,21 @@ class CodeBlockTools(RecordTools):
444
335
  if workflow_node_id is not None:
445
336
  write_body["auditNodeId"] = workflow_node_id
446
337
  writeback_attempted = True
447
- try:
448
- write_result = cast(
449
- JSONObject,
450
- self.backend.request(
451
- "POST",
452
- context,
453
- f"/app/{app_key}/apply/{normalized_record_id}",
454
- json_body=write_body,
455
- ),
456
- )
457
- writeback_applied = True
458
- except QingflowApiError as exc:
459
- write_error = _code_block_transport_error(exc)
460
- status = "writeback_failed"
461
- ok = False
462
- if writeback_applied and verify_writeback:
338
+ write_result = cast(
339
+ JSONObject,
340
+ self.backend.request(
341
+ "POST",
342
+ context,
343
+ f"/app/{app_key}/apply/{normalized_record_id}",
344
+ json_body=write_body,
345
+ ),
346
+ )
347
+ writeback_applied = True
348
+ if verify_writeback:
463
349
  verification = self._verify_code_block_writeback_result(
464
350
  context,
465
- profile=profile,
466
351
  app_key=app_key,
467
352
  apply_id=normalized_record_id,
468
- view_id=view_id,
469
353
  expected_answers=calculated_answers,
470
354
  index=index,
471
355
  role=role,
@@ -473,16 +357,7 @@ class CodeBlockTools(RecordTools):
473
357
  )
474
358
  if not bool(verification.get("verified")):
475
359
  status = "verification_failed"
476
- ok = True
477
- warnings.append(
478
- {
479
- "code": "CODE_BLOCK_WRITEBACK_VERIFICATION_FAILED",
480
- "message": (
481
- "code-block execution and writeback completed, but field-level readback "
482
- "could not verify the written values; do not treat this as writeback denial."
483
- ),
484
- }
485
- )
360
+ ok = False
486
361
  else:
487
362
  status = "no_writeback"
488
363
  response: JSONObject = {
@@ -494,14 +369,9 @@ class CodeBlockTools(RecordTools):
494
369
  "apply_id": normalized_record_id,
495
370
  "status": status,
496
371
  "ok": ok,
497
- "write_executed": writeback_applied,
498
- "write_succeeded": writeback_applied,
499
- "safe_to_retry": not writeback_applied,
500
- "warnings": warnings,
501
372
  "code_block_field": _field_ref_payload(code_block),
502
373
  "execution": {
503
374
  "executed": True,
504
- "view_id": _normalize_optional_text(view_id),
505
375
  "role": role,
506
376
  "workflow_node_id": workflow_node_id,
507
377
  "manual": bool(manual),
@@ -519,7 +389,6 @@ class CodeBlockTools(RecordTools):
519
389
  "calculated_answer_count": len(calculated_answers),
520
390
  "calculated_answers_preview": calculated_answers,
521
391
  "errors": relation_errors,
522
- "transport_error": relation_transport_error,
523
392
  },
524
393
  "writeback": {
525
394
  "enabled": bool(apply_writeback),
@@ -529,15 +398,10 @@ class CodeBlockTools(RecordTools):
529
398
  "verify_writeback": verify_writeback,
530
399
  "write_verified": bool(verification.get("verified")) if verification is not None else None,
531
400
  "result": write_result,
532
- "error": write_error,
533
401
  "verification": verification,
534
402
  },
535
403
  "resource": {"apply_id": normalized_record_id},
536
404
  }
537
- if not ok:
538
- failure_error = relation_transport_error if relation_transport_error is not None else write_error
539
- failure_context = "relation" if relation_transport_error is not None else "writeback"
540
- response.update(_code_block_failure_fields(failure_error, context=failure_context))
541
405
  if normalized_output_profile == "verbose":
542
406
  response["debug"] = {
543
407
  "run_body": run_body,
@@ -551,66 +415,16 @@ class CodeBlockTools(RecordTools):
551
415
 
552
416
  return self._run_record_tool(profile, runner)
553
417
 
554
- def _resolve_code_block_field_for_run(self, selector: str | int, index: FieldIndex) -> FormField:
555
- field_id = _coerce_count(selector)
556
- if field_id is not None and str(field_id) not in index.by_id:
557
- return FormField(
558
- que_id=field_id,
559
- que_title=str(field_id),
560
- que_type=CODE_BLOCK_QUE_TYPE,
561
- required=False,
562
- readonly=False,
563
- system=False,
564
- options=[],
565
- aliases=[],
566
- target_app_key=None,
567
- target_app_name_hint=None,
568
- member_select_scope_type=None,
569
- member_select_scope=None,
570
- dept_select_scope_type=None,
571
- dept_select_scope=None,
572
- raw={"queId": field_id, "queTitle": str(field_id), "queType": CODE_BLOCK_QUE_TYPE},
573
- )
574
- return self._resolve_field_selector(selector, index, location="code_block_field")
575
-
576
418
  def _load_record_answers_for_code_block(
577
419
  self,
578
420
  context, # type: ignore[no-untyped-def]
579
421
  *,
580
- profile: str,
581
422
  app_key: str,
582
423
  apply_id: int,
583
- view_id: str | None,
584
424
  role: int,
585
425
  audit_node_id: int | None,
586
426
  ) -> list[JSONObject]:
587
427
  """执行内部辅助逻辑。"""
588
- normalized_view_id = _normalize_optional_text(view_id)
589
- if normalized_view_id:
590
- try:
591
- resolved_view, _warnings = self._resolve_accessible_view_route(
592
- profile,
593
- context,
594
- app_key,
595
- view_id=normalized_view_id,
596
- list_type=None,
597
- view_key=None,
598
- view_name=None,
599
- allow_default=False,
600
- )
601
- record, _used_list_type, _used_role = self._record_get_apply_detail(
602
- context,
603
- app_key=app_key,
604
- record_id=apply_id,
605
- resolved_view=resolved_view,
606
- audit_node_id=audit_node_id,
607
- )
608
- answers = record.get("answers") if isinstance(record, dict) else None
609
- return [item for item in answers if isinstance(item, dict)] if isinstance(answers, list) else []
610
- except QingflowApiError as exc:
611
- if not _is_optional_code_block_record_read_error(exc):
612
- raise
613
-
614
428
  last_error: QingflowApiError | None = None
615
429
  for list_type in self._INTERNAL_GET_LIST_TYPE_FALLBACKS:
616
430
  params: JSONObject = {"role": role, "listType": list_type}
@@ -622,28 +436,19 @@ class CodeBlockTools(RecordTools):
622
436
  return [item for item in answers if isinstance(item, dict)] if isinstance(answers, list) else []
623
437
  except QingflowApiError as exc:
624
438
  last_error = exc
625
- if _is_code_block_permission_error(exc):
439
+ if exc.backend_code == 40002:
626
440
  continue
627
441
  raise
628
442
  if last_error is not None:
629
443
  raise last_error
630
444
  raise_tool_error(QingflowApiError.config_error("record answers could not be loaded for code-block execution"))
631
445
 
632
- def _answers_to_open_match_values(
633
- self,
634
- answers: list[JSONObject],
635
- index: FieldIndex,
636
- *,
637
- exclude_que_ids: set[int] | None = None,
638
- ) -> list[JSONObject]:
446
+ def _answers_to_open_match_values(self, answers: list[JSONObject], index: FieldIndex) -> list[JSONObject]:
639
447
  """执行内部辅助逻辑。"""
640
448
  values: list[JSONObject] = []
641
449
  for answer in answers:
642
450
  if not isinstance(answer, dict):
643
451
  continue
644
- que_id = _coerce_count(answer.get("queId", answer.get("que_id")))
645
- if que_id is not None and exclude_que_ids is not None and que_id in exclude_que_ids:
646
- continue
647
452
  open_match = self._answer_to_open_match_value(answer, index)
648
453
  if open_match is None:
649
454
  continue
@@ -702,10 +507,8 @@ class CodeBlockTools(RecordTools):
702
507
  self,
703
508
  context, # type: ignore[no-untyped-def]
704
509
  *,
705
- profile: str,
706
510
  app_key: str,
707
511
  apply_id: int,
708
- view_id: str | None,
709
512
  expected_answers: list[JSONObject],
710
513
  index: FieldIndex,
711
514
  role: int,
@@ -723,10 +526,8 @@ class CodeBlockTools(RecordTools):
723
526
  )
724
527
  actual_answers = self._load_record_answers_for_code_block(
725
528
  context,
726
- profile=profile,
727
529
  app_key=app_key,
728
530
  apply_id=apply_id,
729
- view_id=view_id,
730
531
  role=role,
731
532
  audit_node_id=audit_node_id,
732
533
  )
@@ -818,42 +619,6 @@ def _normalize_code_block_value_item(value: JSONValue, field: FormField) -> str
818
619
  return text if text is not None else None
819
620
 
820
621
 
821
- def _code_block_transport_error(error: QingflowApiError) -> JSONObject:
822
- payload: JSONObject = {
823
- "category": error.category,
824
- "message": error.message,
825
- }
826
- if error.backend_code is not None:
827
- payload["backend_code"] = error.backend_code
828
- if error.http_status is not None:
829
- payload["http_status"] = error.http_status
830
- if error.request_id:
831
- payload["request_id"] = error.request_id
832
- if error.details:
833
- payload["details"] = error.details
834
- return payload
835
-
836
-
837
- def _code_block_failure_fields(error: JSONObject | None, *, context: str) -> JSONObject:
838
- default_code = "CODE_BLOCK_RELATION_FAILED" if context == "relation" else "CODE_BLOCK_WRITEBACK_FAILED"
839
- permission_code = "CODE_BLOCK_RELATION_PERMISSION_DENIED" if context == "relation" else "CODE_BLOCK_WRITEBACK_PERMISSION_DENIED"
840
- payload: JSONObject = {
841
- "error_code": default_code,
842
- }
843
- if not isinstance(error, dict):
844
- return payload
845
- category = str(error.get("category") or "").strip().lower()
846
- http_status = backend_code_value_int(error.get("http_status"))
847
- if category == "auth" or http_status == 401 or message_looks_like_invalid_token(error.get("message")):
848
- payload["error_code"] = "AUTH_REQUIRED"
849
- elif backend_code_value_int(error.get("backend_code")) in {40002, 40027}:
850
- payload["error_code"] = permission_code
851
- for key in ("category", "backend_code", "http_status", "request_id"):
852
- if key in error:
853
- payload[key] = error.get(key)
854
- return payload
855
-
856
-
857
622
  def _selector_numeric_or_text(value: JSONValue, keys: tuple[str, ...], *, allow_text: bool) -> str | None:
858
623
  numeric = _coerce_count(value)
859
624
  if numeric is not None:
@@ -1010,26 +775,3 @@ def _relation_result_errors(items: list[JSONObject]) -> list[JSONObject]:
1010
775
  }
1011
776
  )
1012
777
  return errors
1013
-
1014
-
1015
- def _is_optional_code_block_record_read_error(error: QingflowApiError) -> bool:
1016
- if is_auth_like_error(error):
1017
- return False
1018
- backend_code = _code_block_backend_code(error)
1019
- return backend_code in {40002, 40027, 404} or error.http_status == 404
1020
-
1021
-
1022
- def _is_optional_code_block_schema_error(error: QingflowApiError) -> bool:
1023
- if is_auth_like_error(error):
1024
- return False
1025
- return _code_block_backend_code(error) in _CODE_BLOCK_SCHEMA_PERMISSION_CODES or error.http_status == 404
1026
-
1027
-
1028
- def _is_code_block_permission_error(error: QingflowApiError) -> bool:
1029
- if is_auth_like_error(error):
1030
- return False
1031
- return _code_block_backend_code(error) in {40002, 40027}
1032
-
1033
-
1034
- def _code_block_backend_code(error: QingflowApiError) -> int | None:
1035
- return backend_code_int(error)