@josephyan/qingflow-cli 1.1.4 → 1.1.6
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 +7 -3
- package/docs/local-agent-install.md +57 -6
- package/entry_point.py +1 -1
- package/npm/bin/qingflow-skills.mjs +5 -0
- package/npm/bin/qingflow.mjs +1 -34
- package/npm/lib/runtime.mjs +21 -101
- package/npm/scripts/postinstall.mjs +1 -10
- package/package.json +3 -2
- package/pyproject.toml +1 -1
- package/skills/qingflow-cli/SKILL.md +58 -44
- package/skills/qingflow-cli/manifest.yaml +1 -1
- package/skills/qingflow-cli/reference/00-INDEX.md +35 -0
- package/skills/qingflow-cli/reference/builder/10-build-single-app.md +38 -0
- package/skills/qingflow-cli/reference/builder/20-build-complete-system.md +39 -0
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_SCHEMA_APPLY_FIELD_TYPES_AND_SCENARIOS.md → builder/30-schema-fields.md} +52 -10
- package/skills/qingflow-cli/reference/builder/40-layout.md +52 -0
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_VIEWS_WORKFLOW.md → builder/50-views.md} +39 -15
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_CHARTS_WORKFLOW.md → builder/60-charts.md} +36 -13
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_PORTAL_WORKFLOW.md → builder/70-portal.md} +36 -13
- package/skills/qingflow-cli/reference/builder/80-buttons-associated-resources.md +41 -0
- package/skills/qingflow-cli/reference/builder/90-workflow.md +34 -0
- package/skills/qingflow-cli/reference/builder/99-publish-verify.md +46 -0
- package/skills/qingflow-cli/reference/builder/README.md +41 -0
- package/skills/qingflow-cli/reference/builder/code-integrations/README.md +130 -0
- package/skills/qingflow-cli/reference/builder/code-integrations/code-block.md +66 -0
- package/skills/qingflow-cli/reference/builder/code-integrations/q-linker.md +77 -0
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_APP_DELIVERY_WORKFLOW.md → builder/reference/app-delivery-sop.md} +26 -16
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/README.md +293 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/build-complete-system.md +809 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/build-single-app.md +830 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/complete-system-development-guide.md +123 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/create-app.md +182 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/environments.md +63 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/flow-actors-and-permissions.md +142 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/gotchas.md +108 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/match-rules.md +114 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/public-surface-sync.md +75 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/single-app-development-guide.md +58 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/solution-playbooks.md +52 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/tool-selection.md +107 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-flow.md +7 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-layout.md +7 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-schema.md +7 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-views.md +7 -0
- package/skills/qingflow-cli/reference/builder/workflow/01-overview.md +45 -0
- package/skills/qingflow-cli/reference/builder/workflow/02-update-mode.md +53 -0
- package/skills/qingflow-cli/reference/builder/workflow/03-flow-patterns.md +57 -0
- package/skills/qingflow-cli/reference/builder/workflow/04-stage1-business-modeling.md +131 -0
- package/skills/qingflow-cli/reference/builder/workflow/05-stage2-members-roles.md +29 -0
- package/skills/qingflow-cli/reference/builder/workflow/06-stage3-build-spec.md +165 -0
- package/skills/qingflow-cli/reference/builder/workflow/07-stage4-validate-spec.md +33 -0
- package/skills/qingflow-cli/reference/builder/workflow/08-stage5-apply-verify.md +51 -0
- package/skills/qingflow-cli/reference/builder/workflow/09-stage6-summary.md +88 -0
- package/skills/qingflow-cli/reference/builder/workflow/10-node-config-reference.md +93 -0
- package/skills/qingflow-cli/reference/builder/workflow/11-troubleshooting.md +15 -0
- package/skills/qingflow-cli/reference/builder/workflow/README.md +88 -0
- package/skills/qingflow-cli/reference/builder/workflow/workflow-schema.json +1754 -0
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_ADMIN_CHEATSHEET.md → core/QINGFLOW_CLI_ADMIN_CHEATSHEET.md} +3 -3
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md → core/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md} +6 -6
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_EXPLORATION_REPORT.md → core/QINGFLOW_CLI_EXPLORATION_REPORT.md} +2 -2
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_FIELD_DATA_TYPES.md → core/QINGFLOW_CLI_FIELD_DATA_TYPES.md} +11 -11
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_MEMBER_CHEATSHEET.md → core/QINGFLOW_CLI_MEMBER_CHEATSHEET.md} +4 -4
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md → core/QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md} +4 -4
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md → record/QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md} +3 -3
- package/skills/qingflow-cli/reference/record/QINGFLOW_CLI_RECORD_DELETE_WORKFLOW.md +31 -0
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md → record/QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md} +4 -4
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md → record/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md} +7 -7
- package/skills/qingflow-cli/reference/record/analysis/README.md +130 -0
- package/skills/qingflow-cli/reference/record/analysis/analysis-gotchas.md +91 -0
- package/skills/qingflow-cli/reference/record/analysis/analysis-patterns.md +112 -0
- package/skills/qingflow-cli/reference/record/analysis/business-context.md +74 -0
- package/skills/qingflow-cli/reference/record/analysis/confidence-reporting.md +69 -0
- package/skills/qingflow-cli/reference/record/analysis/data-access-playbook.md +106 -0
- package/skills/qingflow-cli/reference/record/analysis/pandas-recipes.md +172 -0
- package/skills/qingflow-cli/reference/record/analysis/report-format.md +76 -0
- package/skills/qingflow-cli/reference/record/insert/README.md +75 -0
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md → task/QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md} +5 -5
- package/skills/qingflow-cli/reference/task/ops/README.md +131 -0
- package/skills/qingflow-cli/reference/task/ops/environments.md +43 -0
- package/skills/qingflow-cli/reference/task/ops/workflow-usage.md +26 -0
- package/skills/qingflow-cli/scripts/validate_system_build_summary.py +124 -0
- package/skills/qingflow-cli/scripts/workflow/diff_flow_spec.py +275 -0
- package/skills/qingflow-cli/scripts/workflow/validate_flow_spec.py +605 -0
- package/skills/qingflow-mcp-setup/SKILL.md +115 -0
- package/skills/qingflow-mcp-setup/agents/openai.yaml +4 -0
- package/skills/qingflow-mcp-setup/references/claude-desktop.md +34 -0
- package/skills/qingflow-mcp-setup/references/environments.md +62 -0
- package/skills/qingflow-mcp-setup/references/generic-stdio.md +32 -0
- package/skills/qingflow-mcp-setup/scripts/check_local_server.sh +38 -0
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/__main__.py +6 -2
- package/src/qingflow_mcp/builder_facade/models.py +282 -102
- package/src/qingflow_mcp/builder_facade/service.py +4192 -935
- package/src/qingflow_mcp/cli/commands/builder.py +316 -298
- package/src/qingflow_mcp/cli/commands/chart.py +1 -1
- package/src/qingflow_mcp/cli/commands/common.py +12 -3
- package/src/qingflow_mcp/cli/commands/exports.py +2 -2
- package/src/qingflow_mcp/cli/commands/imports.py +3 -3
- package/src/qingflow_mcp/cli/commands/portal.py +2 -2
- package/src/qingflow_mcp/cli/commands/record.py +101 -27
- package/src/qingflow_mcp/cli/commands/task.py +28 -47
- package/src/qingflow_mcp/cli/commands/view.py +1 -1
- package/src/qingflow_mcp/cli/context.py +0 -3
- package/src/qingflow_mcp/cli/formatters.py +784 -16
- package/src/qingflow_mcp/cli/main.py +117 -33
- package/src/qingflow_mcp/errors.py +43 -2
- package/src/qingflow_mcp/public_surface.py +26 -17
- package/src/qingflow_mcp/response_trim.py +81 -17
- package/src/qingflow_mcp/server.py +14 -12
- package/src/qingflow_mcp/server_app_builder.py +65 -21
- package/src/qingflow_mcp/server_app_user.py +22 -16
- package/src/qingflow_mcp/session_store.py +11 -7
- package/src/qingflow_mcp/solution/compiler/__init__.py +3 -1
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +173 -0
- package/src/qingflow_mcp/solution/executor.py +245 -18
- package/src/qingflow_mcp/tools/ai_builder_tools.py +1780 -406
- package/src/qingflow_mcp/tools/app_tools.py +184 -43
- package/src/qingflow_mcp/tools/approval_tools.py +197 -35
- package/src/qingflow_mcp/tools/auth_tools.py +92 -16
- package/src/qingflow_mcp/tools/code_block_tools.py +298 -40
- package/src/qingflow_mcp/tools/custom_button_tools.py +64 -10
- package/src/qingflow_mcp/tools/directory_tools.py +236 -72
- package/src/qingflow_mcp/tools/export_tools.py +244 -34
- package/src/qingflow_mcp/tools/feedback_tools.py +9 -0
- package/src/qingflow_mcp/tools/file_tools.py +9 -3
- package/src/qingflow_mcp/tools/import_tools.py +336 -49
- package/src/qingflow_mcp/tools/navigation_tools.py +91 -12
- package/src/qingflow_mcp/tools/package_tools.py +118 -6
- package/src/qingflow_mcp/tools/portal_tools.py +39 -3
- package/src/qingflow_mcp/tools/qingbi_report_tools.py +116 -7
- package/src/qingflow_mcp/tools/record_tools.py +1141 -356
- package/src/qingflow_mcp/tools/resource_read_tools.py +188 -39
- package/src/qingflow_mcp/tools/role_tools.py +80 -9
- package/src/qingflow_mcp/tools/solution_tools.py +59 -45
- package/src/qingflow_mcp/tools/task_context_tools.py +662 -158
- package/src/qingflow_mcp/tools/task_tools.py +113 -29
- package/src/qingflow_mcp/tools/view_tools.py +106 -3
- package/src/qingflow_mcp/tools/workflow_tools.py +48 -4
- package/src/qingflow_mcp/tools/workspace_tools.py +71 -3
- /package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_MATCH_RULES.md → builder/reference/match-rules.md} +0 -0
- /package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_WORKSPACE_ICONS.md → builder/reference/workspace-icons.md} +0 -0
- /package/skills/qingflow-cli/reference/{charts_remove.example.json → examples/charts/charts_remove.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{charts_reorder.example.json → examples/charts/charts_reorder.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{charts_upsert_bar.example.json → examples/charts/charts_upsert_bar.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{charts_upsert_dashboard_starter.example.json → examples/charts/charts_upsert_dashboard_starter.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{charts_upsert_minimal.example.json → examples/charts/charts_upsert_minimal.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{portal_sections_all_types.example.json → examples/portal/portal_sections_all_types.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{portal_sections_five_types.example.json → examples/portal/portal_sections_five_types.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{portal_sections_standard_workbench.example.json → examples/portal/portal_sections_standard_workbench.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{_batch_schema_complex.json → examples/schema/_batch_schema_complex.json} +0 -0
- /package/skills/qingflow-cli/reference/{_batch_schema_scalar.json → examples/schema/_batch_schema_scalar.json} +0 -0
- /package/skills/qingflow-cli/reference/{schema_add_fields_minimal.example.json → examples/schema/schema_add_fields_minimal.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{schema_apply_add_fields_all_types.json → examples/schema/schema_apply_add_fields_all_types.json} +0 -0
- /package/skills/qingflow-cli/reference/{views_upsert_table_minimal.example.json → examples/views/views_upsert_table_minimal.example.json} +0 -0
|
@@ -7,7 +7,7 @@ from mcp.server.fastmcp import FastMCP
|
|
|
7
7
|
|
|
8
8
|
from ..backend_client import BackendRequestContext
|
|
9
9
|
from ..config import DEFAULT_PROFILE
|
|
10
|
-
from ..errors import QingflowApiError, raise_tool_error
|
|
10
|
+
from ..errors import QingflowApiError, backend_code_int, is_auth_like_error, raise_tool_error
|
|
11
11
|
from ..json_types import JSONObject
|
|
12
12
|
from ..list_type_labels import SYSTEM_VIEW_DEFINITIONS, get_app_publish_status_label
|
|
13
13
|
from ..solution.compiler.icon_utils import workspace_icon_config
|
|
@@ -132,6 +132,20 @@ class AppTools(ToolBase):
|
|
|
132
132
|
matched.append(item)
|
|
133
133
|
return matched
|
|
134
134
|
|
|
135
|
+
def _resolve_visible_app(self, context: BackendRequestContext, app_key: str) -> JSONObject | None:
|
|
136
|
+
"""Resolve app display metadata from the current user's visible app tree."""
|
|
137
|
+
try:
|
|
138
|
+
result = self.backend.request("GET", context, "/tag/apps")
|
|
139
|
+
except QingflowApiError as exc:
|
|
140
|
+
if not _is_optional_app_metadata_error(exc):
|
|
141
|
+
raise
|
|
142
|
+
return None
|
|
143
|
+
items, _source_shape = self._extract_visible_apps(result)
|
|
144
|
+
for item in items:
|
|
145
|
+
if str(item.get("app_key") or "").strip() == app_key:
|
|
146
|
+
return item
|
|
147
|
+
return None
|
|
148
|
+
|
|
135
149
|
@tool_cn_name("应用搜索")
|
|
136
150
|
def app_search(self, *, profile: str, keyword: str = "", page_num: int = 1, page_size: int = 50) -> JSONObject:
|
|
137
151
|
"""Search apps by keyword in name/title using backend search API.
|
|
@@ -141,9 +155,50 @@ class AppTools(ToolBase):
|
|
|
141
155
|
params: JSONObject = {"pageNum": page_num, "pageSize": page_size}
|
|
142
156
|
if keyword:
|
|
143
157
|
params["queryKey"] = keyword
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
158
|
+
|
|
159
|
+
warnings: list[JSONObject] = []
|
|
160
|
+
source = "app_item_search"
|
|
161
|
+
try:
|
|
162
|
+
result = self.backend.request("GET", context, "/app/item", params=params)
|
|
163
|
+
except QingflowApiError as exc:
|
|
164
|
+
if is_auth_like_error(exc):
|
|
165
|
+
raise
|
|
166
|
+
if not _is_optional_app_metadata_error(exc):
|
|
167
|
+
raise
|
|
168
|
+
visible_payload = self.backend.request("GET", context, "/tag/apps")
|
|
169
|
+
all_apps, source_shape = self._extract_visible_apps(visible_payload)
|
|
170
|
+
matched_apps = self._filter_visible_apps(all_apps, keyword) if keyword else all_apps
|
|
171
|
+
page_start = max(page_num - 1, 0) * page_size
|
|
172
|
+
page_end = page_start + page_size
|
|
173
|
+
apps = matched_apps[page_start:page_end]
|
|
174
|
+
warnings.append(
|
|
175
|
+
{
|
|
176
|
+
"code": "APP_SEARCH_FALLBACK_VISIBLE_APPS",
|
|
177
|
+
"message": (
|
|
178
|
+
"app_search used the current user's visible app tree because the global app search "
|
|
179
|
+
"endpoint is unavailable in this permission context."
|
|
180
|
+
),
|
|
181
|
+
"backend_code": exc.backend_code,
|
|
182
|
+
"http_status": exc.http_status,
|
|
183
|
+
"request_id": exc.request_id,
|
|
184
|
+
}
|
|
185
|
+
)
|
|
186
|
+
return {
|
|
187
|
+
"profile": profile,
|
|
188
|
+
"ws_id": session_profile.selected_ws_id,
|
|
189
|
+
"keyword": keyword,
|
|
190
|
+
"page_num": page_num,
|
|
191
|
+
"page_size": page_size,
|
|
192
|
+
"total": len(matched_apps),
|
|
193
|
+
"items": apps,
|
|
194
|
+
"apps": apps,
|
|
195
|
+
"source": "visible_apps_fallback",
|
|
196
|
+
"source_shape": source_shape,
|
|
197
|
+
"unfiltered_count": len(all_apps),
|
|
198
|
+
"matched_count": len(matched_apps),
|
|
199
|
+
"warnings": warnings,
|
|
200
|
+
}
|
|
201
|
+
|
|
147
202
|
apps = []
|
|
148
203
|
if isinstance(result, dict):
|
|
149
204
|
items = result.get("list", [])
|
|
@@ -168,6 +223,7 @@ class AppTools(ToolBase):
|
|
|
168
223
|
"total": result.get("total") if isinstance(result, dict) else len(apps),
|
|
169
224
|
"items": apps,
|
|
170
225
|
"apps": apps,
|
|
226
|
+
"source": source,
|
|
171
227
|
}
|
|
172
228
|
|
|
173
229
|
return self._run(profile, runner)
|
|
@@ -182,6 +238,7 @@ class AppTools(ToolBase):
|
|
|
182
238
|
app_name = app_key
|
|
183
239
|
base_info: JSONObject | None = None
|
|
184
240
|
app_icon = None
|
|
241
|
+
visible_app: JSONObject | None = None
|
|
185
242
|
|
|
186
243
|
try:
|
|
187
244
|
base_info = self.backend.request("GET", context, f"/app/{app_key}/baseInfo")
|
|
@@ -192,31 +249,31 @@ class AppTools(ToolBase):
|
|
|
192
249
|
)
|
|
193
250
|
app_icon = str(base_info.get("appIcon") or "").strip() or None
|
|
194
251
|
except QingflowApiError as exc:
|
|
195
|
-
if
|
|
252
|
+
if not _is_optional_app_metadata_error(exc):
|
|
196
253
|
raise
|
|
254
|
+
visible_app = self._resolve_visible_app(context, app_key)
|
|
255
|
+
if visible_app is not None:
|
|
256
|
+
app_name = str(visible_app.get("app_name") or visible_app.get("title") or app_key).strip() or app_key
|
|
257
|
+
app_icon = str(visible_app.get("app_icon") or "").strip() or None
|
|
197
258
|
warnings.append(
|
|
198
|
-
|
|
259
|
+
_with_api_error_fields(
|
|
260
|
+
{
|
|
199
261
|
"code": "APP_BASE_INFO_UNAVAILABLE",
|
|
200
|
-
"message": f"app_get could not load base info for {app_key}; using app_key as app_name.",
|
|
201
|
-
}
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
try:
|
|
205
|
-
can_create = self._probe_create_access(context, app_key)
|
|
206
|
-
except QingflowApiError as exc:
|
|
207
|
-
can_create = False
|
|
208
|
-
warnings.append(
|
|
209
|
-
{
|
|
210
|
-
"code": "APP_CREATE_PROBE_UNVERIFIED",
|
|
211
262
|
"message": (
|
|
212
|
-
f"app_get could not
|
|
213
|
-
|
|
263
|
+
f"app_get could not load base info for {app_key}; "
|
|
264
|
+
"using the current user's visible app tree when available."
|
|
214
265
|
),
|
|
215
|
-
|
|
266
|
+
},
|
|
267
|
+
exc,
|
|
268
|
+
)
|
|
216
269
|
)
|
|
270
|
+
|
|
271
|
+
can_create = self._probe_create_access(context, app_key)
|
|
217
272
|
accessible_views, system_view_warnings = self._resolve_accessible_system_views(context, app_key)
|
|
218
273
|
warnings.extend(system_view_warnings)
|
|
219
|
-
|
|
274
|
+
custom_views, custom_view_warnings = self._resolve_accessible_custom_views(context, app_key)
|
|
275
|
+
warnings.extend(custom_view_warnings)
|
|
276
|
+
accessible_views.extend(custom_views)
|
|
220
277
|
import_capability, import_warnings = _derive_import_capability(base_info)
|
|
221
278
|
warnings.extend(import_warnings)
|
|
222
279
|
return {
|
|
@@ -230,6 +287,7 @@ class AppTools(ToolBase):
|
|
|
230
287
|
"app_name": app_name,
|
|
231
288
|
"app_icon": app_icon,
|
|
232
289
|
"icon_config": workspace_icon_config(app_icon),
|
|
290
|
+
**({"visible_app": visible_app} if base_info is None and visible_app is not None else {}),
|
|
233
291
|
"can_create": can_create,
|
|
234
292
|
"import_capability": import_capability,
|
|
235
293
|
"accessible_views": accessible_views,
|
|
@@ -244,7 +302,30 @@ class AppTools(ToolBase):
|
|
|
244
302
|
self._require_app_key(app_key)
|
|
245
303
|
|
|
246
304
|
def runner(session_profile, context):
|
|
247
|
-
|
|
305
|
+
warnings: list[JSONObject] = []
|
|
306
|
+
verification: JSONObject = {"base_info_verified": True, "fallback_visible_app_used": False}
|
|
307
|
+
try:
|
|
308
|
+
result = self.backend.request("GET", context, f"/app/{app_key}/baseInfo")
|
|
309
|
+
except QingflowApiError as exc:
|
|
310
|
+
if not _is_optional_app_metadata_error(exc):
|
|
311
|
+
raise
|
|
312
|
+
visible_app = self._resolve_visible_app(context, app_key)
|
|
313
|
+
if visible_app is None:
|
|
314
|
+
raise
|
|
315
|
+
result = self._base_info_from_visible_app(app_key, visible_app)
|
|
316
|
+
warnings.append(
|
|
317
|
+
_with_api_error_fields(
|
|
318
|
+
{
|
|
319
|
+
"code": "APP_BASE_INFO_UNAVAILABLE",
|
|
320
|
+
"message": (
|
|
321
|
+
f"app_get_base could not load base info for {app_key}; "
|
|
322
|
+
"using the current user's visible app tree."
|
|
323
|
+
),
|
|
324
|
+
},
|
|
325
|
+
exc,
|
|
326
|
+
)
|
|
327
|
+
)
|
|
328
|
+
verification = {"base_info_verified": False, "fallback_visible_app_used": True}
|
|
248
329
|
publish_status = result.get("appPublishStatus") if isinstance(result, dict) else None
|
|
249
330
|
compact = self._compact_base_info(result if isinstance(result, dict) else {})
|
|
250
331
|
response = {
|
|
@@ -255,7 +336,10 @@ class AppTools(ToolBase):
|
|
|
255
336
|
"app_publish_status": publish_status,
|
|
256
337
|
"app_publish_status_label": get_app_publish_status_label(publish_status if isinstance(publish_status, int) else None),
|
|
257
338
|
"compact": not include_raw,
|
|
339
|
+
"verification": verification,
|
|
258
340
|
}
|
|
341
|
+
if warnings:
|
|
342
|
+
response["warnings"] = warnings
|
|
259
343
|
if include_raw:
|
|
260
344
|
response["summary"] = compact
|
|
261
345
|
return response
|
|
@@ -489,7 +573,9 @@ class AppTools(ToolBase):
|
|
|
489
573
|
)
|
|
490
574
|
return True
|
|
491
575
|
except QingflowApiError as exc:
|
|
492
|
-
if exc
|
|
576
|
+
if is_auth_like_error(exc):
|
|
577
|
+
raise
|
|
578
|
+
if backend_code_int(exc) in {40002, 40027, 404} or exc.http_status == 404:
|
|
493
579
|
return False
|
|
494
580
|
raise
|
|
495
581
|
|
|
@@ -504,7 +590,9 @@ class AppTools(ToolBase):
|
|
|
504
590
|
)
|
|
505
591
|
return True
|
|
506
592
|
except QingflowApiError as exc:
|
|
507
|
-
if exc
|
|
593
|
+
if is_auth_like_error(exc):
|
|
594
|
+
raise
|
|
595
|
+
if backend_code_int(exc) in {40002, 40027, 404} or exc.http_status == 404:
|
|
508
596
|
return False
|
|
509
597
|
raise
|
|
510
598
|
|
|
@@ -516,40 +604,42 @@ class AppTools(ToolBase):
|
|
|
516
604
|
try:
|
|
517
605
|
can_access = self._probe_list_type_access(context, app_key, list_type)
|
|
518
606
|
except QingflowApiError as exc:
|
|
519
|
-
|
|
520
|
-
{
|
|
521
|
-
"code": "SYSTEM_VIEW_PROBE_UNVERIFIED",
|
|
522
|
-
"message": (
|
|
523
|
-
f"app_get skipped system view '{name}' because access probing failed: "
|
|
524
|
-
f"{exc.message or 'probe failed'}"
|
|
525
|
-
),
|
|
526
|
-
"view_id": view_id,
|
|
527
|
-
"list_type": list_type,
|
|
528
|
-
}
|
|
529
|
-
)
|
|
530
|
-
continue
|
|
607
|
+
raise
|
|
531
608
|
if not can_access:
|
|
532
609
|
continue
|
|
533
610
|
items.append({"view_id": view_id, "name": name, "kind": "system", "analysis_supported": True})
|
|
534
611
|
return items, warnings
|
|
535
612
|
|
|
536
|
-
def _resolve_accessible_custom_views(self, context: BackendRequestContext, app_key: str) -> list[JSONObject]:
|
|
613
|
+
def _resolve_accessible_custom_views(self, context: BackendRequestContext, app_key: str) -> tuple[list[JSONObject], list[JSONObject]]:
|
|
537
614
|
"""执行内部辅助逻辑。"""
|
|
615
|
+
warnings: list[JSONObject] = []
|
|
538
616
|
try:
|
|
539
617
|
payload = self.backend.request("GET", context, f"/app/{app_key}/view/viewList")
|
|
540
618
|
except QingflowApiError as exc:
|
|
541
|
-
if exc
|
|
542
|
-
|
|
619
|
+
if _is_optional_app_metadata_error(exc):
|
|
620
|
+
warnings.append(
|
|
621
|
+
_with_api_error_fields(
|
|
622
|
+
{
|
|
623
|
+
"code": "CUSTOM_VIEW_LIST_UNAVAILABLE",
|
|
624
|
+
"message": (
|
|
625
|
+
f"app_get could not load custom views for {app_key}; "
|
|
626
|
+
"system views and other app metadata may still be usable."
|
|
627
|
+
),
|
|
628
|
+
},
|
|
629
|
+
exc,
|
|
630
|
+
)
|
|
631
|
+
)
|
|
632
|
+
return [], warnings
|
|
543
633
|
raise
|
|
544
634
|
|
|
545
635
|
items: list[JSONObject] = []
|
|
546
636
|
for item in _normalize_view_list(payload):
|
|
547
|
-
view_key = str(item.get("viewKey") or "").strip()
|
|
637
|
+
view_key = str(item.get("viewKey") or item.get("viewgraphKey") or "").strip()
|
|
548
638
|
if not view_key:
|
|
549
639
|
continue
|
|
550
640
|
normalized: JSONObject = {
|
|
551
641
|
"view_id": f"custom:{view_key}",
|
|
552
|
-
"name": str(item.get("viewName") or view_key).strip() or view_key,
|
|
642
|
+
"name": str(item.get("viewName") or item.get("viewgraphName") or view_key).strip() or view_key,
|
|
553
643
|
"kind": "custom",
|
|
554
644
|
}
|
|
555
645
|
view_type = str(item.get("viewType") or item.get("viewgraphType") or "").strip()
|
|
@@ -557,7 +647,7 @@ class AppTools(ToolBase):
|
|
|
557
647
|
normalized["view_type"] = view_type
|
|
558
648
|
normalized["analysis_supported"] = _analysis_supported_for_view_type(view_type or None)
|
|
559
649
|
items.append(normalized)
|
|
560
|
-
return items
|
|
650
|
+
return items, warnings
|
|
561
651
|
|
|
562
652
|
def _compact_base_info(self, result: dict[str, Any]) -> JSONObject:
|
|
563
653
|
"""执行内部辅助逻辑。"""
|
|
@@ -601,6 +691,30 @@ class AppTools(ToolBase):
|
|
|
601
691
|
},
|
|
602
692
|
}
|
|
603
693
|
|
|
694
|
+
def _base_info_from_visible_app(self, app_key: str, visible_app: JSONObject) -> JSONObject:
|
|
695
|
+
"""Build a base-info-shaped fallback from a current-user visible app item."""
|
|
696
|
+
tag_ids: list[int] = []
|
|
697
|
+
tag_id = _coerce_positive_int(visible_app.get("tag_id"))
|
|
698
|
+
if tag_id is not None:
|
|
699
|
+
tag_ids.append(tag_id)
|
|
700
|
+
for value in visible_app.get("tag_ids") if isinstance(visible_app.get("tag_ids"), list) else []:
|
|
701
|
+
tag_id = _coerce_positive_int(value)
|
|
702
|
+
if tag_id is not None and tag_id not in tag_ids:
|
|
703
|
+
tag_ids.append(tag_id)
|
|
704
|
+
|
|
705
|
+
fallback: JSONObject = {
|
|
706
|
+
"appKey": app_key,
|
|
707
|
+
"formId": visible_app.get("form_id"),
|
|
708
|
+
"formTitle": visible_app.get("app_name") or visible_app.get("title") or app_key,
|
|
709
|
+
"appIcon": visible_app.get("app_icon"),
|
|
710
|
+
"tagIds": tag_ids,
|
|
711
|
+
"visibleApp": visible_app,
|
|
712
|
+
}
|
|
713
|
+
package_name = visible_app.get("package_name") or visible_app.get("tag_name") or visible_app.get("group_name")
|
|
714
|
+
if package_name:
|
|
715
|
+
fallback["tagItems"] = [{"tagId": tag_id, "tagName": package_name} for tag_id in tag_ids]
|
|
716
|
+
return fallback
|
|
717
|
+
|
|
604
718
|
def _compact_form_schema(self, result: dict[str, Any]) -> JSONObject:
|
|
605
719
|
"""执行内部辅助逻辑。"""
|
|
606
720
|
base_questions_raw = result.get("baseQues") if isinstance(result.get("baseQues"), list) else []
|
|
@@ -759,6 +873,7 @@ class AppTools(ToolBase):
|
|
|
759
873
|
"app_key": app_key,
|
|
760
874
|
"app_name": title,
|
|
761
875
|
"title": title,
|
|
876
|
+
"app_icon": str(item.get("appIcon") or "").strip() or None,
|
|
762
877
|
"form_id": item.get("formId"),
|
|
763
878
|
"tag_id": package_tag_id,
|
|
764
879
|
"package_name": package_name,
|
|
@@ -819,17 +934,26 @@ def _normalize_form_type(value: int | str) -> int:
|
|
|
819
934
|
|
|
820
935
|
|
|
821
936
|
def _normalize_view_list(payload: Any) -> list[JSONObject]:
|
|
937
|
+
if isinstance(payload, dict):
|
|
938
|
+
raw_list = payload.get("list")
|
|
939
|
+
if isinstance(raw_list, list):
|
|
940
|
+
payload = raw_list
|
|
941
|
+
else:
|
|
942
|
+
payload = [payload]
|
|
822
943
|
if not isinstance(payload, list):
|
|
823
944
|
return []
|
|
824
945
|
flattened: list[JSONObject] = []
|
|
825
946
|
for group in payload:
|
|
826
947
|
if not isinstance(group, dict):
|
|
827
948
|
continue
|
|
949
|
+
if group.get("viewKey") or group.get("viewgraphKey"):
|
|
950
|
+
flattened.append(group)
|
|
951
|
+
continue
|
|
828
952
|
view_list = group.get("viewList")
|
|
829
953
|
if not isinstance(view_list, list):
|
|
830
954
|
continue
|
|
831
955
|
for item in view_list:
|
|
832
|
-
if isinstance(item, dict) and item.get("viewKey"):
|
|
956
|
+
if isinstance(item, dict) and (item.get("viewKey") or item.get("viewgraphKey")):
|
|
833
957
|
flattened.append(item)
|
|
834
958
|
return flattened
|
|
835
959
|
|
|
@@ -841,6 +965,23 @@ def _analysis_supported_for_view_type(view_type: str | None) -> bool:
|
|
|
841
965
|
return normalized not in {"boardview", "ganttview"}
|
|
842
966
|
|
|
843
967
|
|
|
968
|
+
def _is_optional_app_metadata_error(error: QingflowApiError) -> bool:
|
|
969
|
+
if is_auth_like_error(error):
|
|
970
|
+
return False
|
|
971
|
+
backend_code = backend_code_int(error)
|
|
972
|
+
return backend_code in {40002, 40027, 404} or error.http_status == 404
|
|
973
|
+
|
|
974
|
+
|
|
975
|
+
def _with_api_error_fields(payload: JSONObject, error: QingflowApiError) -> JSONObject:
|
|
976
|
+
if error.backend_code is not None:
|
|
977
|
+
payload["backend_code"] = error.backend_code
|
|
978
|
+
if error.http_status is not None:
|
|
979
|
+
payload["http_status"] = error.http_status
|
|
980
|
+
if error.request_id:
|
|
981
|
+
payload["request_id"] = error.request_id
|
|
982
|
+
return payload
|
|
983
|
+
|
|
984
|
+
|
|
844
985
|
def _derive_import_capability(base_info: Any) -> tuple[JSONObject, list[JSONObject]]:
|
|
845
986
|
warnings: list[JSONObject] = []
|
|
846
987
|
if not isinstance(base_info, dict):
|