@josephyan/qingflow-app-user-mcp 0.2.0-beta.89 → 0.2.0-beta.90
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/src/qingflow_mcp/tools/record_tools.py +294 -88
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.90
|
|
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.90 qingflow-app-user-mcp
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
package/package.json
CHANGED
|
@@ -180,6 +180,16 @@ class AccessibleViewRoute:
|
|
|
180
180
|
view_type: str | None = None
|
|
181
181
|
|
|
182
182
|
|
|
183
|
+
@dataclass(slots=True)
|
|
184
|
+
class RecordContextRouteProbe:
|
|
185
|
+
route: AccessibleViewRoute
|
|
186
|
+
answer_list: list[JSONObject] | None
|
|
187
|
+
used_list_type: int | None
|
|
188
|
+
readable: bool
|
|
189
|
+
transport_error: bool
|
|
190
|
+
error_payload: JSONObject | None
|
|
191
|
+
|
|
192
|
+
|
|
183
193
|
@dataclass(slots=True)
|
|
184
194
|
class WorkflowNodeRef:
|
|
185
195
|
workflow_node_id: int
|
|
@@ -782,55 +792,29 @@ class RecordTools(ToolBase):
|
|
|
782
792
|
index=app_index,
|
|
783
793
|
question_relations=question_relations,
|
|
784
794
|
)
|
|
785
|
-
try:
|
|
786
|
-
current_answers = self._load_record_answers_for_preflight(context, app_key=app_key, apply_id=record_id)
|
|
787
|
-
except QingflowApiError:
|
|
788
|
-
return self._record_update_schema_blocked_response(
|
|
789
|
-
profile=profile,
|
|
790
|
-
ws_id=session_profile.selected_ws_id,
|
|
791
|
-
request_route=request_route,
|
|
792
|
-
app_key=app_key,
|
|
793
|
-
record_id=record_id,
|
|
794
|
-
blockers=["CURRENT_RECORD_CONTEXT_UNAVAILABLE"],
|
|
795
|
-
warnings=[
|
|
796
|
-
"update schema could not load the current record; context-sensitive lookup requirements cannot be derived safely."
|
|
797
|
-
],
|
|
798
|
-
recommended_next_actions=[
|
|
799
|
-
"Retry after the record becomes readable in the current workspace/profile context.",
|
|
800
|
-
"If the issue persists, verify that the current profile still has read access to this record.",
|
|
801
|
-
],
|
|
802
|
-
output_profile=normalized_output_profile,
|
|
803
|
-
view_probe_summary=[],
|
|
804
|
-
ambiguous_fields=[],
|
|
805
|
-
)
|
|
806
|
-
|
|
807
795
|
candidate_routes = self._candidate_update_views(profile, context, app_key)
|
|
796
|
+
probes = self._probe_candidate_record_contexts(
|
|
797
|
+
context,
|
|
798
|
+
app_key=app_key,
|
|
799
|
+
apply_id=record_id,
|
|
800
|
+
candidate_routes=candidate_routes,
|
|
801
|
+
)
|
|
808
802
|
probe_summary: list[JSONObject] = []
|
|
809
|
-
|
|
803
|
+
matched_probes: list[RecordContextRouteProbe] = []
|
|
810
804
|
ordered_field_ids: list[int] = []
|
|
811
805
|
field_payloads_by_id: dict[int, JSONObject] = {}
|
|
812
806
|
title_to_field_ids: dict[str, list[int]] = {}
|
|
813
807
|
ws_id = session_profile.selected_ws_id
|
|
814
808
|
|
|
815
|
-
for
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
)
|
|
821
|
-
candidate_summary: JSONObject = {
|
|
822
|
-
"view_id": candidate.view_id,
|
|
823
|
-
"name": candidate.name,
|
|
824
|
-
"kind": candidate.kind,
|
|
825
|
-
"matched_record": matched_record,
|
|
826
|
-
"context_complete": True,
|
|
827
|
-
"writable_field_titles": [],
|
|
828
|
-
}
|
|
829
|
-
if not matched_record:
|
|
809
|
+
for probe in probes:
|
|
810
|
+
candidate = probe.route
|
|
811
|
+
candidate_summary = self._record_context_probe_summary_payload(probe)
|
|
812
|
+
candidate_summary["writable_field_titles"] = []
|
|
813
|
+
if not probe.readable:
|
|
830
814
|
probe_summary.append(candidate_summary)
|
|
831
815
|
continue
|
|
832
816
|
|
|
833
|
-
|
|
817
|
+
matched_probes.append(probe)
|
|
834
818
|
browse_scope = self._build_browse_write_scope(
|
|
835
819
|
profile,
|
|
836
820
|
context,
|
|
@@ -874,19 +858,39 @@ class RecordTools(ToolBase):
|
|
|
874
858
|
candidate_summary["writable_field_titles"] = candidate_titles
|
|
875
859
|
probe_summary.append(candidate_summary)
|
|
876
860
|
|
|
877
|
-
if not
|
|
861
|
+
if not matched_probes:
|
|
862
|
+
blockers = (
|
|
863
|
+
["CURRENT_RECORD_CONTEXT_UNAVAILABLE"]
|
|
864
|
+
if probes and all(probe.transport_error for probe in probes)
|
|
865
|
+
else ["NO_MATCHING_ACCESSIBLE_VIEW_FOR_RECORD"]
|
|
866
|
+
)
|
|
867
|
+
warnings = (
|
|
868
|
+
[
|
|
869
|
+
"update schema could not load the current record from any candidate route; context-sensitive lookup requirements cannot be derived safely."
|
|
870
|
+
]
|
|
871
|
+
if blockers == ["CURRENT_RECORD_CONTEXT_UNAVAILABLE"]
|
|
872
|
+
else []
|
|
873
|
+
)
|
|
874
|
+
recommended_next_actions = (
|
|
875
|
+
[
|
|
876
|
+
"Retry after the record becomes readable in the current workspace/profile context.",
|
|
877
|
+
"If the issue persists, verify that the current profile still has read access to this record.",
|
|
878
|
+
]
|
|
879
|
+
if blockers == ["CURRENT_RECORD_CONTEXT_UNAVAILABLE"]
|
|
880
|
+
else [
|
|
881
|
+
"Check whether this record is still visible in any accessible view for the current profile.",
|
|
882
|
+
"Use record_get or record_list to confirm the record still exists in the current workspace.",
|
|
883
|
+
]
|
|
884
|
+
)
|
|
878
885
|
return self._record_update_schema_blocked_response(
|
|
879
886
|
profile=profile,
|
|
880
887
|
ws_id=ws_id,
|
|
881
888
|
request_route=request_route,
|
|
882
889
|
app_key=app_key,
|
|
883
890
|
record_id=record_id,
|
|
884
|
-
blockers=
|
|
885
|
-
warnings=
|
|
886
|
-
recommended_next_actions=
|
|
887
|
-
"Check whether this record is still visible in any accessible view for the current profile.",
|
|
888
|
-
"Use record_get or record_list to confirm the record still exists in the current workspace.",
|
|
889
|
-
],
|
|
891
|
+
blockers=blockers,
|
|
892
|
+
warnings=warnings,
|
|
893
|
+
recommended_next_actions=recommended_next_actions,
|
|
890
894
|
output_profile=normalized_output_profile,
|
|
891
895
|
view_probe_summary=probe_summary,
|
|
892
896
|
ambiguous_fields=[],
|
|
@@ -894,7 +898,10 @@ class RecordTools(ToolBase):
|
|
|
894
898
|
|
|
895
899
|
ambiguous_field_ids: set[int] = set()
|
|
896
900
|
ambiguous_fields: list[JSONObject] = []
|
|
897
|
-
warnings: list[JSONObject] = [
|
|
901
|
+
warnings: list[JSONObject] = [
|
|
902
|
+
{"code": "RECORD_CONTEXT_ROUTE_FALLBACK", "message": message}
|
|
903
|
+
for message in self._record_context_probe_fallback_warnings(probes)
|
|
904
|
+
]
|
|
898
905
|
for title, field_ids in title_to_field_ids.items():
|
|
899
906
|
unique_ids = list(dict.fromkeys(field_ids))
|
|
900
907
|
if len(unique_ids) <= 1:
|
|
@@ -953,6 +960,7 @@ class RecordTools(ToolBase):
|
|
|
953
960
|
}
|
|
954
961
|
if normalized_output_profile == "verbose":
|
|
955
962
|
response["view_probe_summary"] = probe_summary
|
|
963
|
+
response["record_context_probe"] = probe_summary
|
|
956
964
|
response["ambiguous_fields"] = ambiguous_fields
|
|
957
965
|
return response
|
|
958
966
|
|
|
@@ -2286,9 +2294,15 @@ class RecordTools(ToolBase):
|
|
|
2286
2294
|
def runner(session_profile, context):
|
|
2287
2295
|
request_route = self._request_route_payload(context)
|
|
2288
2296
|
def build_once(*, effective_force_refresh: bool) -> JSONObject:
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2297
|
+
candidate_routes = self._candidate_update_views(profile, context, app_key)
|
|
2298
|
+
probes = self._probe_candidate_record_contexts(
|
|
2299
|
+
context,
|
|
2300
|
+
app_key=app_key,
|
|
2301
|
+
apply_id=record_id,
|
|
2302
|
+
candidate_routes=candidate_routes,
|
|
2303
|
+
)
|
|
2304
|
+
matched_probes = [probe for probe in probes if probe.readable]
|
|
2305
|
+
if not matched_probes and probes and all(probe.transport_error for probe in probes):
|
|
2292
2306
|
return {
|
|
2293
2307
|
"profile": profile,
|
|
2294
2308
|
"ws_id": session_profile.selected_ws_id,
|
|
@@ -2299,46 +2313,40 @@ class RecordTools(ToolBase):
|
|
|
2299
2313
|
record_id=record_id,
|
|
2300
2314
|
blockers=["CURRENT_RECORD_CONTEXT_UNAVAILABLE"],
|
|
2301
2315
|
warnings=[
|
|
2302
|
-
"update preflight could not load the current record; automatic view selection was blocked to avoid resolving lookup fields against a partial patch."
|
|
2316
|
+
"update preflight could not load the current record from any candidate route; automatic view selection was blocked to avoid resolving lookup fields against a partial patch."
|
|
2303
2317
|
],
|
|
2304
2318
|
recommended_next_actions=[
|
|
2305
2319
|
"Retry after the record becomes readable in the current workspace/profile context.",
|
|
2306
2320
|
"Call record_update_schema_get to inspect the overall writable field set for this record after context access is restored.",
|
|
2307
2321
|
],
|
|
2308
|
-
view_probe_summary=[
|
|
2322
|
+
view_probe_summary=[
|
|
2323
|
+
self._record_context_probe_summary_payload(probe)
|
|
2324
|
+
for probe in probes
|
|
2325
|
+
],
|
|
2309
2326
|
),
|
|
2310
2327
|
}
|
|
2311
2328
|
|
|
2312
|
-
candidate_routes = self._candidate_update_views(profile, context, app_key)
|
|
2313
2329
|
probe_summary: list[JSONObject] = []
|
|
2314
|
-
matched_any = False
|
|
2315
2330
|
matched_routes: list[AccessibleViewRoute] = []
|
|
2331
|
+
matched_answers_for_union: list[JSONObject] | None = None
|
|
2316
2332
|
first_blocked_plan: JSONObject | None = None
|
|
2317
2333
|
first_confirmation_plan: JSONObject | None = None
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
"view_id": candidate.view_id,
|
|
2329
|
-
"name": candidate.name,
|
|
2330
|
-
"kind": candidate.kind,
|
|
2331
|
-
"matched_record": False,
|
|
2332
|
-
"writable_field_titles": [],
|
|
2333
|
-
"missing_field_titles": [],
|
|
2334
|
-
"context_complete": True,
|
|
2335
|
-
"selected": False,
|
|
2336
|
-
}
|
|
2337
|
-
)
|
|
2334
|
+
fallback_warning_messages = self._record_context_probe_fallback_warnings(probes)
|
|
2335
|
+
|
|
2336
|
+
for probe in probes:
|
|
2337
|
+
candidate = probe.route
|
|
2338
|
+
candidate_summary = self._record_context_probe_summary_payload(probe)
|
|
2339
|
+
candidate_summary["writable_field_titles"] = []
|
|
2340
|
+
candidate_summary["missing_field_titles"] = []
|
|
2341
|
+
candidate_summary["selected"] = False
|
|
2342
|
+
if not probe.readable:
|
|
2343
|
+
probe_summary.append(candidate_summary)
|
|
2338
2344
|
continue
|
|
2339
2345
|
|
|
2340
|
-
|
|
2346
|
+
current_answers = probe.answer_list or []
|
|
2341
2347
|
matched_routes.append(candidate)
|
|
2348
|
+
if matched_answers_for_union is None:
|
|
2349
|
+
matched_answers_for_union = current_answers
|
|
2342
2350
|
browse_scope = self._build_browse_write_scope(
|
|
2343
2351
|
profile,
|
|
2344
2352
|
context,
|
|
@@ -2394,16 +2402,8 @@ class RecordTools(ToolBase):
|
|
|
2394
2402
|
title = _normalize_optional_text(field_payload.get("que_title"))
|
|
2395
2403
|
if title and title not in missing_field_titles:
|
|
2396
2404
|
missing_field_titles.append(title)
|
|
2397
|
-
candidate_summary
|
|
2398
|
-
|
|
2399
|
-
"name": candidate.name,
|
|
2400
|
-
"kind": candidate.kind,
|
|
2401
|
-
"matched_record": True,
|
|
2402
|
-
"writable_field_titles": candidate_titles,
|
|
2403
|
-
"missing_field_titles": missing_field_titles,
|
|
2404
|
-
"context_complete": True,
|
|
2405
|
-
"selected": False,
|
|
2406
|
-
}
|
|
2405
|
+
candidate_summary["writable_field_titles"] = candidate_titles
|
|
2406
|
+
candidate_summary["missing_field_titles"] = missing_field_titles
|
|
2407
2407
|
if plan_data.get("blockers"):
|
|
2408
2408
|
confirmation_requests = plan_data.get("confirmation_requests")
|
|
2409
2409
|
if (
|
|
@@ -2422,8 +2422,26 @@ class RecordTools(ToolBase):
|
|
|
2422
2422
|
view_payload["auto_selected"] = True
|
|
2423
2423
|
view_payload["selection_source"] = "first_satisfying_accessible_view"
|
|
2424
2424
|
candidate_summary["selected"] = True
|
|
2425
|
+
validation = plan_data.get("validation")
|
|
2426
|
+
if isinstance(validation, dict):
|
|
2427
|
+
warnings = validation.get("warnings")
|
|
2428
|
+
if not isinstance(warnings, list):
|
|
2429
|
+
warnings = []
|
|
2430
|
+
validation["warnings"] = warnings
|
|
2431
|
+
for message in fallback_warning_messages:
|
|
2432
|
+
if message not in warnings:
|
|
2433
|
+
warnings.append(message)
|
|
2425
2434
|
first_confirmation_plan = plan_data
|
|
2426
2435
|
elif first_blocked_plan is None:
|
|
2436
|
+
validation = plan_data.get("validation")
|
|
2437
|
+
if isinstance(validation, dict):
|
|
2438
|
+
warnings = validation.get("warnings")
|
|
2439
|
+
if not isinstance(warnings, list):
|
|
2440
|
+
warnings = []
|
|
2441
|
+
validation["warnings"] = warnings
|
|
2442
|
+
for message in fallback_warning_messages:
|
|
2443
|
+
if message not in warnings:
|
|
2444
|
+
warnings.append(message)
|
|
2427
2445
|
first_blocked_plan = plan_data
|
|
2428
2446
|
probe_summary.append(candidate_summary)
|
|
2429
2447
|
continue
|
|
@@ -2439,8 +2457,18 @@ class RecordTools(ToolBase):
|
|
|
2439
2457
|
view_payload["auto_selected"] = True
|
|
2440
2458
|
view_payload["selection_source"] = "first_satisfying_accessible_view"
|
|
2441
2459
|
candidate_summary["selected"] = True
|
|
2460
|
+
validation = plan_data.get("validation")
|
|
2461
|
+
if isinstance(validation, dict):
|
|
2462
|
+
warnings = validation.get("warnings")
|
|
2463
|
+
if not isinstance(warnings, list):
|
|
2464
|
+
warnings = []
|
|
2465
|
+
validation["warnings"] = warnings
|
|
2466
|
+
for message in fallback_warning_messages:
|
|
2467
|
+
if message not in warnings:
|
|
2468
|
+
warnings.append(message)
|
|
2442
2469
|
probe_summary.append(candidate_summary)
|
|
2443
2470
|
plan_data["view_probe_summary"] = probe_summary
|
|
2471
|
+
plan_data["record_context_probe"] = probe_summary
|
|
2444
2472
|
return {
|
|
2445
2473
|
"profile": profile,
|
|
2446
2474
|
"ws_id": session_profile.selected_ws_id,
|
|
@@ -2449,7 +2477,7 @@ class RecordTools(ToolBase):
|
|
|
2449
2477
|
"data": plan_data,
|
|
2450
2478
|
}
|
|
2451
2479
|
|
|
2452
|
-
if not
|
|
2480
|
+
if not matched_probes:
|
|
2453
2481
|
blocked_data = self._build_auto_view_blocked_preflight_data(
|
|
2454
2482
|
app_key=app_key,
|
|
2455
2483
|
record_id=record_id,
|
|
@@ -2471,6 +2499,7 @@ class RecordTools(ToolBase):
|
|
|
2471
2499
|
|
|
2472
2500
|
if first_confirmation_plan is not None:
|
|
2473
2501
|
first_confirmation_plan["view_probe_summary"] = probe_summary
|
|
2502
|
+
first_confirmation_plan["record_context_probe"] = probe_summary
|
|
2474
2503
|
return {
|
|
2475
2504
|
"profile": profile,
|
|
2476
2505
|
"ws_id": session_profile.selected_ws_id,
|
|
@@ -2485,12 +2514,22 @@ class RecordTools(ToolBase):
|
|
|
2485
2514
|
app_key=app_key,
|
|
2486
2515
|
record_id=record_id,
|
|
2487
2516
|
fields=fields,
|
|
2488
|
-
current_answers=
|
|
2517
|
+
current_answers=matched_answers_for_union or [],
|
|
2489
2518
|
matched_routes=matched_routes,
|
|
2490
2519
|
force_refresh_form=effective_force_refresh,
|
|
2491
2520
|
)
|
|
2492
2521
|
if union_plan is not None:
|
|
2522
|
+
validation = union_plan.get("validation")
|
|
2523
|
+
if isinstance(validation, dict):
|
|
2524
|
+
warnings = validation.get("warnings")
|
|
2525
|
+
if not isinstance(warnings, list):
|
|
2526
|
+
warnings = []
|
|
2527
|
+
validation["warnings"] = warnings
|
|
2528
|
+
for message in fallback_warning_messages:
|
|
2529
|
+
if message not in warnings:
|
|
2530
|
+
warnings.append(message)
|
|
2493
2531
|
union_plan["view_probe_summary"] = probe_summary
|
|
2532
|
+
union_plan["record_context_probe"] = probe_summary
|
|
2494
2533
|
return {
|
|
2495
2534
|
"profile": profile,
|
|
2496
2535
|
"ws_id": session_profile.selected_ws_id,
|
|
@@ -2785,6 +2824,161 @@ class RecordTools(ToolBase):
|
|
|
2785
2824
|
)
|
|
2786
2825
|
return candidates
|
|
2787
2826
|
|
|
2827
|
+
def _route_view_key(self, resolved_view: AccessibleViewRoute) -> str | None:
|
|
2828
|
+
if resolved_view.view_selection is not None:
|
|
2829
|
+
view_key = _normalize_optional_text(resolved_view.view_selection.view_key)
|
|
2830
|
+
if view_key:
|
|
2831
|
+
return view_key
|
|
2832
|
+
if resolved_view.kind == "custom" and resolved_view.view_id.startswith("custom:"):
|
|
2833
|
+
view_key = resolved_view.view_id.split(":", 1)[1].strip()
|
|
2834
|
+
return view_key or None
|
|
2835
|
+
return None
|
|
2836
|
+
|
|
2837
|
+
def _record_context_route_error_payload(self, error: QingflowApiError) -> JSONObject:
|
|
2838
|
+
payload: JSONObject = {"message": error.message}
|
|
2839
|
+
if error.category:
|
|
2840
|
+
payload["category"] = error.category
|
|
2841
|
+
if error.backend_code is not None:
|
|
2842
|
+
payload["backend_code"] = error.backend_code
|
|
2843
|
+
if error.http_status is not None:
|
|
2844
|
+
payload["http_status"] = error.http_status
|
|
2845
|
+
return payload
|
|
2846
|
+
|
|
2847
|
+
def _is_record_context_route_miss(self, error: QingflowApiError) -> bool:
|
|
2848
|
+
if error.backend_code in {40002, 40027, 40038, 404}:
|
|
2849
|
+
return True
|
|
2850
|
+
if error.http_status == 404:
|
|
2851
|
+
return True
|
|
2852
|
+
return False
|
|
2853
|
+
|
|
2854
|
+
def _load_record_answers_for_accessible_route(
|
|
2855
|
+
self,
|
|
2856
|
+
context, # type: ignore[no-untyped-def]
|
|
2857
|
+
*,
|
|
2858
|
+
app_key: str,
|
|
2859
|
+
apply_id: int,
|
|
2860
|
+
resolved_view: AccessibleViewRoute,
|
|
2861
|
+
) -> tuple[list[JSONObject], int | None]:
|
|
2862
|
+
if resolved_view.kind == "custom":
|
|
2863
|
+
view_key = self._route_view_key(resolved_view)
|
|
2864
|
+
if not view_key:
|
|
2865
|
+
raise_tool_error(
|
|
2866
|
+
QingflowApiError.config_error(
|
|
2867
|
+
f"cannot resolve custom view route for '{resolved_view.view_id}'"
|
|
2868
|
+
)
|
|
2869
|
+
)
|
|
2870
|
+
record = self.backend.request(
|
|
2871
|
+
"GET",
|
|
2872
|
+
context,
|
|
2873
|
+
f"/view/{view_key}/apply/{apply_id}",
|
|
2874
|
+
)
|
|
2875
|
+
used_list_type = None
|
|
2876
|
+
else:
|
|
2877
|
+
used_list_type = resolved_view.list_type if resolved_view.list_type is not None else DEFAULT_RECORD_LIST_TYPE
|
|
2878
|
+
record = self.backend.request(
|
|
2879
|
+
"GET",
|
|
2880
|
+
context,
|
|
2881
|
+
f"/app/{app_key}/apply/{apply_id}",
|
|
2882
|
+
params={"role": 1, "listType": used_list_type},
|
|
2883
|
+
)
|
|
2884
|
+
answers = record.get("answers") if isinstance(record, dict) else None
|
|
2885
|
+
normalized_answers = [item for item in answers if isinstance(item, dict)] if isinstance(answers, list) else []
|
|
2886
|
+
return normalized_answers, used_list_type
|
|
2887
|
+
|
|
2888
|
+
def _probe_record_context_route(
|
|
2889
|
+
self,
|
|
2890
|
+
context, # type: ignore[no-untyped-def]
|
|
2891
|
+
*,
|
|
2892
|
+
app_key: str,
|
|
2893
|
+
apply_id: int,
|
|
2894
|
+
resolved_view: AccessibleViewRoute,
|
|
2895
|
+
) -> RecordContextRouteProbe:
|
|
2896
|
+
try:
|
|
2897
|
+
answer_list, used_list_type = self._load_record_answers_for_accessible_route(
|
|
2898
|
+
context,
|
|
2899
|
+
app_key=app_key,
|
|
2900
|
+
apply_id=apply_id,
|
|
2901
|
+
resolved_view=resolved_view,
|
|
2902
|
+
)
|
|
2903
|
+
return RecordContextRouteProbe(
|
|
2904
|
+
route=resolved_view,
|
|
2905
|
+
answer_list=answer_list,
|
|
2906
|
+
used_list_type=used_list_type,
|
|
2907
|
+
readable=True,
|
|
2908
|
+
transport_error=False,
|
|
2909
|
+
error_payload=None,
|
|
2910
|
+
)
|
|
2911
|
+
except QingflowApiError as exc:
|
|
2912
|
+
return RecordContextRouteProbe(
|
|
2913
|
+
route=resolved_view,
|
|
2914
|
+
answer_list=None,
|
|
2915
|
+
used_list_type=resolved_view.list_type if resolved_view.kind == "system" else None,
|
|
2916
|
+
readable=False,
|
|
2917
|
+
transport_error=not self._is_record_context_route_miss(exc),
|
|
2918
|
+
error_payload=self._record_context_route_error_payload(exc),
|
|
2919
|
+
)
|
|
2920
|
+
|
|
2921
|
+
def _probe_candidate_record_contexts(
|
|
2922
|
+
self,
|
|
2923
|
+
context, # type: ignore[no-untyped-def]
|
|
2924
|
+
*,
|
|
2925
|
+
app_key: str,
|
|
2926
|
+
apply_id: int,
|
|
2927
|
+
candidate_routes: list[AccessibleViewRoute],
|
|
2928
|
+
) -> list[RecordContextRouteProbe]:
|
|
2929
|
+
return [
|
|
2930
|
+
self._probe_record_context_route(
|
|
2931
|
+
context,
|
|
2932
|
+
app_key=app_key,
|
|
2933
|
+
apply_id=apply_id,
|
|
2934
|
+
resolved_view=candidate,
|
|
2935
|
+
)
|
|
2936
|
+
for candidate in candidate_routes
|
|
2937
|
+
]
|
|
2938
|
+
|
|
2939
|
+
def _record_context_probe_summary_payload(self, probe: RecordContextRouteProbe) -> JSONObject:
|
|
2940
|
+
payload: JSONObject = {
|
|
2941
|
+
"view_id": probe.route.view_id,
|
|
2942
|
+
"name": probe.route.name,
|
|
2943
|
+
"kind": probe.route.kind,
|
|
2944
|
+
"matched_record": probe.readable,
|
|
2945
|
+
"readable": probe.readable,
|
|
2946
|
+
"context_complete": probe.readable,
|
|
2947
|
+
"used_list_type": probe.used_list_type,
|
|
2948
|
+
}
|
|
2949
|
+
if probe.error_payload is not None:
|
|
2950
|
+
payload["error"] = probe.error_payload
|
|
2951
|
+
payload["transport_error"] = probe.transport_error
|
|
2952
|
+
return payload
|
|
2953
|
+
|
|
2954
|
+
def _record_context_probe_fallback_warnings(
|
|
2955
|
+
self,
|
|
2956
|
+
probes: list[RecordContextRouteProbe],
|
|
2957
|
+
) -> list[str]:
|
|
2958
|
+
matched_probes = [probe for probe in probes if probe.readable]
|
|
2959
|
+
if not matched_probes:
|
|
2960
|
+
return []
|
|
2961
|
+
if any(
|
|
2962
|
+
probe.route.kind == "system" and probe.used_list_type == DEFAULT_RECORD_LIST_TYPE
|
|
2963
|
+
for probe in matched_probes
|
|
2964
|
+
):
|
|
2965
|
+
return []
|
|
2966
|
+
first_probe = matched_probes[0]
|
|
2967
|
+
if first_probe.route.kind == "custom":
|
|
2968
|
+
return [
|
|
2969
|
+
"current record context was not accessible via listType="
|
|
2970
|
+
f"{DEFAULT_RECORD_LIST_TYPE}; preflight resolved it via custom view "
|
|
2971
|
+
f"'{first_probe.route.name}'."
|
|
2972
|
+
]
|
|
2973
|
+
used_list_type = first_probe.used_list_type
|
|
2974
|
+
if used_list_type is None:
|
|
2975
|
+
return []
|
|
2976
|
+
return [
|
|
2977
|
+
"current record context was not accessible via listType="
|
|
2978
|
+
f"{DEFAULT_RECORD_LIST_TYPE}; preflight resolved it via listType={used_list_type} "
|
|
2979
|
+
f"({get_record_list_type_label(used_list_type)})."
|
|
2980
|
+
]
|
|
2981
|
+
|
|
2788
2982
|
def _record_matches_accessible_view(
|
|
2789
2983
|
self,
|
|
2790
2984
|
context, # type: ignore[no-untyped-def]
|
|
@@ -5276,7 +5470,19 @@ class RecordTools(ToolBase):
|
|
|
5276
5470
|
existing_answers_loaded = True
|
|
5277
5471
|
else:
|
|
5278
5472
|
try:
|
|
5279
|
-
|
|
5473
|
+
if resolved_view is not None:
|
|
5474
|
+
existing_answers_for_update, _used_list_type = self._load_record_answers_for_accessible_route(
|
|
5475
|
+
context,
|
|
5476
|
+
app_key=app_key,
|
|
5477
|
+
apply_id=apply_id,
|
|
5478
|
+
resolved_view=resolved_view,
|
|
5479
|
+
)
|
|
5480
|
+
else:
|
|
5481
|
+
existing_answers_for_update = self._load_record_answers_for_preflight(
|
|
5482
|
+
context,
|
|
5483
|
+
app_key=app_key,
|
|
5484
|
+
apply_id=apply_id,
|
|
5485
|
+
)
|
|
5280
5486
|
existing_answers_loaded = True
|
|
5281
5487
|
except QingflowApiError:
|
|
5282
5488
|
validation_warnings.append(
|