@josephyan/qingflow-cli 0.2.0-beta.66 → 0.2.0-beta.68

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.
@@ -33,6 +33,27 @@ SUPPORTED_CODE_BLOCK_ROLES = {1, 2, 3, 5}
33
33
 
34
34
 
35
35
  class CodeBlockTools(RecordTools):
36
+ def _get_code_block_relation_schema(
37
+ self,
38
+ profile: str,
39
+ context, # type: ignore[no-untyped-def]
40
+ app_key: str,
41
+ *,
42
+ force_refresh: bool,
43
+ ) -> JSONObject:
44
+ cache_key = (profile, app_key, "code_block_relation_form", 1)
45
+ if not force_refresh and cache_key in self._form_cache:
46
+ return self._form_cache[cache_key]
47
+ schema = self.backend.request(
48
+ "GET",
49
+ context,
50
+ f"/app/{app_key}/form",
51
+ params={"type": 1},
52
+ )
53
+ normalized = schema if isinstance(schema, dict) else {}
54
+ self._form_cache[cache_key] = normalized
55
+ return normalized
56
+
36
57
  def register(self, mcp: FastMCP) -> None:
37
58
  super().register(mcp)
38
59
 
@@ -49,9 +70,10 @@ class CodeBlockTools(RecordTools):
49
70
 
50
71
  @mcp.tool(
51
72
  description=(
52
- "Run a form code-block field against the current record data, then reuse Qingflow's existing "
53
- "relation-calculation chain to compute bound outputs and write them back automatically. "
54
- "Use record_code_block_schema_get first and choose an exact code-block field selector."
73
+ "Run a form code-block field against the current record data, parse alias results, and optionally "
74
+ "reuse Qingflow's existing relation-calculation chain to compute bound outputs and write them back. "
75
+ "Use record_code_block_schema_get first and choose an exact code-block field selector. "
76
+ "For safe debugging, pass apply_writeback=false to inspect parsed results without writing back."
55
77
  )
56
78
  )
