@josephyan/qingflow-app-user-mcp 0.2.0-beta.986 → 0.2.0-beta.987
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.
- package/README.md +2 -2
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/builder_facade/service.py +375 -18
- package/src/qingflow_mcp/cli/commands/workspace.py +11 -0
- package/src/qingflow_mcp/cli/formatters.py +19 -1
- package/src/qingflow_mcp/public_surface.py +2 -0
- package/src/qingflow_mcp/solution/compiler/form_compiler.py +2 -2
- package/src/qingflow_mcp/solution/executor.py +2 -2
- package/src/qingflow_mcp/tools/auth_tools.py +50 -2
- package/src/qingflow_mcp/tools/workspace_tools.py +94 -0
package/README.md
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
Install:
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npm install @josephyan/qingflow-app-user-mcp@0.2.0-beta.
|
|
6
|
+
npm install @josephyan/qingflow-app-user-mcp@0.2.0-beta.987
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Run:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx -y -p @josephyan/qingflow-app-user-mcp@0.2.0-beta.
|
|
12
|
+
npx -y -p @josephyan/qingflow-app-user-mcp@0.2.0-beta.987 qingflow-app-user-mcp
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -9811,6 +9811,15 @@ def _apply_relation_target_selection(
|
|
|
9811
9811
|
config["refer_field_types"] = [item.get("type") for item in normalized_visible]
|
|
9812
9812
|
config["auth_field_ids"] = [item.get("field_id") or item.get("name") for item in normalized_visible]
|
|
9813
9813
|
config["auth_field_que_ids"] = [_coerce_positive_int(item.get("que_id")) or 0 for item in normalized_visible]
|
|
9814
|
+
config["refer_auth_ques"] = [
|
|
9815
|
+
{
|
|
9816
|
+
"queId": _coerce_positive_int(item.get("que_id")) or 0,
|
|
9817
|
+
"queAuth": _REFERENCE_FIELD_VISIBLE_AUTH,
|
|
9818
|
+
"_field_id": item.get("field_id") or item.get("name"),
|
|
9819
|
+
}
|
|
9820
|
+
for item in normalized_visible
|
|
9821
|
+
if (_coerce_positive_int(item.get("que_id")) or 0) > 0
|
|
9822
|
+
]
|
|
9814
9823
|
config["field_name_show"] = bool(field.get("field_name_show", True))
|
|
9815
9824
|
field["target_field_id"] = display_field.get("field_id") or display_field.get("name")
|
|
9816
9825
|
field["target_field_que_id"] = _coerce_positive_int(display_field.get("que_id")) or 0
|
|
@@ -10071,17 +10080,32 @@ def _parse_field(question: dict[str, Any], *, field_id_hint: str | None = None)
|
|
|
10071
10080
|
field["target_app_key"] = reference.get("referAppKey")
|
|
10072
10081
|
field["relation_mode"] = _relation_mode_from_optional_data_num(reference.get("optionalDataNum"))
|
|
10073
10082
|
refer_questions = reference.get("referQuestions") if isinstance(reference.get("referQuestions"), list) else []
|
|
10083
|
+
refer_auth_questions = reference.get("referAuthQues") if isinstance(reference.get("referAuthQues"), list) else []
|
|
10084
|
+
refer_auth_by_que_id: dict[int, int] = {}
|
|
10085
|
+
for raw_item in refer_auth_questions:
|
|
10086
|
+
if not isinstance(raw_item, dict):
|
|
10087
|
+
continue
|
|
10088
|
+
que_id = _coerce_nonnegative_int(raw_item.get("queId"))
|
|
10089
|
+
que_auth = _coerce_nonnegative_int(raw_item.get("queAuth"))
|
|
10090
|
+
if que_id is None or que_auth is None or que_id in refer_auth_by_que_id:
|
|
10091
|
+
continue
|
|
10092
|
+
refer_auth_by_que_id[que_id] = que_auth
|
|
10074
10093
|
visible_fields: list[dict[str, Any]] = []
|
|
10075
10094
|
display_field_que_id = _coerce_nonnegative_int(reference.get("referQueId"))
|
|
10076
10095
|
display_field_name: str | None = None
|
|
10077
10096
|
for item in refer_questions:
|
|
10078
10097
|
if not isinstance(item, dict):
|
|
10079
10098
|
continue
|
|
10099
|
+
que_id = _coerce_nonnegative_int(item.get("queId"))
|
|
10100
|
+
que_auth = _coerce_nonnegative_int(item.get("queAuth"))
|
|
10101
|
+
if que_auth is None and que_id is not None:
|
|
10102
|
+
que_auth = refer_auth_by_que_id.get(que_id)
|
|
10080
10103
|
selector = {
|
|
10081
|
-
"que_id":
|
|
10104
|
+
"que_id": que_id,
|
|
10082
10105
|
"name": str(item.get("queTitle") or "").strip() or None,
|
|
10083
10106
|
}
|
|
10084
|
-
|
|
10107
|
+
if que_auth != _REFERENCE_FIELD_HIDDEN_AUTH:
|
|
10108
|
+
visible_fields.append(selector)
|
|
10085
10109
|
if display_field_que_id is not None and selector["que_id"] == display_field_que_id:
|
|
10086
10110
|
display_field_name = selector["name"]
|
|
10087
10111
|
if display_field_name is None and visible_fields:
|
|
@@ -13275,6 +13299,333 @@ def _normalize_reference_auth_question_for_save(value: Any) -> dict[str, Any] |
|
|
|
13275
13299
|
return payload
|
|
13276
13300
|
|
|
13277
13301
|
|
|
13302
|
+
def _dedupe_reference_auth_questions(auth_questions: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
13303
|
+
deduped: list[dict[str, Any]] = []
|
|
13304
|
+
seen_que_ids: set[int] = set()
|
|
13305
|
+
for item in auth_questions:
|
|
13306
|
+
normalized_item = _normalize_reference_auth_question_for_save(item)
|
|
13307
|
+
if normalized_item is None:
|
|
13308
|
+
continue
|
|
13309
|
+
que_id = _coerce_any_int(normalized_item.get("queId"))
|
|
13310
|
+
if que_id is None or que_id in seen_que_ids:
|
|
13311
|
+
continue
|
|
13312
|
+
seen_que_ids.add(que_id)
|
|
13313
|
+
deduped.append(normalized_item)
|
|
13314
|
+
return deduped
|
|
13315
|
+
|
|
13316
|
+
|
|
13317
|
+
_REFERENCE_FIELD_HIDDEN_AUTH = 2
|
|
13318
|
+
_REFERENCE_FIELD_VISIBLE_AUTH = 3
|
|
13319
|
+
|
|
13320
|
+
|
|
13321
|
+
def _synthesize_reference_auth_questions_for_save(
|
|
13322
|
+
*,
|
|
13323
|
+
source: dict[str, Any],
|
|
13324
|
+
field: dict[str, Any],
|
|
13325
|
+
) -> list[dict[str, Any]]:
|
|
13326
|
+
config = field.get("config") if isinstance(field.get("config"), dict) else {}
|
|
13327
|
+
synthesized: list[dict[str, Any]] = []
|
|
13328
|
+
|
|
13329
|
+
if isinstance(config.get("refer_auth_ques"), list):
|
|
13330
|
+
synthesized.extend(cast(list[dict[str, Any]], config.get("refer_auth_ques") or []))
|
|
13331
|
+
if synthesized:
|
|
13332
|
+
return _dedupe_reference_auth_questions(synthesized)
|
|
13333
|
+
|
|
13334
|
+
refer_question_ids_by_name: dict[str, int] = {}
|
|
13335
|
+
for raw_item in cast(list[Any], source.get("referQuestions") or []):
|
|
13336
|
+
if not isinstance(raw_item, dict):
|
|
13337
|
+
continue
|
|
13338
|
+
que_id = _coerce_any_int(raw_item.get("queId"))
|
|
13339
|
+
name = str(raw_item.get("queTitle") or "").strip()
|
|
13340
|
+
if que_id is None or not name or name in refer_question_ids_by_name:
|
|
13341
|
+
continue
|
|
13342
|
+
refer_question_ids_by_name[name] = que_id
|
|
13343
|
+
|
|
13344
|
+
visible_fields = cast(list[dict[str, Any]], field.get("visible_fields") or [])
|
|
13345
|
+
for item in visible_fields:
|
|
13346
|
+
if not isinstance(item, dict):
|
|
13347
|
+
continue
|
|
13348
|
+
que_id = _coerce_any_int(item.get("que_id"))
|
|
13349
|
+
if que_id is None:
|
|
13350
|
+
name = str(item.get("name") or "").strip()
|
|
13351
|
+
que_id = refer_question_ids_by_name.get(name)
|
|
13352
|
+
if que_id is None:
|
|
13353
|
+
continue
|
|
13354
|
+
synthesized.append({"queId": que_id, "queAuth": _REFERENCE_FIELD_VISIBLE_AUTH})
|
|
13355
|
+
if synthesized:
|
|
13356
|
+
return _dedupe_reference_auth_questions(synthesized)
|
|
13357
|
+
|
|
13358
|
+
auth_field_que_ids = cast(list[Any], config.get("auth_field_que_ids") or [])
|
|
13359
|
+
for raw_que_id in auth_field_que_ids:
|
|
13360
|
+
que_id = _coerce_any_int(raw_que_id)
|
|
13361
|
+
if que_id is None:
|
|
13362
|
+
continue
|
|
13363
|
+
synthesized.append({"queId": que_id, "queAuth": _REFERENCE_FIELD_VISIBLE_AUTH})
|
|
13364
|
+
if synthesized:
|
|
13365
|
+
return _dedupe_reference_auth_questions(synthesized)
|
|
13366
|
+
|
|
13367
|
+
for raw_item in cast(list[Any], source.get("referQuestions") or []):
|
|
13368
|
+
if not isinstance(raw_item, dict):
|
|
13369
|
+
continue
|
|
13370
|
+
que_id = _coerce_any_int(raw_item.get("queId"))
|
|
13371
|
+
if que_id is None:
|
|
13372
|
+
continue
|
|
13373
|
+
synthesized.append(
|
|
13374
|
+
{
|
|
13375
|
+
"queId": que_id,
|
|
13376
|
+
"queAuth": _REFERENCE_FIELD_VISIBLE_AUTH,
|
|
13377
|
+
}
|
|
13378
|
+
)
|
|
13379
|
+
if synthesized:
|
|
13380
|
+
return _dedupe_reference_auth_questions(synthesized)
|
|
13381
|
+
|
|
13382
|
+
fallback_que_id = _coerce_any_int(field.get("target_field_que_id"))
|
|
13383
|
+
if fallback_que_id is not None:
|
|
13384
|
+
synthesized.append({"queId": fallback_que_id, "queAuth": _REFERENCE_FIELD_VISIBLE_AUTH})
|
|
13385
|
+
return _dedupe_reference_auth_questions(synthesized)
|
|
13386
|
+
|
|
13387
|
+
|
|
13388
|
+
def _reference_question_auth_overrides_for_save(
|
|
13389
|
+
*,
|
|
13390
|
+
source: dict[str, Any],
|
|
13391
|
+
field: dict[str, Any],
|
|
13392
|
+
) -> dict[int, int]:
|
|
13393
|
+
overrides: dict[int, int] = {}
|
|
13394
|
+
visible_que_ids: set[int] = set()
|
|
13395
|
+
|
|
13396
|
+
for item in cast(list[Any], field.get("visible_fields") or []):
|
|
13397
|
+
if not isinstance(item, dict):
|
|
13398
|
+
continue
|
|
13399
|
+
que_id = _coerce_any_int(item.get("que_id"))
|
|
13400
|
+
if que_id is not None:
|
|
13401
|
+
visible_que_ids.add(que_id)
|
|
13402
|
+
|
|
13403
|
+
if not visible_que_ids:
|
|
13404
|
+
refer_auth_ques = _synthesize_reference_auth_questions_for_save(source=source, field=field)
|
|
13405
|
+
for item in refer_auth_ques:
|
|
13406
|
+
que_id = _coerce_any_int(item.get("queId"))
|
|
13407
|
+
que_auth = _coerce_nonnegative_int(item.get("queAuth"))
|
|
13408
|
+
if que_id is None or que_auth is None:
|
|
13409
|
+
continue
|
|
13410
|
+
overrides[que_id] = que_auth
|
|
13411
|
+
if overrides:
|
|
13412
|
+
return overrides
|
|
13413
|
+
|
|
13414
|
+
for raw_item in cast(list[Any], source.get("referQuestions") or []):
|
|
13415
|
+
if not isinstance(raw_item, dict):
|
|
13416
|
+
continue
|
|
13417
|
+
que_id = _coerce_any_int(raw_item.get("queId"))
|
|
13418
|
+
if que_id is None:
|
|
13419
|
+
continue
|
|
13420
|
+
overrides[que_id] = (
|
|
13421
|
+
_REFERENCE_FIELD_VISIBLE_AUTH if que_id in visible_que_ids else _REFERENCE_FIELD_HIDDEN_AUTH
|
|
13422
|
+
)
|
|
13423
|
+
return overrides
|
|
13424
|
+
|
|
13425
|
+
|
|
13426
|
+
def _reference_question_matches_visible_selector(question: dict[str, Any], selector: dict[str, Any]) -> bool:
|
|
13427
|
+
question_que_id = _coerce_any_int(question.get("queId"))
|
|
13428
|
+
selector_que_id = _coerce_any_int(selector.get("que_id"))
|
|
13429
|
+
if question_que_id is not None and selector_que_id is not None and question_que_id == selector_que_id:
|
|
13430
|
+
return True
|
|
13431
|
+
question_name = str(question.get("queTitle") or "").strip()
|
|
13432
|
+
selector_name = str(selector.get("name") or "").strip()
|
|
13433
|
+
return bool(question_name and selector_name and question_name == selector_name)
|
|
13434
|
+
|
|
13435
|
+
|
|
13436
|
+
def _build_reference_question_from_visible_selector(
|
|
13437
|
+
selector: dict[str, Any],
|
|
13438
|
+
*,
|
|
13439
|
+
ordinal: int,
|
|
13440
|
+
) -> dict[str, Any] | None:
|
|
13441
|
+
return _normalize_reference_question_for_save(
|
|
13442
|
+
{
|
|
13443
|
+
"queId": _coerce_any_int(selector.get("que_id")),
|
|
13444
|
+
"queTitle": str(selector.get("name") or "").strip() or None,
|
|
13445
|
+
"queType": str(selector.get("type") or "2"),
|
|
13446
|
+
"ordinal": ordinal,
|
|
13447
|
+
},
|
|
13448
|
+
ordinal=ordinal,
|
|
13449
|
+
)
|
|
13450
|
+
|
|
13451
|
+
|
|
13452
|
+
def _canonicalize_reference_questions_for_save(
|
|
13453
|
+
*,
|
|
13454
|
+
source: dict[str, Any],
|
|
13455
|
+
field: dict[str, Any],
|
|
13456
|
+
) -> list[dict[str, Any]]:
|
|
13457
|
+
normalized_source_questions = [
|
|
13458
|
+
item
|
|
13459
|
+
for item in (
|
|
13460
|
+
_normalize_reference_question_for_save(raw_item, ordinal=index)
|
|
13461
|
+
for index, raw_item in enumerate(cast(list[Any], source.get("referQuestions") or []), start=1)
|
|
13462
|
+
)
|
|
13463
|
+
if item is not None
|
|
13464
|
+
]
|
|
13465
|
+
|
|
13466
|
+
display_field = field.get("display_field") if isinstance(field.get("display_field"), dict) else None
|
|
13467
|
+
visible_fields = [item for item in cast(list[Any], field.get("visible_fields") or []) if isinstance(item, dict)]
|
|
13468
|
+
ordered_visible_selectors: list[dict[str, Any]] = []
|
|
13469
|
+
if display_field is not None:
|
|
13470
|
+
ordered_visible_selectors.append(display_field)
|
|
13471
|
+
for item in visible_fields:
|
|
13472
|
+
if any(_relation_target_field_matches(existing, item) for existing in ordered_visible_selectors):
|
|
13473
|
+
continue
|
|
13474
|
+
ordered_visible_selectors.append(item)
|
|
13475
|
+
|
|
13476
|
+
if not ordered_visible_selectors:
|
|
13477
|
+
return normalized_source_questions
|
|
13478
|
+
|
|
13479
|
+
canonical_questions: list[dict[str, Any]] = []
|
|
13480
|
+
used_source_indexes: set[int] = set()
|
|
13481
|
+
|
|
13482
|
+
for ordinal, selector in enumerate(ordered_visible_selectors, start=1):
|
|
13483
|
+
matched_index: int | None = None
|
|
13484
|
+
matched_item: dict[str, Any] | None = None
|
|
13485
|
+
for index, item in enumerate(normalized_source_questions):
|
|
13486
|
+
if index in used_source_indexes:
|
|
13487
|
+
continue
|
|
13488
|
+
if _reference_question_matches_visible_selector(item, selector):
|
|
13489
|
+
matched_index = index
|
|
13490
|
+
matched_item = deepcopy(item)
|
|
13491
|
+
break
|
|
13492
|
+
if matched_item is None:
|
|
13493
|
+
matched_item = _build_reference_question_from_visible_selector(selector, ordinal=ordinal)
|
|
13494
|
+
if matched_item is None:
|
|
13495
|
+
continue
|
|
13496
|
+
matched_item["ordinal"] = ordinal
|
|
13497
|
+
canonical_questions.append(matched_item)
|
|
13498
|
+
if matched_index is not None:
|
|
13499
|
+
used_source_indexes.add(matched_index)
|
|
13500
|
+
|
|
13501
|
+
next_ordinal = len(canonical_questions) + 1
|
|
13502
|
+
for index, item in enumerate(normalized_source_questions):
|
|
13503
|
+
if index in used_source_indexes:
|
|
13504
|
+
continue
|
|
13505
|
+
remaining_item = deepcopy(item)
|
|
13506
|
+
remaining_item["ordinal"] = next_ordinal
|
|
13507
|
+
next_ordinal += 1
|
|
13508
|
+
canonical_questions.append(remaining_item)
|
|
13509
|
+
|
|
13510
|
+
return canonical_questions
|
|
13511
|
+
|
|
13512
|
+
|
|
13513
|
+
def _canonicalize_reference_auth_questions_for_save(
|
|
13514
|
+
*,
|
|
13515
|
+
source: dict[str, Any],
|
|
13516
|
+
refer_questions: list[dict[str, Any]],
|
|
13517
|
+
) -> list[dict[str, Any]]:
|
|
13518
|
+
source_auth_questions = [
|
|
13519
|
+
item
|
|
13520
|
+
for item in (
|
|
13521
|
+
_normalize_reference_auth_question_for_save(raw_item)
|
|
13522
|
+
for raw_item in cast(list[Any], source.get("referAuthQues") or [])
|
|
13523
|
+
)
|
|
13524
|
+
if item is not None
|
|
13525
|
+
]
|
|
13526
|
+
source_auth_by_que_id: dict[int, dict[str, Any]] = {}
|
|
13527
|
+
for item in source_auth_questions:
|
|
13528
|
+
que_id = _coerce_any_int(item.get("queId"))
|
|
13529
|
+
if que_id is None or que_id in source_auth_by_que_id:
|
|
13530
|
+
continue
|
|
13531
|
+
source_auth_by_que_id[que_id] = item
|
|
13532
|
+
|
|
13533
|
+
auth_questions: list[dict[str, Any]] = []
|
|
13534
|
+
for item in refer_questions:
|
|
13535
|
+
que_id = _coerce_any_int(item.get("queId"))
|
|
13536
|
+
que_auth = _coerce_nonnegative_int(item.get("queAuth"))
|
|
13537
|
+
if que_id is None or que_auth is None:
|
|
13538
|
+
continue
|
|
13539
|
+
payload = deepcopy(source_auth_by_que_id.get(que_id) or {"queId": que_id})
|
|
13540
|
+
payload["queId"] = que_id
|
|
13541
|
+
payload["queAuth"] = que_auth
|
|
13542
|
+
auth_questions.append(payload)
|
|
13543
|
+
return _dedupe_reference_auth_questions(auth_questions)
|
|
13544
|
+
|
|
13545
|
+
|
|
13546
|
+
def _enforce_reference_config_consistency_for_save(
|
|
13547
|
+
payload: dict[str, Any],
|
|
13548
|
+
*,
|
|
13549
|
+
field: dict[str, Any],
|
|
13550
|
+
) -> dict[str, Any]:
|
|
13551
|
+
refer_questions = [
|
|
13552
|
+
item
|
|
13553
|
+
for item in (
|
|
13554
|
+
_normalize_reference_question_for_save(raw_item, ordinal=index)
|
|
13555
|
+
for index, raw_item in enumerate(cast(list[Any], payload.get("referQuestions") or []), start=1)
|
|
13556
|
+
)
|
|
13557
|
+
if item is not None
|
|
13558
|
+
]
|
|
13559
|
+
if not refer_questions:
|
|
13560
|
+
return payload
|
|
13561
|
+
|
|
13562
|
+
refer_auth_ques = _dedupe_reference_auth_questions(
|
|
13563
|
+
[
|
|
13564
|
+
item
|
|
13565
|
+
for item in (
|
|
13566
|
+
_normalize_reference_auth_question_for_save(raw_item)
|
|
13567
|
+
for raw_item in cast(list[Any], payload.get("referAuthQues") or [])
|
|
13568
|
+
)
|
|
13569
|
+
if item is not None
|
|
13570
|
+
]
|
|
13571
|
+
)
|
|
13572
|
+
refer_auth_by_que_id: dict[int, int] = {}
|
|
13573
|
+
for item in refer_auth_ques:
|
|
13574
|
+
que_id = _coerce_any_int(item.get("queId"))
|
|
13575
|
+
que_auth = _coerce_nonnegative_int(item.get("queAuth"))
|
|
13576
|
+
if que_id is None or que_auth is None or que_id in refer_auth_by_que_id:
|
|
13577
|
+
continue
|
|
13578
|
+
refer_auth_by_que_id[que_id] = que_auth
|
|
13579
|
+
|
|
13580
|
+
display_field_que_id = _coerce_any_int(payload.get("referQueId"))
|
|
13581
|
+
if display_field_que_id is None:
|
|
13582
|
+
display_field_que_id = _coerce_any_int(field.get("target_field_que_id"))
|
|
13583
|
+
if display_field_que_id is not None:
|
|
13584
|
+
payload["referQueId"] = display_field_que_id
|
|
13585
|
+
|
|
13586
|
+
if display_field_que_id is not None and not any(
|
|
13587
|
+
_coerce_any_int(item.get("queId")) == display_field_que_id for item in refer_questions
|
|
13588
|
+
):
|
|
13589
|
+
display_selector = field.get("display_field") if isinstance(field.get("display_field"), dict) else None
|
|
13590
|
+
display_question = (
|
|
13591
|
+
_build_reference_question_from_visible_selector(display_selector, ordinal=1)
|
|
13592
|
+
if display_selector is not None
|
|
13593
|
+
else None
|
|
13594
|
+
)
|
|
13595
|
+
if display_question is not None:
|
|
13596
|
+
display_question["queId"] = display_field_que_id
|
|
13597
|
+
display_question["queAuth"] = _REFERENCE_FIELD_VISIBLE_AUTH
|
|
13598
|
+
refer_questions = [display_question, *refer_questions]
|
|
13599
|
+
|
|
13600
|
+
if display_field_que_id is not None:
|
|
13601
|
+
display_questions = [
|
|
13602
|
+
item for item in refer_questions if _coerce_any_int(item.get("queId")) == display_field_que_id
|
|
13603
|
+
]
|
|
13604
|
+
trailing_questions = [
|
|
13605
|
+
item for item in refer_questions if _coerce_any_int(item.get("queId")) != display_field_que_id
|
|
13606
|
+
]
|
|
13607
|
+
refer_questions = [*display_questions, *trailing_questions]
|
|
13608
|
+
|
|
13609
|
+
for ordinal, item in enumerate(refer_questions, start=1):
|
|
13610
|
+
que_id = _coerce_any_int(item.get("queId"))
|
|
13611
|
+
if que_id is None:
|
|
13612
|
+
continue
|
|
13613
|
+
item["ordinal"] = ordinal
|
|
13614
|
+
item["queAuth"] = refer_auth_by_que_id.get(
|
|
13615
|
+
que_id,
|
|
13616
|
+
_coerce_nonnegative_int(item.get("queAuth")) or _REFERENCE_FIELD_VISIBLE_AUTH,
|
|
13617
|
+
)
|
|
13618
|
+
if display_field_que_id is not None and que_id == display_field_que_id:
|
|
13619
|
+
item["queAuth"] = _REFERENCE_FIELD_VISIBLE_AUTH
|
|
13620
|
+
|
|
13621
|
+
payload["referQuestions"] = refer_questions
|
|
13622
|
+
payload["referAuthQues"] = _canonicalize_reference_auth_questions_for_save(
|
|
13623
|
+
source={"referAuthQues": refer_auth_ques},
|
|
13624
|
+
refer_questions=refer_questions,
|
|
13625
|
+
)
|
|
13626
|
+
return payload
|
|
13627
|
+
|
|
13628
|
+
|
|
13278
13629
|
def _normalize_reference_config_for_save(
|
|
13279
13630
|
reference: Any,
|
|
13280
13631
|
*,
|
|
@@ -13289,11 +13640,13 @@ def _normalize_reference_config_for_save(
|
|
|
13289
13640
|
if field.get("field_name_show") is not None:
|
|
13290
13641
|
payload["fieldNameShow"] = bool(field.get("field_name_show"))
|
|
13291
13642
|
|
|
13292
|
-
|
|
13293
|
-
|
|
13294
|
-
|
|
13295
|
-
|
|
13296
|
-
|
|
13643
|
+
refer_question_auth_overrides = _reference_question_auth_overrides_for_save(source=source, field=field)
|
|
13644
|
+
refer_questions = _canonicalize_reference_questions_for_save(source=source, field=field)
|
|
13645
|
+
for index, normalized_item in enumerate(refer_questions, start=1):
|
|
13646
|
+
que_id = _coerce_any_int(normalized_item.get("queId"))
|
|
13647
|
+
if que_id is not None and que_id in refer_question_auth_overrides:
|
|
13648
|
+
normalized_item["queAuth"] = refer_question_auth_overrides[que_id]
|
|
13649
|
+
normalized_item["ordinal"] = index
|
|
13297
13650
|
if refer_questions or "referQuestions" in source:
|
|
13298
13651
|
payload["referQuestions"] = refer_questions
|
|
13299
13652
|
|
|
@@ -13308,18 +13661,13 @@ def _normalize_reference_config_for_save(
|
|
|
13308
13661
|
if refer_fill_rules or "referFillRules" in source:
|
|
13309
13662
|
payload["referFillRules"] = refer_fill_rules
|
|
13310
13663
|
|
|
13311
|
-
refer_auth_ques =
|
|
13312
|
-
|
|
13313
|
-
|
|
13314
|
-
_normalize_reference_auth_question_for_save(raw_item)
|
|
13315
|
-
for raw_item in cast(list[Any], source.get("referAuthQues") or [])
|
|
13316
|
-
)
|
|
13317
|
-
if item is not None
|
|
13318
|
-
]
|
|
13664
|
+
refer_auth_ques = _canonicalize_reference_auth_questions_for_save(source=source, refer_questions=refer_questions)
|
|
13665
|
+
if not refer_auth_ques:
|
|
13666
|
+
refer_auth_ques = _synthesize_reference_auth_questions_for_save(source=source, field=field)
|
|
13319
13667
|
if refer_auth_ques or "referAuthQues" in source:
|
|
13320
13668
|
payload["referAuthQues"] = refer_auth_ques
|
|
13321
13669
|
|
|
13322
|
-
return payload
|
|
13670
|
+
return _enforce_reference_config_consistency_for_save(payload, field=field)
|
|
13323
13671
|
|
|
13324
13672
|
|
|
13325
13673
|
def _normalize_relation_question_for_save(question: dict[str, Any], *, field: dict[str, Any]) -> dict[str, Any]:
|
|
@@ -13550,9 +13898,16 @@ def _field_to_question(field: dict[str, Any], *, temp_id: int) -> dict[str, Any]
|
|
|
13550
13898
|
preserved_reference["referAppKey"] = field.get("target_app_key")
|
|
13551
13899
|
question["referenceConfig"] = preserved_reference
|
|
13552
13900
|
else:
|
|
13901
|
+
existing_reference = (
|
|
13902
|
+
deepcopy(relation_question_template.get("referenceConfig"))
|
|
13903
|
+
if relation_question_template is not None and isinstance(relation_question_template.get("referenceConfig"), dict)
|
|
13904
|
+
else deepcopy(question.get("referenceConfig"))
|
|
13905
|
+
if isinstance(question.get("referenceConfig"), dict)
|
|
13906
|
+
else {}
|
|
13907
|
+
)
|
|
13553
13908
|
reference = (
|
|
13554
|
-
|
|
13555
|
-
if relation_config_explicit
|
|
13909
|
+
existing_reference
|
|
13910
|
+
if relation_config_explicit
|
|
13556
13911
|
else deepcopy(question.get("referenceConfig"))
|
|
13557
13912
|
if isinstance(question.get("referenceConfig"), dict)
|
|
13558
13913
|
else {}
|
|
@@ -13573,6 +13928,8 @@ def _field_to_question(field: dict[str, Any], *, temp_id: int) -> dict[str, Any]
|
|
|
13573
13928
|
"fieldNameShow",
|
|
13574
13929
|
"_targetFieldId",
|
|
13575
13930
|
):
|
|
13931
|
+
if relation_config_explicit and key in {"referQuestions", "referAuthQues"}:
|
|
13932
|
+
continue
|
|
13576
13933
|
if key in built_reference:
|
|
13577
13934
|
reference[key] = deepcopy(built_reference[key])
|
|
13578
13935
|
reference["referAppKey"] = field.get("target_app_key")
|
|
@@ -15,6 +15,10 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
15
15
|
list_parser.add_argument("--include-external", action="store_true")
|
|
16
16
|
list_parser.set_defaults(handler=_handle_list, format_hint="workspace_list")
|
|
17
17
|
|
|
18
|
+
get_parser = workspace_subparsers.add_parser("get", help="读取工作区详情")
|
|
19
|
+
get_parser.add_argument("--ws-id", type=int, default=0)
|
|
20
|
+
get_parser.set_defaults(handler=_handle_get, format_hint="workspace_get")
|
|
21
|
+
|
|
18
22
|
|
|
19
23
|
def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
|
|
20
24
|
return context.workspace.workspace_list(
|
|
@@ -23,3 +27,10 @@ def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
23
27
|
page_size=args.page_size,
|
|
24
28
|
include_external=bool(args.include_external),
|
|
25
29
|
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _handle_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
33
|
+
return context.workspace.workspace_get(
|
|
34
|
+
profile=args.profile,
|
|
35
|
+
ws_id=args.ws_id if int(args.ws_id or 0) > 0 else None,
|
|
36
|
+
)
|
|
@@ -38,8 +38,12 @@ def _format_whoami(result: dict[str, Any]) -> str:
|
|
|
38
38
|
f"User: {result.get('nick_name') or '-'} ({result.get('email') or '-'})",
|
|
39
39
|
f"UID: {result.get('uid')}",
|
|
40
40
|
f"Workspace: {result.get('selected_ws_name') or '-'} ({result.get('selected_ws_id') or '-'})",
|
|
41
|
-
f"QF Version: {result.get('qf_version') or '-'}",
|
|
41
|
+
f"Workspace QF Version: {result.get('qf_version') or '-'}",
|
|
42
42
|
]
|
|
43
|
+
request_route = result.get("request_route") if isinstance(result.get("request_route"), dict) else {}
|
|
44
|
+
route_qf_version = request_route.get("qf_version")
|
|
45
|
+
if route_qf_version and route_qf_version != result.get("qf_version"):
|
|
46
|
+
lines.append(f"Request Route QF Version: {route_qf_version}")
|
|
43
47
|
lines.append(f"Permission Level: {result.get('permission_level') or '-'}")
|
|
44
48
|
departments = result.get("departments") if isinstance(result.get("departments"), list) else []
|
|
45
49
|
roles = result.get("roles") if isinstance(result.get("roles"), list) else []
|
|
@@ -82,6 +86,19 @@ def _format_workspace_list(result: dict[str, Any]) -> str:
|
|
|
82
86
|
return _render_titled_table("Workspaces", ["ws_id", "name", "remark"], rows)
|
|
83
87
|
|
|
84
88
|
|
|
89
|
+
def _format_workspace_get(result: dict[str, Any]) -> str:
|
|
90
|
+
workspace = result.get("workspace") if isinstance(result.get("workspace"), dict) else {}
|
|
91
|
+
lines = [
|
|
92
|
+
f"Workspace: {workspace.get('workspaceName') or workspace.get('wsName') or '-'} ({workspace.get('wsId') or result.get('ws_id') or '-'})",
|
|
93
|
+
f"QF Version: {result.get('qf_version') or workspace.get('systemVersion') or '-'}",
|
|
94
|
+
f"Identity: {workspace.get('identity') or '-'}",
|
|
95
|
+
f"Auth: {workspace.get('auth') if workspace.get('auth') is not None else '-'}",
|
|
96
|
+
f"State: {workspace.get('state') if workspace.get('state') is not None else '-'}",
|
|
97
|
+
]
|
|
98
|
+
_append_warnings(lines, result.get("warnings"))
|
|
99
|
+
return "\n".join(lines) + "\n"
|
|
100
|
+
|
|
101
|
+
|
|
85
102
|
def _format_app_items(result: dict[str, Any]) -> str:
|
|
86
103
|
items = result.get("items")
|
|
87
104
|
if not isinstance(items, list):
|
|
@@ -344,6 +361,7 @@ def _first_present(payload: dict[str, Any], *keys: str) -> Any:
|
|
|
344
361
|
_FORMATTERS = {
|
|
345
362
|
"auth_whoami": _format_whoami,
|
|
346
363
|
"workspace_list": _format_workspace_list,
|
|
364
|
+
"workspace_get": _format_workspace_get,
|
|
347
365
|
"app_list": _format_app_items,
|
|
348
366
|
"app_search": _format_app_items,
|
|
349
367
|
"app_get": _format_app_get,
|
|
@@ -34,6 +34,7 @@ USER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
|
|
|
34
34
|
PublicToolSpec(USER_DOMAIN, "auth_whoami", ("auth_whoami",), ("auth", "whoami")),
|
|
35
35
|
PublicToolSpec(USER_DOMAIN, "auth_logout", ("auth_logout",), ("auth", "logout")),
|
|
36
36
|
PublicToolSpec(USER_DOMAIN, "workspace_list", ("workspace_list",), ("workspace", "list")),
|
|
37
|
+
PublicToolSpec(USER_DOMAIN, "workspace_get", ("workspace_get",), ("workspace", "get")),
|
|
37
38
|
PublicToolSpec(USER_DOMAIN, "app_list", ("app_list",), ("app", "list"), cli_show_effective_context=True),
|
|
38
39
|
PublicToolSpec(USER_DOMAIN, "app_search", ("app_search",), ("app", "search"), cli_show_effective_context=True),
|
|
39
40
|
PublicToolSpec(USER_DOMAIN, "app_get", ("app_get",), ("app", "get"), cli_show_effective_context=True),
|
|
@@ -109,6 +110,7 @@ BUILDER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
|
|
|
109
110
|
PublicToolSpec(BUILDER_DOMAIN, "auth_whoami", ("auth_whoami",), ("builder", "auth", "whoami"), cli_public=False),
|
|
110
111
|
PublicToolSpec(BUILDER_DOMAIN, "auth_logout", ("auth_logout",), ("builder", "auth", "logout"), cli_public=False),
|
|
111
112
|
PublicToolSpec(BUILDER_DOMAIN, "workspace_list", ("workspace_list",), ("builder", "workspace", "list"), cli_public=False),
|
|
113
|
+
PublicToolSpec(BUILDER_DOMAIN, "workspace_get", ("workspace_get",), ("builder", "workspace", "get"), cli_public=False),
|
|
112
114
|
PublicToolSpec(BUILDER_DOMAIN, "file_upload_local", ("file_upload_local",), ("builder", "file", "upload-local"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
|
|
113
115
|
PublicToolSpec(BUILDER_DOMAIN, "feedback_submit", ("feedback_submit",), ("builder", "feedback", "submit"), has_contract=True),
|
|
114
116
|
PublicToolSpec(BUILDER_DOMAIN, "builder_tool_contract", ("builder_tool_contract",), ("builder", "contract"), has_contract=False),
|
|
@@ -307,7 +307,7 @@ def build_reference_config(field: dict[str, Any], temp_id: int) -> dict[str, Any
|
|
|
307
307
|
"queId": que_id,
|
|
308
308
|
"queTitle": label,
|
|
309
309
|
"queType": _normalize_reference_que_type(raw_type) or "2",
|
|
310
|
-
"queAuth":
|
|
310
|
+
"queAuth": 3,
|
|
311
311
|
"ordinal": ordinal,
|
|
312
312
|
"quoteId": temp_id,
|
|
313
313
|
"_field_id": field_id,
|
|
@@ -320,7 +320,7 @@ def build_reference_config(field: dict[str, Any], temp_id: int) -> dict[str, Any
|
|
|
320
320
|
auth_ques = []
|
|
321
321
|
for ordinal, field_id in enumerate(auth_field_ids, start=1):
|
|
322
322
|
que_id = auth_field_que_ids[ordinal - 1] if ordinal - 1 < len(auth_field_que_ids) else 0
|
|
323
|
-
auth_ques.append({"queId": que_id, "queAuth":
|
|
323
|
+
auth_ques.append({"queId": que_id, "queAuth": 3, "_field_id": field_id})
|
|
324
324
|
return {
|
|
325
325
|
"referAppKey": "__TARGET_APP_KEY__",
|
|
326
326
|
"referQueId": display_field_que_id,
|
|
@@ -857,12 +857,12 @@ class SolutionExecutor:
|
|
|
857
857
|
if refer_que_id is None:
|
|
858
858
|
continue
|
|
859
859
|
resolved["queId"] = refer_que_id
|
|
860
|
-
resolved["queAuth"] = int(resolved.get("queAuth",
|
|
860
|
+
resolved["queAuth"] = int(resolved.get("queAuth", 3))
|
|
861
861
|
auth_ques.append(resolved)
|
|
862
862
|
if not auth_ques:
|
|
863
863
|
fallback_que_id = target_meta.get("by_field_id", {}).get(target_field_id)
|
|
864
864
|
if fallback_que_id is not None:
|
|
865
|
-
auth_ques.append({"queId": fallback_que_id, "queAuth":
|
|
865
|
+
auth_ques.append({"queId": fallback_que_id, "queAuth": 3})
|
|
866
866
|
reference_config["referAuthQues"] = auth_ques
|
|
867
867
|
reference_config["fieldNameShow"] = bool(reference_config.get("fieldNameShow", True))
|
|
868
868
|
fill_rules = []
|
|
@@ -212,6 +212,36 @@ class AuthTools(ToolBase):
|
|
|
212
212
|
backend_session, # type: ignore[no-untyped-def]
|
|
213
213
|
context: BackendRequestContext,
|
|
214
214
|
) -> dict[str, Any]:
|
|
215
|
+
workspace, workspace_qf_version = self._selected_workspace_snapshot(
|
|
216
|
+
session_profile=session_profile,
|
|
217
|
+
backend_session=backend_session,
|
|
218
|
+
)
|
|
219
|
+
resolved_qf_version = workspace_qf_version or session_profile.qf_version
|
|
220
|
+
resolved_qf_version_source = (
|
|
221
|
+
"workspace_system_version"
|
|
222
|
+
if workspace_qf_version is not None
|
|
223
|
+
else session_profile.qf_version_source
|
|
224
|
+
)
|
|
225
|
+
if (
|
|
226
|
+
workspace_qf_version is not None
|
|
227
|
+
and (
|
|
228
|
+
workspace_qf_version != session_profile.qf_version
|
|
229
|
+
or session_profile.qf_version_source != "workspace_system_version"
|
|
230
|
+
)
|
|
231
|
+
):
|
|
232
|
+
session_profile = self.sessions.update_route(
|
|
233
|
+
profile,
|
|
234
|
+
qf_version=workspace_qf_version,
|
|
235
|
+
qf_version_source="workspace_system_version",
|
|
236
|
+
)
|
|
237
|
+
backend_session = self.sessions.get_backend_session(profile) or backend_session
|
|
238
|
+
context = BackendRequestContext(
|
|
239
|
+
base_url=backend_session.base_url,
|
|
240
|
+
token=backend_session.token,
|
|
241
|
+
ws_id=session_profile.selected_ws_id,
|
|
242
|
+
qf_version=backend_session.qf_version,
|
|
243
|
+
qf_version_source=backend_session.qf_version_source,
|
|
244
|
+
)
|
|
215
245
|
if self._should_refresh_identity_metadata(session_profile):
|
|
216
246
|
refreshed_profile = self._refresh_identity_metadata(
|
|
217
247
|
profile=profile,
|
|
@@ -224,8 +254,8 @@ class AuthTools(ToolBase):
|
|
|
224
254
|
response = {
|
|
225
255
|
"profile": session_profile.profile,
|
|
226
256
|
"base_url": session_profile.base_url,
|
|
227
|
-
"qf_version":
|
|
228
|
-
"qf_version_source":
|
|
257
|
+
"qf_version": resolved_qf_version,
|
|
258
|
+
"qf_version_source": resolved_qf_version_source,
|
|
229
259
|
"uid": session_profile.uid,
|
|
230
260
|
"email": session_profile.email,
|
|
231
261
|
"nick_name": session_profile.nick_name,
|
|
@@ -512,6 +542,24 @@ class AuthTools(ToolBase):
|
|
|
512
542
|
raise_tool_error(QingflowApiError(category="workspace", message=f"Workspace {ws_id} is not accessible"))
|
|
513
543
|
return workspace
|
|
514
544
|
|
|
545
|
+
def _selected_workspace_snapshot(
|
|
546
|
+
self,
|
|
547
|
+
*,
|
|
548
|
+
session_profile, # type: ignore[no-untyped-def]
|
|
549
|
+
backend_session, # type: ignore[no-untyped-def]
|
|
550
|
+
) -> tuple[dict[str, Any] | None, str | None]:
|
|
551
|
+
ws_id = session_profile.selected_ws_id
|
|
552
|
+
if ws_id is None:
|
|
553
|
+
return None, None
|
|
554
|
+
workspace = self._fetch_workspace_with_name_fallback(
|
|
555
|
+
session_profile.base_url,
|
|
556
|
+
backend_session.token,
|
|
557
|
+
ws_id,
|
|
558
|
+
qf_version=session_profile.qf_version,
|
|
559
|
+
qf_version_source=session_profile.qf_version_source,
|
|
560
|
+
)
|
|
561
|
+
return workspace, self._workspace_system_version(workspace)
|
|
562
|
+
|
|
515
563
|
def _request_route_payload(self, context: BackendRequestContext) -> dict[str, Any]:
|
|
516
564
|
"""执行内部辅助逻辑。"""
|
|
517
565
|
describe_route = getattr(self.backend, "describe_route", None)
|
|
@@ -35,6 +35,16 @@ class WorkspaceTools(ToolBase):
|
|
|
35
35
|
include_external=include_external,
|
|
36
36
|
)
|
|
37
37
|
|
|
38
|
+
@mcp.tool()
|
|
39
|
+
def workspace_get(
|
|
40
|
+
profile: str = DEFAULT_PROFILE,
|
|
41
|
+
ws_id: int = 0,
|
|
42
|
+
) -> dict[str, Any]:
|
|
43
|
+
return self.workspace_get(
|
|
44
|
+
profile=profile,
|
|
45
|
+
ws_id=ws_id if ws_id > 0 else None,
|
|
46
|
+
)
|
|
47
|
+
|
|
38
48
|
@mcp.tool()
|
|
39
49
|
def workspace_set_plugin_status(
|
|
40
50
|
profile: str = DEFAULT_PROFILE,
|
|
@@ -93,6 +103,31 @@ class WorkspaceTools(ToolBase):
|
|
|
93
103
|
|
|
94
104
|
return self._run(profile, runner, require_workspace=False)
|
|
95
105
|
|
|
106
|
+
@tool_cn_name("工作区详情")
|
|
107
|
+
def workspace_get(
|
|
108
|
+
self,
|
|
109
|
+
*,
|
|
110
|
+
profile: str = DEFAULT_PROFILE,
|
|
111
|
+
ws_id: int | None = None,
|
|
112
|
+
) -> dict[str, Any]:
|
|
113
|
+
"""读取单个工作区详情,并尽量补齐真实 systemVersion。"""
|
|
114
|
+
|
|
115
|
+
def runner(session_profile, context):
|
|
116
|
+
target_ws_id = ws_id or (session_profile.selected_ws_id if session_profile is not None else None)
|
|
117
|
+
if target_ws_id is None or target_ws_id <= 0:
|
|
118
|
+
raise_tool_error(QingflowApiError.workspace_not_selected(profile))
|
|
119
|
+
workspace = self._fetch_workspace_with_fallback(context, ws_id=target_ws_id)
|
|
120
|
+
system_version = self._workspace_system_version(workspace)
|
|
121
|
+
return {
|
|
122
|
+
"profile": profile,
|
|
123
|
+
"ws_id": target_ws_id,
|
|
124
|
+
"qf_version": system_version,
|
|
125
|
+
"qf_version_source": "workspace_system_version" if system_version else "unverified",
|
|
126
|
+
"workspace": workspace,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return self._run(profile, runner, require_workspace=False)
|
|
130
|
+
|
|
96
131
|
@tool_cn_name("设置工作区插件状态")
|
|
97
132
|
def workspace_set_plugin_status(
|
|
98
133
|
self,
|
|
@@ -123,3 +158,62 @@ class WorkspaceTools(ToolBase):
|
|
|
123
158
|
}
|
|
124
159
|
|
|
125
160
|
return self._run(profile, runner)
|
|
161
|
+
|
|
162
|
+
def _fetch_workspace_with_fallback(
|
|
163
|
+
self,
|
|
164
|
+
context: BackendRequestContext,
|
|
165
|
+
*,
|
|
166
|
+
ws_id: int,
|
|
167
|
+
) -> dict[str, Any]:
|
|
168
|
+
workspace = self.backend.request("GET", context, f"/user/workspace/{ws_id}")
|
|
169
|
+
if not isinstance(workspace, dict):
|
|
170
|
+
raise_tool_error(QingflowApiError(category="workspace", message=f"Workspace {ws_id} is not accessible"))
|
|
171
|
+
if self._workspace_needs_list_fallback(workspace):
|
|
172
|
+
fallback = self._fetch_workspace_from_list(context, ws_id=ws_id)
|
|
173
|
+
if isinstance(fallback, dict):
|
|
174
|
+
merged = dict(workspace)
|
|
175
|
+
for key, value in fallback.items():
|
|
176
|
+
if merged.get(key) in (None, "") and value not in (None, ""):
|
|
177
|
+
merged[key] = value
|
|
178
|
+
workspace = merged
|
|
179
|
+
return workspace
|
|
180
|
+
|
|
181
|
+
def _fetch_workspace_from_list(self, context: BackendRequestContext, *, ws_id: int) -> dict[str, Any] | None:
|
|
182
|
+
payload = self.backend.request(
|
|
183
|
+
"POST",
|
|
184
|
+
BackendRequestContext(
|
|
185
|
+
base_url=context.base_url,
|
|
186
|
+
token=context.token,
|
|
187
|
+
ws_id=None,
|
|
188
|
+
qf_version=context.qf_version,
|
|
189
|
+
qf_version_source=context.qf_version_source,
|
|
190
|
+
),
|
|
191
|
+
"/user/workspaceList/pageQuery",
|
|
192
|
+
json_body={"pageNum": 1, "pageSize": 100, "authList": [0, 1, 2, 3]},
|
|
193
|
+
)
|
|
194
|
+
workspaces = payload.get("list") if isinstance(payload, dict) else []
|
|
195
|
+
if not isinstance(workspaces, list):
|
|
196
|
+
return None
|
|
197
|
+
found = next(
|
|
198
|
+
(
|
|
199
|
+
item
|
|
200
|
+
for item in workspaces
|
|
201
|
+
if isinstance(item, dict) and item.get("wsId") == ws_id
|
|
202
|
+
),
|
|
203
|
+
None,
|
|
204
|
+
)
|
|
205
|
+
return found if isinstance(found, dict) else None
|
|
206
|
+
|
|
207
|
+
def _workspace_needs_list_fallback(self, workspace: dict[str, Any]) -> bool:
|
|
208
|
+
workspace_name = str(workspace.get("workspaceName") or workspace.get("wsName") or "").strip()
|
|
209
|
+
system_version = self._workspace_system_version(workspace)
|
|
210
|
+
return not workspace_name or system_version is None
|
|
211
|
+
|
|
212
|
+
def _workspace_system_version(self, workspace: Any) -> str | None:
|
|
213
|
+
if not isinstance(workspace, dict):
|
|
214
|
+
return None
|
|
215
|
+
value = workspace.get("systemVersion")
|
|
216
|
+
if value is None:
|
|
217
|
+
return None
|
|
218
|
+
normalized = str(value).strip()
|
|
219
|
+
return normalized or None
|