@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.
Files changed (154) hide show
  1. package/README.md +7 -3
  2. package/docs/local-agent-install.md +57 -6
  3. package/entry_point.py +1 -1
  4. package/npm/bin/qingflow-skills.mjs +5 -0
  5. package/npm/bin/qingflow.mjs +1 -34
  6. package/npm/lib/runtime.mjs +21 -101
  7. package/npm/scripts/postinstall.mjs +1 -10
  8. package/package.json +3 -2
  9. package/pyproject.toml +1 -1
  10. package/skills/qingflow-cli/SKILL.md +58 -44
  11. package/skills/qingflow-cli/manifest.yaml +1 -1
  12. package/skills/qingflow-cli/reference/00-INDEX.md +35 -0
  13. package/skills/qingflow-cli/reference/builder/10-build-single-app.md +38 -0
  14. package/skills/qingflow-cli/reference/builder/20-build-complete-system.md +39 -0
  15. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_SCHEMA_APPLY_FIELD_TYPES_AND_SCENARIOS.md → builder/30-schema-fields.md} +52 -10
  16. package/skills/qingflow-cli/reference/builder/40-layout.md +52 -0
  17. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_VIEWS_WORKFLOW.md → builder/50-views.md} +39 -15
  18. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_CHARTS_WORKFLOW.md → builder/60-charts.md} +36 -13
  19. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_PORTAL_WORKFLOW.md → builder/70-portal.md} +36 -13
  20. package/skills/qingflow-cli/reference/builder/80-buttons-associated-resources.md +41 -0
  21. package/skills/qingflow-cli/reference/builder/90-workflow.md +34 -0
  22. package/skills/qingflow-cli/reference/builder/99-publish-verify.md +46 -0
  23. package/skills/qingflow-cli/reference/builder/README.md +41 -0
  24. package/skills/qingflow-cli/reference/builder/code-integrations/README.md +130 -0
  25. package/skills/qingflow-cli/reference/builder/code-integrations/code-block.md +66 -0
  26. package/skills/qingflow-cli/reference/builder/code-integrations/q-linker.md +77 -0
  27. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_APP_DELIVERY_WORKFLOW.md → builder/reference/app-delivery-sop.md} +26 -16
  28. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/README.md +293 -0
  29. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/build-complete-system.md +809 -0
  30. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/build-single-app.md +830 -0
  31. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/complete-system-development-guide.md +123 -0
  32. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/create-app.md +182 -0
  33. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/environments.md +63 -0
  34. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/flow-actors-and-permissions.md +142 -0
  35. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/gotchas.md +108 -0
  36. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/match-rules.md +114 -0
  37. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/public-surface-sync.md +75 -0
  38. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/single-app-development-guide.md +58 -0
  39. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/solution-playbooks.md +52 -0
  40. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/tool-selection.md +107 -0
  41. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-flow.md +7 -0
  42. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-layout.md +7 -0
  43. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-schema.md +7 -0
  44. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-views.md +7 -0
  45. package/skills/qingflow-cli/reference/builder/workflow/01-overview.md +45 -0
  46. package/skills/qingflow-cli/reference/builder/workflow/02-update-mode.md +53 -0
  47. package/skills/qingflow-cli/reference/builder/workflow/03-flow-patterns.md +57 -0
  48. package/skills/qingflow-cli/reference/builder/workflow/04-stage1-business-modeling.md +131 -0
  49. package/skills/qingflow-cli/reference/builder/workflow/05-stage2-members-roles.md +29 -0
  50. package/skills/qingflow-cli/reference/builder/workflow/06-stage3-build-spec.md +165 -0
  51. package/skills/qingflow-cli/reference/builder/workflow/07-stage4-validate-spec.md +33 -0
  52. package/skills/qingflow-cli/reference/builder/workflow/08-stage5-apply-verify.md +51 -0
  53. package/skills/qingflow-cli/reference/builder/workflow/09-stage6-summary.md +88 -0
  54. package/skills/qingflow-cli/reference/builder/workflow/10-node-config-reference.md +93 -0
  55. package/skills/qingflow-cli/reference/builder/workflow/11-troubleshooting.md +15 -0
  56. package/skills/qingflow-cli/reference/builder/workflow/README.md +88 -0
  57. package/skills/qingflow-cli/reference/builder/workflow/workflow-schema.json +1754 -0
  58. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_ADMIN_CHEATSHEET.md → core/QINGFLOW_CLI_ADMIN_CHEATSHEET.md} +3 -3
  59. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md → core/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md} +6 -6
  60. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_EXPLORATION_REPORT.md → core/QINGFLOW_CLI_EXPLORATION_REPORT.md} +2 -2
  61. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_FIELD_DATA_TYPES.md → core/QINGFLOW_CLI_FIELD_DATA_TYPES.md} +11 -11
  62. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_MEMBER_CHEATSHEET.md → core/QINGFLOW_CLI_MEMBER_CHEATSHEET.md} +4 -4
  63. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md → core/QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md} +4 -4
  64. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md → record/QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md} +3 -3
  65. package/skills/qingflow-cli/reference/record/QINGFLOW_CLI_RECORD_DELETE_WORKFLOW.md +31 -0
  66. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md → record/QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md} +4 -4
  67. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md → record/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md} +7 -7
  68. package/skills/qingflow-cli/reference/record/analysis/README.md +130 -0
  69. package/skills/qingflow-cli/reference/record/analysis/analysis-gotchas.md +91 -0
  70. package/skills/qingflow-cli/reference/record/analysis/analysis-patterns.md +112 -0
  71. package/skills/qingflow-cli/reference/record/analysis/business-context.md +74 -0
  72. package/skills/qingflow-cli/reference/record/analysis/confidence-reporting.md +69 -0
  73. package/skills/qingflow-cli/reference/record/analysis/data-access-playbook.md +106 -0
  74. package/skills/qingflow-cli/reference/record/analysis/pandas-recipes.md +172 -0
  75. package/skills/qingflow-cli/reference/record/analysis/report-format.md +76 -0
  76. package/skills/qingflow-cli/reference/record/insert/README.md +75 -0
  77. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md → task/QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md} +5 -5
  78. package/skills/qingflow-cli/reference/task/ops/README.md +131 -0
  79. package/skills/qingflow-cli/reference/task/ops/environments.md +43 -0
  80. package/skills/qingflow-cli/reference/task/ops/workflow-usage.md +26 -0
  81. package/skills/qingflow-cli/scripts/validate_system_build_summary.py +124 -0
  82. package/skills/qingflow-cli/scripts/workflow/diff_flow_spec.py +275 -0
  83. package/skills/qingflow-cli/scripts/workflow/validate_flow_spec.py +605 -0
  84. package/skills/qingflow-mcp-setup/SKILL.md +115 -0
  85. package/skills/qingflow-mcp-setup/agents/openai.yaml +4 -0
  86. package/skills/qingflow-mcp-setup/references/claude-desktop.md +34 -0
  87. package/skills/qingflow-mcp-setup/references/environments.md +62 -0
  88. package/skills/qingflow-mcp-setup/references/generic-stdio.md +32 -0
  89. package/skills/qingflow-mcp-setup/scripts/check_local_server.sh +38 -0
  90. package/src/qingflow_mcp/__init__.py +1 -1
  91. package/src/qingflow_mcp/__main__.py +6 -2
  92. package/src/qingflow_mcp/builder_facade/models.py +282 -102
  93. package/src/qingflow_mcp/builder_facade/service.py +4192 -935
  94. package/src/qingflow_mcp/cli/commands/builder.py +316 -298
  95. package/src/qingflow_mcp/cli/commands/chart.py +1 -1
  96. package/src/qingflow_mcp/cli/commands/common.py +12 -3
  97. package/src/qingflow_mcp/cli/commands/exports.py +2 -2
  98. package/src/qingflow_mcp/cli/commands/imports.py +3 -3
  99. package/src/qingflow_mcp/cli/commands/portal.py +2 -2
  100. package/src/qingflow_mcp/cli/commands/record.py +101 -27
  101. package/src/qingflow_mcp/cli/commands/task.py +28 -47
  102. package/src/qingflow_mcp/cli/commands/view.py +1 -1
  103. package/src/qingflow_mcp/cli/context.py +0 -3
  104. package/src/qingflow_mcp/cli/formatters.py +784 -16
  105. package/src/qingflow_mcp/cli/main.py +117 -33
  106. package/src/qingflow_mcp/errors.py +43 -2
  107. package/src/qingflow_mcp/public_surface.py +26 -17
  108. package/src/qingflow_mcp/response_trim.py +81 -17
  109. package/src/qingflow_mcp/server.py +14 -12
  110. package/src/qingflow_mcp/server_app_builder.py +65 -21
  111. package/src/qingflow_mcp/server_app_user.py +22 -16
  112. package/src/qingflow_mcp/session_store.py +11 -7
  113. package/src/qingflow_mcp/solution/compiler/__init__.py +3 -1
  114. package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +173 -0
  115. package/src/qingflow_mcp/solution/executor.py +245 -18
  116. package/src/qingflow_mcp/tools/ai_builder_tools.py +1780 -406
  117. package/src/qingflow_mcp/tools/app_tools.py +184 -43
  118. package/src/qingflow_mcp/tools/approval_tools.py +197 -35
  119. package/src/qingflow_mcp/tools/auth_tools.py +92 -16
  120. package/src/qingflow_mcp/tools/code_block_tools.py +298 -40
  121. package/src/qingflow_mcp/tools/custom_button_tools.py +64 -10
  122. package/src/qingflow_mcp/tools/directory_tools.py +236 -72
  123. package/src/qingflow_mcp/tools/export_tools.py +244 -34
  124. package/src/qingflow_mcp/tools/feedback_tools.py +9 -0
  125. package/src/qingflow_mcp/tools/file_tools.py +9 -3
  126. package/src/qingflow_mcp/tools/import_tools.py +336 -49
  127. package/src/qingflow_mcp/tools/navigation_tools.py +91 -12
  128. package/src/qingflow_mcp/tools/package_tools.py +118 -6
  129. package/src/qingflow_mcp/tools/portal_tools.py +39 -3
  130. package/src/qingflow_mcp/tools/qingbi_report_tools.py +116 -7
  131. package/src/qingflow_mcp/tools/record_tools.py +1141 -356
  132. package/src/qingflow_mcp/tools/resource_read_tools.py +188 -39
  133. package/src/qingflow_mcp/tools/role_tools.py +80 -9
  134. package/src/qingflow_mcp/tools/solution_tools.py +59 -45
  135. package/src/qingflow_mcp/tools/task_context_tools.py +662 -158
  136. package/src/qingflow_mcp/tools/task_tools.py +113 -29
  137. package/src/qingflow_mcp/tools/view_tools.py +106 -3
  138. package/src/qingflow_mcp/tools/workflow_tools.py +48 -4
  139. package/src/qingflow_mcp/tools/workspace_tools.py +71 -3
  140. /package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_MATCH_RULES.md → builder/reference/match-rules.md} +0 -0
  141. /package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_WORKSPACE_ICONS.md → builder/reference/workspace-icons.md} +0 -0
  142. /package/skills/qingflow-cli/reference/{charts_remove.example.json → examples/charts/charts_remove.example.json} +0 -0
  143. /package/skills/qingflow-cli/reference/{charts_reorder.example.json → examples/charts/charts_reorder.example.json} +0 -0
  144. /package/skills/qingflow-cli/reference/{charts_upsert_bar.example.json → examples/charts/charts_upsert_bar.example.json} +0 -0
  145. /package/skills/qingflow-cli/reference/{charts_upsert_dashboard_starter.example.json → examples/charts/charts_upsert_dashboard_starter.example.json} +0 -0
  146. /package/skills/qingflow-cli/reference/{charts_upsert_minimal.example.json → examples/charts/charts_upsert_minimal.example.json} +0 -0
  147. /package/skills/qingflow-cli/reference/{portal_sections_all_types.example.json → examples/portal/portal_sections_all_types.example.json} +0 -0
  148. /package/skills/qingflow-cli/reference/{portal_sections_five_types.example.json → examples/portal/portal_sections_five_types.example.json} +0 -0
  149. /package/skills/qingflow-cli/reference/{portal_sections_standard_workbench.example.json → examples/portal/portal_sections_standard_workbench.example.json} +0 -0
  150. /package/skills/qingflow-cli/reference/{_batch_schema_complex.json → examples/schema/_batch_schema_complex.json} +0 -0
  151. /package/skills/qingflow-cli/reference/{_batch_schema_scalar.json → examples/schema/_batch_schema_scalar.json} +0 -0
  152. /package/skills/qingflow-cli/reference/{schema_add_fields_minimal.example.json → examples/schema/schema_add_fields_minimal.example.json} +0 -0
  153. /package/skills/qingflow-cli/reference/{schema_apply_add_fields_all_types.json → examples/schema/schema_apply_add_fields_all_types.json} +0 -0
  154. /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
- result = self.backend.request("GET", context, "/app/item", params=params)
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 exc.backend_code not in {40002, 40027}:
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 fully verify create access for {app_key}: "
213
- f"{exc.message or 'probe failed'}"
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
- accessible_views.extend(self._resolve_accessible_custom_views(context, app_key))
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
- result = self.backend.request("GET", context, f"/app/{app_key}/baseInfo")
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.backend_code in {40002, 40027}:
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.backend_code in {40002, 40027}:
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
- warnings.append(
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.backend_code in {40002, 40027}:
542
- return []
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):