@qingflow-tech/qingflow-app-builder-mcp 1.0.2 → 1.0.4
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/docs/local-agent-install.md +9 -3
- package/npm/lib/runtime.mjs +10 -3
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-builder/SKILL.md +88 -184
- package/skills/qingflow-app-builder/references/create-app.md +15 -34
- package/skills/qingflow-app-builder/references/gotchas.md +3 -3
- package/skills/qingflow-app-builder/references/solution-playbooks.md +1 -2
- package/skills/qingflow-app-builder/references/tool-selection.md +9 -10
- package/src/qingflow_mcp/__init__.py +33 -1
- package/src/qingflow_mcp/backend_client.py +109 -0
- package/src/qingflow_mcp/builder_facade/button_style_catalog.py +282 -0
- package/src/qingflow_mcp/builder_facade/models.py +58 -9
- package/src/qingflow_mcp/builder_facade/service.py +1711 -240
- package/src/qingflow_mcp/cli/commands/__init__.py +2 -1
- package/src/qingflow_mcp/cli/commands/app.py +47 -1
- package/src/qingflow_mcp/cli/commands/auth.py +63 -0
- package/src/qingflow_mcp/cli/commands/builder.py +11 -3
- package/src/qingflow_mcp/cli/commands/exports.py +111 -0
- package/src/qingflow_mcp/cli/commands/record.py +5 -5
- package/src/qingflow_mcp/cli/commands/task.py +701 -27
- package/src/qingflow_mcp/cli/commands/workspace.py +84 -0
- package/src/qingflow_mcp/cli/context.py +3 -0
- package/src/qingflow_mcp/cli/formatters.py +424 -50
- package/src/qingflow_mcp/cli/interaction.py +72 -0
- package/src/qingflow_mcp/cli/main.py +11 -1
- package/src/qingflow_mcp/cli/qingflow_login.py +116 -0
- package/src/qingflow_mcp/cli/terminal_ui.py +218 -0
- package/src/qingflow_mcp/config.py +1 -1
- package/src/qingflow_mcp/errors.py +4 -4
- package/src/qingflow_mcp/export_store.py +14 -0
- package/src/qingflow_mcp/id_utils.py +49 -0
- package/src/qingflow_mcp/public_surface.py +16 -1
- package/src/qingflow_mcp/response_trim.py +394 -9
- package/src/qingflow_mcp/server.py +26 -0
- package/src/qingflow_mcp/server_app_builder.py +15 -1
- package/src/qingflow_mcp/server_app_user.py +113 -0
- package/src/qingflow_mcp/session_store.py +126 -21
- 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/ai_builder_tools.py +107 -34
- package/src/qingflow_mcp/tools/app_tools.py +1 -0
- package/src/qingflow_mcp/tools/auth_tools.py +243 -9
- package/src/qingflow_mcp/tools/base.py +6 -2
- package/src/qingflow_mcp/tools/code_block_tools.py +2 -2
- package/src/qingflow_mcp/tools/custom_button_tools.py +0 -2
- package/src/qingflow_mcp/tools/export_tools.py +1565 -0
- package/src/qingflow_mcp/tools/import_tools.py +78 -4
- package/src/qingflow_mcp/tools/record_tools.py +551 -165
- package/src/qingflow_mcp/tools/resource_read_tools.py +154 -33
- package/src/qingflow_mcp/tools/task_context_tools.py +917 -141
- package/src/qingflow_mcp/tools/workspace_tools.py +141 -0
|
@@ -13,6 +13,7 @@ from .tools.app_tools import AppTools
|
|
|
13
13
|
from .tools.auth_tools import AuthTools
|
|
14
14
|
from .tools.code_block_tools import CodeBlockTools
|
|
15
15
|
from .tools.directory_tools import DirectoryTools
|
|
16
|
+
from .tools.export_tools import ExportTools
|
|
16
17
|
from .tools.feedback_tools import FeedbackTools
|
|
17
18
|
from .tools.file_tools import FileTools
|
|
18
19
|
from .tools.import_tools import ImportTools
|
|
@@ -33,6 +34,7 @@ def build_user_server() -> FastMCP:
|
|
|
33
34
|
If `app_key` is unknown, use `app_list` or `app_search` first.
|
|
34
35
|
If the app is known but the data range is not, use `app_get` first and choose from `accessible_views`.
|
|
35
36
|
If an accessible view has `analysis_supported=false`, do not use it for `record_list` or `record_analyze`. `boardView` and `ganttView` are special UI views, not list/analyze targets.
|
|
37
|
+
`view_get(view_id=...)` also returns `export_capability`; it only means there is a supported export route, not that export permission has been verified.
|
|
36
38
|
|
|
37
39
|
## Shared Helper
|
|
38
40
|
|
|
@@ -142,10 +144,33 @@ Use `record_code_block_run` when the user wants to execute a form code-block fie
|
|
|
142
144
|
- Do not modify user-uploaded files unless the user explicitly authorizes repair.
|
|
143
145
|
- If repair is authorized, keep the original file and repair a copy, then run `record_import_verify` again before `record_import_start`.
|
|
144
146
|
|
|
147
|
+
## Export Path
|
|
148
|
+
|
|
149
|
+
`view_get -> record_export_start -> record_export_status_get -> record_export_get`
|
|
150
|
+
|
|
151
|
+
- `record_export_direct` is the one-shot path that starts export, waits, downloads locally, and still returns remote download links.
|
|
152
|
+
- Export v1 supports record views only and follows the same public `view_id` semantics as `record_list`.
|
|
153
|
+
- `record_export_start` / `record_export_direct` support frontend-like row selection:
|
|
154
|
+
- omit `record_ids` to export all rows in the selected view
|
|
155
|
+
- pass `record_ids` to export selected rows only
|
|
156
|
+
- `record_export_start` / `record_export_direct` also support internal query selection:
|
|
157
|
+
- pass `where` to resolve matching `record_id` values first
|
|
158
|
+
- pass `order_by` to keep the internal query and export row order aligned with `record_list`
|
|
159
|
+
- then run native export as selected rows
|
|
160
|
+
- `where/order_by` and `record_ids` are mutually exclusive
|
|
161
|
+
- `record_export_start` / `record_export_direct` also support frontend-like column selection:
|
|
162
|
+
- omit `columns` to export all current-view fields
|
|
163
|
+
- pass `columns` to export only selected fields, preserving the provided order
|
|
164
|
+
- `include_workflow_log=true` maps to the native workflow-log export switch.
|
|
165
|
+
|
|
145
166
|
## Task Workflow Path
|
|
146
167
|
|
|
147
168
|
`task_list -> task_get -> task_action_execute`
|
|
148
169
|
|
|
170
|
+
- `task_list` returns task-card summaries keyed by `task_id`.
|
|
171
|
+
- Prefer `task_get(task_id=...)` for detail reads; MCP resolves the current todo locator internally.
|
|
172
|
+
- `task_action_execute(task_id=..., action=...)` is also supported; MCP resolves the current todo locator internally before calling the real action route.
|
|
173
|
+
- `task_workflow_log_get(task_id=...)` and `task_associated_report_detail_get(task_id=...)` are also supported for the current todo context.
|
|
149
174
|
- Use `task_associated_report_detail_get` for associated view or report details.
|
|
150
175
|
- Use `task_workflow_log_get` for full workflow log history.
|
|
151
176
|
- Task actions operate on `app_key + record_id + workflow_node_id`, not `task_id`.
|
|
@@ -184,6 +209,7 @@ If the current MCP capability is unsupported, the workflow is awkward, or the us
|
|
|
184
209
|
workspace = wrap_trimmed_methods(WorkspaceTools(sessions, backend), USER_SERVER_METHOD_MAP)
|
|
185
210
|
file_tools = wrap_trimmed_methods(FileTools(sessions, backend), USER_SERVER_METHOD_MAP)
|
|
186
211
|
imports = wrap_trimmed_methods(ImportTools(sessions, backend), USER_SERVER_METHOD_MAP)
|
|
212
|
+
exports = wrap_trimmed_methods(ExportTools(sessions, backend), USER_SERVER_METHOD_MAP)
|
|
187
213
|
resources = wrap_trimmed_methods(ResourceReadTools(sessions, backend), USER_SERVER_METHOD_MAP)
|
|
188
214
|
feedback = FeedbackTools(backend, mcp_side="App User MCP")
|
|
189
215
|
code_block_tools = wrap_trimmed_methods(CodeBlockTools(sessions, backend), USER_SERVER_METHOD_MAP)
|
|
@@ -214,6 +240,73 @@ If the current MCP capability is unsupported, the workflow is awkward, or the us
|
|
|
214
240
|
def auth_logout(profile: str = DEFAULT_PROFILE, forget_persisted: bool = False) -> dict:
|
|
215
241
|
return auth.auth_logout(profile=profile, forget_persisted=forget_persisted)
|
|
216
242
|
|
|
243
|
+
@server.tool()
|
|
244
|
+
def record_export_start(
|
|
245
|
+
profile: str = DEFAULT_PROFILE,
|
|
246
|
+
app_key: str = "",
|
|
247
|
+
view_id: str = "system:all",
|
|
248
|
+
columns: list[dict | int] | None = None,
|
|
249
|
+
where: list[dict] | None = None,
|
|
250
|
+
order_by: list[dict] | None = None,
|
|
251
|
+
record_ids: list[str | int] | None = None,
|
|
252
|
+
include_workflow_log: bool = False,
|
|
253
|
+
) -> dict:
|
|
254
|
+
return exports.record_export_start(
|
|
255
|
+
profile=profile,
|
|
256
|
+
app_key=app_key,
|
|
257
|
+
view_id=view_id,
|
|
258
|
+
columns=columns or [],
|
|
259
|
+
where=where or [],
|
|
260
|
+
order_by=order_by or [],
|
|
261
|
+
record_ids=record_ids or [],
|
|
262
|
+
include_workflow_log=include_workflow_log,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
@server.tool()
|
|
266
|
+
def record_export_status_get(
|
|
267
|
+
profile: str = DEFAULT_PROFILE,
|
|
268
|
+
export_handle: str = "",
|
|
269
|
+
) -> dict:
|
|
270
|
+
return exports.record_export_status_get(profile=profile, export_handle=export_handle)
|
|
271
|
+
|
|
272
|
+
@server.tool()
|
|
273
|
+
def record_export_get(
|
|
274
|
+
profile: str = DEFAULT_PROFILE,
|
|
275
|
+
export_handle: str = "",
|
|
276
|
+
download_to_path: str | None = None,
|
|
277
|
+
) -> dict:
|
|
278
|
+
return exports.record_export_get(
|
|
279
|
+
profile=profile,
|
|
280
|
+
export_handle=export_handle,
|
|
281
|
+
download_to_path=download_to_path,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
@server.tool()
|
|
285
|
+
def record_export_direct(
|
|
286
|
+
profile: str = DEFAULT_PROFILE,
|
|
287
|
+
app_key: str = "",
|
|
288
|
+
view_id: str = "system:all",
|
|
289
|
+
columns: list[dict | int] | None = None,
|
|
290
|
+
where: list[dict] | None = None,
|
|
291
|
+
order_by: list[dict] | None = None,
|
|
292
|
+
record_ids: list[str | int] | None = None,
|
|
293
|
+
include_workflow_log: bool = False,
|
|
294
|
+
download_to_path: str | None = None,
|
|
295
|
+
wait_timeout_seconds: float | None = None,
|
|
296
|
+
) -> dict:
|
|
297
|
+
return exports.record_export_direct(
|
|
298
|
+
profile=profile,
|
|
299
|
+
app_key=app_key,
|
|
300
|
+
view_id=view_id,
|
|
301
|
+
columns=columns or [],
|
|
302
|
+
where=where or [],
|
|
303
|
+
order_by=order_by or [],
|
|
304
|
+
record_ids=record_ids or [],
|
|
305
|
+
include_workflow_log=include_workflow_log,
|
|
306
|
+
download_to_path=download_to_path,
|
|
307
|
+
wait_timeout_seconds=wait_timeout_seconds,
|
|
308
|
+
)
|
|
309
|
+
|
|
217
310
|
@server.tool()
|
|
218
311
|
def workspace_list(
|
|
219
312
|
profile: str = DEFAULT_PROFILE,
|
|
@@ -228,6 +321,26 @@ If the current MCP capability is unsupported, the workflow is awkward, or the us
|
|
|
228
321
|
include_external=include_external,
|
|
229
322
|
)
|
|
230
323
|
|
|
324
|
+
@server.tool()
|
|
325
|
+
def workspace_get(
|
|
326
|
+
profile: str = DEFAULT_PROFILE,
|
|
327
|
+
ws_id: int | None = None,
|
|
328
|
+
) -> dict:
|
|
329
|
+
return workspace.workspace_get(
|
|
330
|
+
profile=profile,
|
|
331
|
+
ws_id=ws_id,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
@server.tool()
|
|
335
|
+
def workspace_select(
|
|
336
|
+
profile: str = DEFAULT_PROFILE,
|
|
337
|
+
ws_id: int = 0,
|
|
338
|
+
) -> dict:
|
|
339
|
+
return workspace.workspace_select(
|
|
340
|
+
profile=profile,
|
|
341
|
+
ws_id=ws_id,
|
|
342
|
+
)
|
|
343
|
+
|
|
231
344
|
@server.tool()
|
|
232
345
|
def app_list(profile: str = DEFAULT_PROFILE) -> dict:
|
|
233
346
|
return apps.app_list(profile=profile)
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import os
|
|
5
|
+
from collections.abc import Callable
|
|
4
6
|
from dataclasses import asdict, dataclass
|
|
5
|
-
from datetime import datetime, timezone
|
|
7
|
+
from datetime import datetime, timedelta, timezone
|
|
6
8
|
from pathlib import Path
|
|
7
9
|
|
|
8
10
|
try:
|
|
@@ -77,9 +79,15 @@ class SessionStore:
|
|
|
77
79
|
profiles_path = get_profiles_path() if base_dir is None else Path(base_dir) / "profiles.json"
|
|
78
80
|
self._profiles_path = profiles_path
|
|
79
81
|
self._profiles_path.parent.mkdir(parents=True, exist_ok=True)
|
|
82
|
+
self._secrets_path = self._profiles_path.parent / "secrets.json"
|
|
80
83
|
self._keyring = keyring_backend if keyring_backend is not None else keyring
|
|
81
84
|
self._memory_sessions: dict[str, BackendSession] = {}
|
|
82
85
|
self._logged_out_profiles: set[str] = set()
|
|
86
|
+
self._profile_refresher: Callable[[str, SessionProfile | None], bool] | None = None
|
|
87
|
+
self._refreshing_profiles: set[str] = set()
|
|
88
|
+
|
|
89
|
+
def set_profile_refresher(self, refresher: Callable[[str, SessionProfile | None], bool] | None) -> None:
|
|
90
|
+
self._profile_refresher = refresher
|
|
83
91
|
|
|
84
92
|
def save_session(
|
|
85
93
|
self,
|
|
@@ -146,9 +154,14 @@ class SessionStore:
|
|
|
146
154
|
def get_profile(self, profile: str) -> SessionProfile | None:
|
|
147
155
|
payload = self._load_profiles()
|
|
148
156
|
raw_profile = payload.get("profiles", {}).get(profile)
|
|
149
|
-
if
|
|
150
|
-
|
|
151
|
-
|
|
157
|
+
session_profile = SessionProfile.from_dict(raw_profile) if isinstance(raw_profile, dict) else None
|
|
158
|
+
if profile in self._refreshing_profiles:
|
|
159
|
+
return session_profile
|
|
160
|
+
if self._should_refresh_profile(payload, session_profile) and self._refresh_profile(profile, session_profile):
|
|
161
|
+
payload = self._load_profiles()
|
|
162
|
+
raw_profile = payload.get("profiles", {}).get(profile)
|
|
163
|
+
session_profile = SessionProfile.from_dict(raw_profile) if isinstance(raw_profile, dict) else None
|
|
164
|
+
return session_profile
|
|
152
165
|
|
|
153
166
|
def get_backend_session(self, profile: str) -> BackendSession | None:
|
|
154
167
|
if profile in self._logged_out_profiles:
|
|
@@ -281,8 +294,48 @@ class SessionStore:
|
|
|
281
294
|
def _load_profiles(self) -> JSONObject:
|
|
282
295
|
if not self._profiles_path.exists():
|
|
283
296
|
return {"profiles": {}}
|
|
284
|
-
|
|
285
|
-
|
|
297
|
+
try:
|
|
298
|
+
with self._profiles_path.open("r", encoding="utf-8") as handle:
|
|
299
|
+
payload = json.load(handle)
|
|
300
|
+
except (OSError, json.JSONDecodeError):
|
|
301
|
+
return {"profiles": {}}
|
|
302
|
+
if not isinstance(payload, dict):
|
|
303
|
+
return {"profiles": {}}
|
|
304
|
+
profiles = payload.get("profiles")
|
|
305
|
+
if not isinstance(profiles, dict):
|
|
306
|
+
payload["profiles"] = {}
|
|
307
|
+
return payload
|
|
308
|
+
|
|
309
|
+
def _should_refresh_profile(self, payload: JSONObject, session_profile: SessionProfile | None) -> bool:
|
|
310
|
+
if self._profile_refresher is None:
|
|
311
|
+
return False
|
|
312
|
+
profiles = payload.get("profiles")
|
|
313
|
+
if not isinstance(profiles, dict) or not profiles:
|
|
314
|
+
return True
|
|
315
|
+
if session_profile is None:
|
|
316
|
+
return True
|
|
317
|
+
return self._is_profile_stale(session_profile)
|
|
318
|
+
|
|
319
|
+
def _refresh_profile(self, profile: str, session_profile: SessionProfile | None) -> bool:
|
|
320
|
+
if self._profile_refresher is None:
|
|
321
|
+
return False
|
|
322
|
+
self._refreshing_profiles.add(profile)
|
|
323
|
+
try:
|
|
324
|
+
return bool(self._profile_refresher(profile, session_profile))
|
|
325
|
+
except Exception:
|
|
326
|
+
return False
|
|
327
|
+
finally:
|
|
328
|
+
self._refreshing_profiles.discard(profile)
|
|
329
|
+
|
|
330
|
+
def _is_profile_stale(self, session_profile: SessionProfile) -> bool:
|
|
331
|
+
timestamp_text = session_profile.updated_at or session_profile.created_at
|
|
332
|
+
try:
|
|
333
|
+
timestamp = datetime.fromisoformat(timestamp_text)
|
|
334
|
+
except (TypeError, ValueError):
|
|
335
|
+
return True
|
|
336
|
+
if timestamp.tzinfo is None:
|
|
337
|
+
timestamp = timestamp.replace(tzinfo=timezone.utc)
|
|
338
|
+
return datetime.now(timezone.utc) - timestamp > timedelta(days=7)
|
|
286
339
|
|
|
287
340
|
def _save_profiles(self, payload: JSONObject) -> None:
|
|
288
341
|
self._profiles_path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -290,26 +343,78 @@ class SessionStore:
|
|
|
290
343
|
json.dump(payload, handle, ensure_ascii=False, indent=2)
|
|
291
344
|
|
|
292
345
|
def _set_secret(self, key: str, value: str) -> bool:
|
|
293
|
-
if self._keyring is None:
|
|
294
|
-
|
|
346
|
+
if self._keyring is not None:
|
|
347
|
+
try:
|
|
348
|
+
self._keyring.set_password(KEYRING_SERVICE_NAME, key, value)
|
|
349
|
+
self._delete_file_secret(key)
|
|
350
|
+
return True
|
|
351
|
+
except Exception:
|
|
352
|
+
pass
|
|
353
|
+
return self._set_file_secret(key, value)
|
|
354
|
+
|
|
355
|
+
def _get_secret(self, key: str) -> str | None:
|
|
356
|
+
if self._keyring is not None:
|
|
357
|
+
try:
|
|
358
|
+
value = self._keyring.get_password(KEYRING_SERVICE_NAME, key)
|
|
359
|
+
except Exception:
|
|
360
|
+
value = None
|
|
361
|
+
if value:
|
|
362
|
+
return value
|
|
363
|
+
return self._get_file_secret(key)
|
|
364
|
+
|
|
365
|
+
def _delete_secret(self, key: str) -> None:
|
|
366
|
+
if self._keyring is not None:
|
|
367
|
+
try:
|
|
368
|
+
self._keyring.delete_password(KEYRING_SERVICE_NAME, key)
|
|
369
|
+
except Exception:
|
|
370
|
+
pass
|
|
371
|
+
self._delete_file_secret(key)
|
|
372
|
+
|
|
373
|
+
def _load_file_secrets(self) -> dict[str, str]:
|
|
374
|
+
if not self._secrets_path.exists():
|
|
375
|
+
return {}
|
|
376
|
+
try:
|
|
377
|
+
with self._secrets_path.open("r", encoding="utf-8") as handle:
|
|
378
|
+
payload = json.load(handle)
|
|
379
|
+
except (OSError, json.JSONDecodeError):
|
|
380
|
+
return {}
|
|
381
|
+
if not isinstance(payload, dict):
|
|
382
|
+
return {}
|
|
383
|
+
return {str(key): str(value) for key, value in payload.items() if isinstance(value, str)}
|
|
384
|
+
|
|
385
|
+
def _save_file_secrets(self, payload: dict[str, str]) -> bool:
|
|
386
|
+
self._secrets_path.parent.mkdir(parents=True, exist_ok=True)
|
|
295
387
|
try:
|
|
296
|
-
self.
|
|
388
|
+
fd = os.open(self._secrets_path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
|
389
|
+
with os.fdopen(fd, "w", encoding="utf-8") as handle:
|
|
390
|
+
json.dump(payload, handle, ensure_ascii=False, indent=2)
|
|
391
|
+
try:
|
|
392
|
+
os.chmod(self._secrets_path, 0o600)
|
|
393
|
+
except OSError:
|
|
394
|
+
pass
|
|
297
395
|
return True
|
|
298
|
-
except
|
|
396
|
+
except OSError:
|
|
299
397
|
return False
|
|
300
398
|
|
|
301
|
-
def
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
return self._keyring.get_password(KEYRING_SERVICE_NAME, key)
|
|
306
|
-
except Exception:
|
|
307
|
-
return None
|
|
399
|
+
def _set_file_secret(self, key: str, value: str) -> bool:
|
|
400
|
+
payload = self._load_file_secrets()
|
|
401
|
+
payload[key] = value
|
|
402
|
+
return self._save_file_secrets(payload)
|
|
308
403
|
|
|
309
|
-
def
|
|
310
|
-
|
|
404
|
+
def _get_file_secret(self, key: str) -> str | None:
|
|
405
|
+
return self._load_file_secrets().get(key)
|
|
406
|
+
|
|
407
|
+
def _delete_file_secret(self, key: str) -> None:
|
|
408
|
+
payload = self._load_file_secrets()
|
|
409
|
+
if key not in payload:
|
|
410
|
+
return
|
|
411
|
+
payload.pop(key, None)
|
|
412
|
+
if payload:
|
|
413
|
+
self._save_file_secrets(payload)
|
|
311
414
|
return
|
|
312
415
|
try:
|
|
313
|
-
self.
|
|
314
|
-
except
|
|
416
|
+
self._secrets_path.unlink()
|
|
417
|
+
except FileNotFoundError:
|
|
418
|
+
return
|
|
419
|
+
except OSError:
|
|
315
420
|
return
|
|
@@ -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 = []
|