57
79
  def record_code_block_run(
@@ -64,6 +86,7 @@ class CodeBlockTools(RecordTools):
64
86
  answers: list[JSONObject] | None = None,
65
87
  fields: JSONObject | None = None,
66
88
  manual: bool = True,
89
+ apply_writeback: bool = True,
67
90
  verify_writeback: bool = True,
68
91
  force_refresh_form: bool = False,
69
92
  output_profile: str = "normal",
@@ -78,6 +101,7 @@ class CodeBlockTools(RecordTools):
78
101
  answers=answers or [],
79
102
  fields=fields or {},
80
103
  manual=manual,
104
+ apply_writeback=apply_writeback,
81
105
  verify_writeback=verify_writeback,
82
106
  force_refresh_form=force_refresh_form,
83
107
  output_profile=output_profile,
@@ -95,7 +119,7 @@ class CodeBlockTools(RecordTools):
95
119
  normalized_output_profile = self._normalize_public_output_profile(output_profile)
96
120
 
97
121
  def runner(session_profile, context):
98
- schema = self._get_form_schema(profile, context, app_key, force_refresh=False)
122
+ relation_schema = self._get_code_block_relation_schema(profile, context, app_key, force_refresh=False)
99
123
  index = self._get_applicant_top_level_field_index(profile, context, app_key, force_refresh=False)
100
124
  input_fields = [
101
125
  self._ready_schema_field_payload(
@@ -113,7 +137,7 @@ class CodeBlockTools(RecordTools):
113
137
  if field.que_type != CODE_BLOCK_QUE_TYPE:
114
138
  continue
115
139
  targets = _collect_code_block_relation_targets(
116
- _collect_question_relations(schema),
140
+ _collect_question_relations(relation_schema),
117
141
  code_block_que_id=field.que_id,
118
142
  )
119
143
  bound_output_fields = [
@@ -127,6 +151,7 @@ class CodeBlockTools(RecordTools):
127
151
  "title": field.que_title,
128
152
  "selector": field.que_title,
129
153
  "bound_output_fields": bound_output_fields,
154
+ "configured_aliases": _extract_code_block_configured_aliases(field),
130
155
  }
131
156
  )
132
157
  response: JSONObject = {
@@ -164,6 +189,7 @@ class CodeBlockTools(RecordTools):
164
189
  answers: list[JSONObject] | None = None,
165
190
  fields: JSONObject | None = None,
166
191
  manual: bool = True,
192
+ apply_writeback: bool = True,
167
193
  verify_writeback: bool = True,
168
194
  force_refresh_form: bool = False,
169
195
  output_profile: str = "normal",
@@ -178,7 +204,12 @@ class CodeBlockTools(RecordTools):
178
204
  raise_tool_error(QingflowApiError.config_error("code_block_field is required"))
179
205
 
180
206
  def runner(session_profile, context):
181
- schema = self._get_form_schema(profile, context, app_key, force_refresh=force_refresh_form)
207
+ relation_schema = self._get_code_block_relation_schema(
208
+ profile,
209
+ context,
210
+ app_key,
211
+ force_refresh=force_refresh_form,
212
+ )
182
213
  index = self._get_field_index(profile, context, app_key, force_refresh=force_refresh_form)
183
214
  code_block = self._resolve_field_selector(code_block_field, index, location="code_block_field")
184
215
  if code_block.que_type != CODE_BLOCK_QUE_TYPE:
@@ -233,8 +264,9 @@ class CodeBlockTools(RecordTools):
233
264
  json_body=run_body,
234
265
  )
235
266
  alias_results = _normalize_code_block_alias_results(run_result)
267
+ configured_aliases = _extract_code_block_configured_aliases(code_block)
236
268
  relation_target_fields = _collect_code_block_relation_targets(
237
- _collect_question_relations(schema),
269
+ _collect_question_relations(relation_schema),
238
270
  code_block_que_id=code_block.que_id,
239
271
  )
240
272
  relation_errors: list[JSONObject] = []
@@ -277,7 +309,12 @@ class CodeBlockTools(RecordTools):
277
309
  writeback_applied = False
278
310
  status = "completed"
279
311
  ok = True
280
- if relation_target_fields and calculated_answers:
312
+ if relation_errors:
313
+ status = "relation_failed"
314
+ ok = False
315
+ elif not apply_writeback:
316
+ status = "debug_completed"
317
+ elif relation_target_fields and calculated_answers:
281
318
  write_body: JSONObject = {"role": role, "answers": calculated_answers}
282
319
  if workflow_node_id is not None:
283
320
  write_body["auditNodeId"] = workflow_node_id
@@ -305,9 +342,6 @@ class CodeBlockTools(RecordTools):
305
342
  if not bool(verification.get("verified")):
306
343
  status = "verification_failed"
307
344
  ok = False
308
- elif relation_errors:
309
- status = "relation_failed"
310
- ok = False
311
345
  else:
312
346
  status = "no_writeback"
313
347
  response: JSONObject = {
@@ -325,9 +359,11 @@ class CodeBlockTools(RecordTools):
325
359
  "role": role,
326
360
  "workflow_node_id": workflow_node_id,
327
361
  "manual": bool(manual),
362
+ "apply_writeback": bool(apply_writeback),
328
363
  "result_count": len(alias_results),
329
364
  },
330
365
  "outputs": {
366
+ "configured_aliases": configured_aliases,
331
367
  "alias_results": alias_results,
332
368
  "alias_map": _build_alias_result_map(alias_results),
333
369
  },
@@ -335,11 +371,14 @@ class CodeBlockTools(RecordTools):
335
371
  "target_fields": relation_target_fields,
336
372
  "result_item_count": len(relation_items),
337
373
  "calculated_answer_count": len(calculated_answers),
374
+ "calculated_answers_preview": calculated_answers,
338
375
  "errors": relation_errors,
339
376
  },
340
377
  "writeback": {
378
+ "enabled": bool(apply_writeback),
341
379
  "attempted": writeback_attempted,
342
380
  "applied": writeback_applied,
381
+ "skipped_reason": "apply_writeback_disabled" if not apply_writeback else None,
343
382
  "verify_writeback": verify_writeback,
344
383
  "write_verified": bool(verification.get("verified")) if verification is not None else None,
345
384
  "result": write_result,
@@ -606,6 +645,38 @@ def _normalize_code_block_alias_results(payload: JSONValue) -> list[JSONObject]:
606
645
  return results
607
646
 
608
647
 
648
+ def _extract_code_block_configured_aliases(field: FormField) -> list[JSONObject]:
649
+ raw_config = field.raw.get("codeBlockConfig")
650
+ if not isinstance(raw_config, dict):
651
+ return []
652
+ raw_aliases = raw_config.get("resultAliasPath")
653
+ if not isinstance(raw_aliases, list):
654
+ return []
655
+ return [item for item in (_normalize_code_block_alias_item(alias) for alias in raw_aliases) if item is not None]
656
+
657
+
658
+ def _normalize_code_block_alias_item(value: JSONValue) -> JSONObject | None:
659
+ if not isinstance(value, dict):
660
+ return None
661
+ alias_name = _normalize_optional_text(value.get("aliasName", value.get("alias_name")))
662
+ alias_path = _normalize_optional_text(value.get("aliasPath", value.get("alias_path")))
663
+ if alias_name is None and alias_path is None:
664
+ return None
665
+ raw_sub_alias = value.get("subAlias", value.get("sub_alias"))
666
+ sub_alias = (
667
+ [item for item in (_normalize_code_block_alias_item(entry) for entry in raw_sub_alias) if item is not None]
668
+ if isinstance(raw_sub_alias, list)
669
+ else []
670
+ )
671
+ return {
672
+ "alias_id": _coerce_count(value.get("aliasId", value.get("alias_id"))),
673
+ "alias_name": alias_name,
674
+ "alias_path": alias_path,
675
+ "alias_type": _coerce_count(value.get("aliasType", value.get("alias_type"))) or 1,
676
+ "sub_alias": sub_alias,
677
+ }
678
+
679
+
609
680
  def _build_alias_result_map(alias_results: list[JSONObject]) -> JSONObject:
610
681
  alias_map: JSONObject = {}
611
682
  for item in alias_results:
@@ -625,7 +696,11 @@ def _collect_code_block_relation_targets(question_relations: list[JSONObject], *
625
696
  for relation in question_relations:
626
697
  if _coerce_count(relation.get("relationType")) != CODE_BLOCK_RELATION_TYPE:
627
698
  continue
628
- if _coerce_count(relation.get("qlinkerQueId")) != code_block_que_id:
699
+ alias_config = relation.get("aliasConfig") if isinstance(relation.get("aliasConfig"), dict) else {}
700
+ relation_code_block_que_id = _coerce_count(relation.get("qlinkerQueId"))
701
+ if relation_code_block_que_id is None:
702
+ relation_code_block_que_id = _coerce_count(alias_config.get("queId"))
703
+ if relation_code_block_que_id != code_block_que_id:
629
704
  continue
630
705
  target_id = _coerce_count(
631
706
  relation.get("queId", relation.get("targetQueId", relation.get("displayedQueId")))
@@ -636,8 +711,10 @@ def _collect_code_block_relation_targets(question_relations: list[JSONObject], *
636
711
  targets.append(
637
712
  {
638
713
  "que_id": target_id,
639
- "alias_id": _coerce_count(relation.get("aliasId")),
640
- "qlinker_que_id": _coerce_count(relation.get("qlinkerQueId")),
714
+ "alias_id": _coerce_count(relation.get("aliasId")) or _coerce_count(alias_config.get("aliasId")),
715
+ "alias_name": _normalize_optional_text(relation.get("qlinkerAlias"))
716
+ or _normalize_optional_text(alias_config.get("qlinkerAlias")),
717
+ "qlinker_que_id": relation_code_block_que_id,
641
718
  }
642
719
  )
643
720
  return targets
@@ -200,6 +200,7 @@ class PackageTools(ToolBase):
200
200
  compact = {
201
201
  "tagId": result.get("tagId"),
202
202
  "tagName": result.get("tagName"),
203
+ "tagIcon": result.get("tagIcon") if result.get("tagIcon") is not None else permission_source.get("tagIcon"),
203
204
  "publishStatus": result.get("publishStatus") if result.get("publishStatus") is not None else permission_source.get("publishStatus"),
204
205
  "beingTrial": result.get("beingTrial"),
205
206
  "itemCount": len(tag_items),