@qingflow-tech/qingflow-app-builder-mcp 1.0.39 → 1.0.41

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 CHANGED
@@ -3,13 +3,13 @@
3
3
  Install:
4
4
 
5
5
  ```bash
6
- npm install @qingflow-tech/qingflow-app-builder-mcp@1.0.39
6
+ npm install @qingflow-tech/qingflow-app-builder-mcp@1.0.41
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @qingflow-tech/qingflow-app-builder-mcp@1.0.39 qingflow-app-builder-mcp
12
+ npx -y -p @qingflow-tech/qingflow-app-builder-mcp@1.0.41 qingflow-app-builder-mcp
13
13
  ```
14
14
 
15
15
  Environment:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qingflow-tech/qingflow-app-builder-mcp",
3
- "version": "1.0.39",
3
+ "version": "1.0.41",
4
4
  "description": "Builder MCP for Qingflow app/package/system design and staged solution workflows.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qingflow-mcp"
7
- version = "1.0.39"
7
+ version = "1.0.41"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -7,6 +7,8 @@ metadata:
7
7
 
8
8
  # Qingflow App Builder
9
9
 
10
+ > **Skill 版本**:`qingflow-skills-2026.06.23.1`(入口文档版本;如需确认 CLI 包版本,使用 `qingflow --version` 或 `qingflow --json version`)。
11
+
10
12
  ## Overview
11
13
 
12
14
  This skill is for the current **public builder surface**, not for legacy low-level builder writes.
@@ -15,6 +17,7 @@ In Wingent Momo runtime, trust the injected MCP session, credentials, workspace,
15
17
  Before any write or verification flow, identify whether the task targets `test` or `prod` and read [references/environments.md](references/environments.md). If the user did not specify one, default to `prod`.
16
18
 
17
19
  Before choosing a route, skim the shared maintenance baseline: [public-surface-sync.md](references/public-surface-sync.md).
20
+ Then pick the matching development guide: [single-app-development-guide.md](references/single-app-development-guide.md) for one app, or [complete-system-development-guide.md](references/complete-system-development-guide.md) for app packages/systems.
18
21
 
19
22
  ## Current Public Mental Model
20
23
 
@@ -36,15 +39,17 @@ Default modeling rules:
36
39
 
37
40
  Before any builder write, classify the request:
38
41
 
39
- - **Complete system / app package**: the user asks for a system, package, workspace module set, or several related forms/apps. Use `package_apply` for the package, then one `app_schema_apply(apps=[...])` for the app shells and fields. Do not squeeze several business objects into one app.
42
+ - **Complete system / app package**: the user asks for a system, package, workspace module set, or several related forms/apps. Use `package_apply` for the package, then one `app_schema_apply(package_id=..., apps=[...])` for the app shells and fields. This is the main path for complete systems, not a bulk shortcut to abandon after the first slow response. Do not squeeze several business objects into one app.
40
43
  - **Single app**: the user names one form/app or gives one `app_key`. Use `app_resolve`/`app_get`, then `app_schema_apply` and the app-scoped apply tools.
41
44
  - **Record/user operation**: the user wants to add, edit, delete, approve, or analyze data. Route to the record/task skills instead of builder tools.
42
45
 
43
- For complete systems, `apps[]` should use stable `client_key` values. Same-call relation fields may use `target_app_ref` for a client key or `target_app` for another app name. Prefer `target_app_ref` when names may collide.
46
+ For complete systems, `apps[]` should use stable `client_key` values. Same-call relation fields should use `target_app_ref` for another `apps[].client_key`; use `target_app_key` only when the target app already exists or has been confirmed by readback. Create the related apps with one multi-app schema apply; do not create each app separately just to collect app keys and then patch relations afterward.
47
+
48
+ If a complete-system `app_schema_apply` times out, returns `partial_success`, returns `write_executed=true`, has `safe_to_retry=false`, or has incomplete readback, treat it as an uncertain write, not as a failed create. The next action is always `readback_before_retry`: read the package/app/fields first, compare intended `client_key`/`app_name`/relations against reality, and only then repair the missing slice. Do not retry the whole multi-app create, create `V2`/`测试`/random-suffix apps, or split the system into single-app rebuilds to bypass a duplicate/conflict.
44
49
 
45
50
  Builder schema inputs should follow agent-intuitive semantics:
46
51
 
47
- - icons may be `icon/color`, `icon_name/icon_color`, `icon_config`, or `icon: {name, color}`
52
+ - primary icon syntax is `icon + color`, for example `icon: "table", color: "blue"`; `icon_name/icon_color`, `icon_config`, and `icon: {name, color}` are compatibility aliases only
48
53
  - `single_select` / `multi_select` options may be strings or objects such as `{label, value}`; tools normalize to option labels
49
54
  - relation fields need a target plus `display_field` and `visible_fields`
50
55
  - do not create built-in system fields as form fields: `数据ID`, `编号`, `申请人`, `申请时间`, `创建人`, `创建时间`, `提交人`, `提交时间`, `更新时间`, `更新人`, `当前流程状态`, `当前处理人`, `当前处理节点`, `流程标题`. They are platform-generated; only reference supported system fields where a tool explicitly says so, such as button `source_field: "数据ID"`
@@ -67,8 +72,10 @@ Treat these as the official surface. Do not default to `package_create`, `packag
67
72
  - use `package_get(package_id=...)` to read one known package
68
73
  - use `package_apply(...)` for package creation, rename, icon, visibility, grouping, ordering, and app/portal layout
69
74
  - Multi-app schema work:
70
- - use one `app_schema_apply(apps=[...])` / CLI `builder schema apply --apps-file` when creating several apps in one package
71
- - same-call relation fields may use `target_app_ref` to point at another `apps[].client_key`, or `target_app` to point at another `apps[].app_name`
75
+ - use one `app_schema_apply(package_id=..., apps=[...])` / CLI `builder schema apply --apps-file` when creating several apps in one package
76
+ - every `apps[]` item should carry its own `client_key`, `app_name`, `icon`, `color`, and `add_fields`
77
+ - same-call relation fields should use `target_app_ref` to point at another `apps[].client_key`; use `target_app` only when the app name is unique and stable, and use `target_app_key` only after the target app already exists or readback has confirmed it
78
+ - timeout / `partial_success` / `write_executed=true` / `safe_to_retry=false` means `readback_before_retry`; do not directly downgrade to single-app creation
72
79
  - App base permissions:
73
80
  - trust `app_get.editability.can_edit_app_base` for app base-info writes like app name, icon, and visibility
74
81
  - `can_edit_form` only means schema/form-route capability; it no longer implies app base-info write capability
@@ -127,7 +134,7 @@ Treat these as the official surface. Do not default to `package_create`, `packag
127
134
  1. Trust the current MCP/session when it is already injected by the runtime; only run auth/workspace recovery after a tool explicitly reports an auth, credential, or workspace error
128
135
  2. Confirm whether the task is read-only or write-impacting
129
136
  3. Classify the build scope:
130
- - complete system/package -> `package_apply` or `package_get`, then one `app_schema_apply(apps=[...])`
137
+ - complete system/package -> `package_apply` or `package_get`, then one `app_schema_apply(package_id=..., apps=[...])`
131
138
  - single app -> `app_resolve` / `app_get`, then app-scoped apply tools
132
139
  - record/task/data request -> leave builder and use the matching record/task skill
133
140
  4. Resolve the smallest stable target:
@@ -160,6 +167,8 @@ Treat these as the official surface. Do not default to `package_create`, `packag
160
167
  - Do not guess package identity from a loose name. Public package work assumes a known `package_id`, or an explicit create/update intent through `package_apply`.
161
168
  - Do not perform routine auth probes before every builder action in runtime contexts with injected MCP credentials; recover auth only after an actual auth/workspace failure.
162
169
  - If `package_id` is unknown, derive it from related app/portal readback when possible; otherwise ask the user instead of falling back to hidden package lookup tools.
170
+ - Do not bypass package/app-name conflicts by inventing `V2`, `测试`, timestamp, or random suffix apps in a real business package. Read back and decide whether to update the existing app, create only a truly missing app, or ask the user.
171
+ - After any uncertain schema write, do `readback_before_retry` with `package_get` / `app_resolve` / `app_get_fields`; retry only the verified missing or failed slice.
163
172
  - Do not use `package_create` or `package_attach_app` as a public default path. If they still appear in low-level code, treat them as internal/legacy implementation details.
164
173
  - Do not use raw `portal_*` writes or raw `qingbi_report_*` writes as the default builder strategy.
165
174
  - `app_schema_apply`, `app_layout_apply`, `app_flow_apply`, and `app_views_apply` publish by default; `app_custom_buttons_apply` and `app_associated_resources_apply` publish after at least one write succeeds and do not accept a draft-only `publish` parameter; `app_charts_apply` is immediate-live without publish.
@@ -178,7 +187,7 @@ Treat these as the official surface. Do not default to `package_create`, `packag
178
187
  - For UI cards or quick narration, read `resources[]` first. Each resource has `resource_type`, `operation`, `status`, `id`, `key`, `name`, typed `ids`, and `parent`.
179
188
  - Use legacy fields such as `field_diff`, `views_diff`, `chart_results`, `created/updated/removed`, and `verification` only for compatibility and troubleshooting.
180
189
  - Treat post-write readback as the source of truth, not just write status codes.
181
- - `success` means write and verification completed; `partial_success` means the write landed but verification is incomplete.
190
+ - `success` means write and verification completed; `partial_success` means the write landed or may have landed but verification is incomplete. If `write_executed=true`, `safe_to_retry=false`, or readback is unavailable, do `readback_before_retry` before any create retry.
182
191
  - For portals, distinguish clearly between:
183
192
  - base-info-only update with layout preserved
184
193
  - full sections replace
@@ -270,6 +279,8 @@ For add-data buttons that create a child record linked back to the current recor
270
279
  - Tool choice and sequencing: [references/tool-selection.md](references/tool-selection.md)
271
280
  - Field matching rules: [references/match-rules.md](references/match-rules.md)
272
281
  - Result semantics and gotchas: [references/gotchas.md](references/gotchas.md)
282
+ - Single app development guide: [references/single-app-development-guide.md](references/single-app-development-guide.md)
283
+ - Complete system development guide: [references/complete-system-development-guide.md](references/complete-system-development-guide.md)
273
284
  - Create one app in an existing package: [references/create-app.md](references/create-app.md)
274
285
  - Update fields only: [references/update-schema.md](references/update-schema.md)
275
286
  - Update layout only: [references/update-layout.md](references/update-layout.md)
@@ -0,0 +1,59 @@
1
+ # Complete System Development Guide
2
+
3
+ Use this when the user asks for a system, app package, workspace module set, or several related apps/forms.
4
+ Do not compress several business objects into one app.
5
+
6
+ ## Recommended Completeness
7
+
8
+ Required:
9
+
10
+ - package exists or is created through `package_apply`
11
+ - core business objects are modeled as separate apps
12
+ - all new apps are created in one multi-app `app_schema_apply(package_id=..., apps=[...])`
13
+ - every app has stable `client_key`, `app_name`, `icon`, `color`, and one readable `as_data_title: true`
14
+ - same-call relations use `target_app_ref` to point to another `apps[].client_key`
15
+ - basic operating views exist for each core app
16
+ - package/apps/fields/relations are read back before repair or retry
17
+
18
+ Strongly recommended:
19
+
20
+ - cross-app relation fields for the main business links
21
+ - core metric charts and BI charts using semantic `metric`, `metrics`, `group_by`, and `where`
22
+ - a standard portal: business entry area, core metrics, BI charts, then concrete data views
23
+ - portal grid/business-entry sections contain real `config.items[]`, not empty containers
24
+
25
+ Optional:
26
+
27
+ - workflow, reminders, buttons, associated resources, sample data, roles, and permissions when the user asks for them or the business process clearly depends on them
28
+
29
+ ## Standard Path
30
+
31
+ 1. Resolve or create package: `package_get` / `package_apply`.
32
+ 2. Draft all apps and relations with stable `client_key` values.
33
+ 3. Run one multi-app `app_schema_apply` with `apps[]`.
34
+ 4. If write result is uncertain, run readback before retrying.
35
+ 5. For each app, apply layout and views.
36
+ 6. Create charts before portals when the portal needs metrics or BI sections.
37
+ 7. Create portal only after referenced apps, views, and charts are known.
38
+ 8. Add workflows/buttons/associated resources when they are part of the requested business process.
39
+
40
+ ## Recovery Rules
41
+
42
+ - Timeout, `partial_success`, `write_executed=true`, `safe_to_retry=false`, or incomplete readback means `write_may_have_succeeded`.
43
+ - Next action is always `readback_before_retry`: read package, resolve intended apps, then read fields/relations.
44
+ - Retry only verified missing apps, fields, or relations.
45
+ - Do not rebuild the system as separate single-app creates.
46
+ - Do not create `V2`, `测试`, timestamp, or random-suffix apps to bypass duplicate names.
47
+
48
+ ## Portal Template
49
+
50
+ - Top area: one business entry grid plus one todo/common/frequent component when useful.
51
+ - Metrics: one row of 4-6 indicator/target cards with portal section `role: "metric"`; recommended height 5.
52
+ - BI charts: one row of 2-3 charts, 1-2 rows; recommended height 7.
53
+ - Data views: one row of 1-2 concrete views, 1-2 rows; recommended height 11.
54
+
55
+ ## Stop Conditions
56
+
57
+ - If the user only asked for one app, switch to the single-app guide.
58
+ - If required package/app/field/relation readback contradicts the intended model, repair the specific missing slice before building dependent resources.
59
+ - If a public tool cannot express a needed business feature, state the gap and ask before using any fallback or submitting feedback.
@@ -6,9 +6,9 @@ This playbook follows the current public builder surface, not legacy package hel
6
6
  Do not use this playbook when the user is really asking for a system/package with multiple forms or modules. In that case:
7
7
 
8
8
  1. read or create the package through `package_get` / `package_apply`
9
- 2. create the related apps in one `app_schema_apply(apps=[...])` call
9
+ 2. create the related apps in one `app_schema_apply(package_id=..., apps=[...])` call
10
10
  3. keep package ownership on the public `package_id` path instead of a separate attach step
11
- 4. add same-call relation fields with `target_app_ref` when one new app references another new app
11
+ 4. add same-call relation fields with `target_app_ref` when one new app references another new app; use `target_app_key` only after the target app already exists or readback confirms it
12
12
  5. choose explicit non-template `icon + color` for each new package/app
13
13
 
14
14
  Hierarchy reminder:
@@ -38,9 +38,11 @@ then do not treat that as one app.
38
38
  Use this pattern instead:
39
39
 
40
40
  1. `package_get` or `package_apply(create_if_missing=true, package_name=...)`
41
- 2. for a multi-app system, run one `app_schema_apply` with `apps[]`
41
+ 2. for a multi-app system, run one `app_schema_apply` with `package_id` and `apps[]`
42
42
  3. use `apps[].client_key` plus relation field `target_app_ref` when one new app references another new app
43
43
 
44
+ If that multi-app call times out, returns `partial_success`, returns `write_executed=true`, has `safe_to_retry=false`, or has incomplete readback, do not decide that the create failed. Run `readback_before_retry`: read the package, resolve each intended app by package/name or returned app key, read fields, and retry only verified missing apps or fields. Do not rebuild the same complete system as separate single-app creates, and do not create `V2`, `测试`, timestamp, or random-suffix apps to bypass duplicate names.
45
+
44
46
  ## Example
45
47
 
46
48
  When designing fields, do not add platform system fields such as `数据ID`, `编号`, `申请人`, `申请时间`, `创建人`, `创建时间`, `提交人`, `提交时间`, `更新时间`, `更新人`, `当前流程状态`, `当前处理人`, `当前处理节点`, or `流程标题`. Qingflow provides them automatically; create only business fields.
@@ -54,7 +56,8 @@ Create a new package only after the user confirms package creation:
54
56
  "profile": "default",
55
57
  "package_name": "研发项目管理",
56
58
  "create_if_missing": true,
57
- "icon": {"name": "briefcase", "color": "azure"}
59
+ "icon": "briefcase",
60
+ "color": "azure"
58
61
  }
59
62
  }
60
63
  ```
@@ -80,7 +83,8 @@ Apply schema for a new app:
80
83
  "profile": "default",
81
84
  "app_name": "客户订单",
82
85
  "package_id": 1218950,
83
- "icon": {"name": "delivery-box-1", "color": "emerald"},
86
+ "icon": "delivery-box-1",
87
+ "color": "emerald",
84
88
  "create_if_missing": true,
85
89
  "publish": true,
86
90
  "add_fields": [
@@ -110,7 +114,8 @@ Apply schema for multiple apps in one call:
110
114
  {
111
115
  "client_key": "customer",
112
116
  "app_name": "客户",
113
- "icon": {"name": "business-personalcard", "color": "emerald"},
117
+ "icon": "business-personalcard",
118
+ "color": "emerald",
114
119
  "add_fields": [
115
120
  {"name": "客户名称", "type": "text", "required": true, "as_data_title": true}
116
121
  ]
@@ -118,7 +123,8 @@ Apply schema for multiple apps in one call:
118
123
  {
119
124
  "client_key": "order",
120
125
  "app_name": "订单",
121
- "icon": {"name": "delivery-box-1", "color": "blue"},
126
+ "icon": "delivery-box-1",
127
+ "color": "blue",
122
128
  "add_fields": [
123
129
  {"name": "订单编号", "type": "text", "required": true, "as_data_title": true},
124
130
  {
@@ -17,6 +17,8 @@
17
17
  - Another app is not a field
18
18
  - If the user names multiple forms/modules that relate to each other, create multiple apps and connect them with relation fields
19
19
  - Do not use child app names like “项目/需求/任务/缺陷/团队” as plain text fields inside one app
20
+ - For a complete system, the main creation path is one multi-app `app_schema_apply(package_id=..., apps=[...])` with stable `apps[].client_key` values and same-call relation fields using `target_app_ref`
21
+ - Use `target_app_key` only for an app that already exists or has been confirmed by readback
20
22
 
21
23
  ## Auto publish
22
24
 
@@ -88,7 +90,11 @@
88
90
  ## Retry discipline
89
91
 
90
92
  - If a write returns `partial_success`, read back before retrying
93
+ - If multi-app schema apply times out, returns `write_executed=true`, returns `safe_to_retry=false`, or has incomplete readback, treat it as `write_may_have_succeeded`; the next action is `readback_before_retry`
94
+ - `readback_before_retry` means read package/app/fields first, classify which intended apps and relation fields actually exist, and retry only the verified missing slice
91
95
  - Do not repeat create steps after `app_key` already exists
96
+ - Do not split a complete-system schema create into single-app rebuilds until readback proves exactly what is missing
97
+ - Do not bypass duplicate/conflict states by inventing `V2`, `测试`, timestamp, or random-suffix app names in a real business package
92
98
  - For backend rejects, keep the retry narrow: retry only the failed tool, not the whole chain
93
99
  - For `VALIDATION_ERROR`, do not keep guessing. Reuse `suggested_next_call`, `canonical_arguments`, `allowed_keys`, and `allowed_values` first.
94
100
  - For layout `VALIDATION_ERROR`, also inspect `section_allowed_keys`, `section_aliases`, and `minimal_section_example`. If the same layout-shape error repeats twice, stop free-form retries and re-read `builder_tool_contract(app_layout_apply)`.
@@ -0,0 +1,47 @@
1
+ # Single App Development Guide
2
+
3
+ Use this when the user asks for one app/form, or gives one `app_key`.
4
+ If the user asks for a package, system, or several related modules, use `complete-system-development-guide.md` instead.
5
+
6
+ ## Recommended Completeness
7
+
8
+ Required:
9
+
10
+ - target package/app resolved by `package_get`, `app_resolve`, or `app_get`
11
+ - business fields created or updated through `app_schema_apply`
12
+ - exactly one readable top-level `as_data_title: true`
13
+ - no platform system fields in `add_fields`
14
+ - layout places the important fields
15
+ - at least one business view beyond platform default views when the task includes user-facing list work
16
+
17
+ Strongly recommended:
18
+
19
+ - saved filters and query panel fields for the main operating views
20
+ - simple charts when the app has status, amount, date, or owner fields worth tracking
21
+ - associated resources or buttons only when they support a concrete workflow
22
+
23
+ Optional:
24
+
25
+ - workflow, portal entry, sample data, roles, or visibility changes when requested by the user or clearly needed by the app's job
26
+
27
+ ## Standard Path
28
+
29
+ 1. Read current target: `app_resolve` or `app_get`.
30
+ 2. Read current fields: `app_get_fields`.
31
+ 3. Apply fields with `app_schema_apply`.
32
+ 4. Apply layout with `app_layout_apply`.
33
+ 5. Apply views with `app_views_apply` when list/table/card/gantt behavior is part of the request.
34
+ 6. Apply charts/buttons/associated resources only if they are part of the app's actual workflow.
35
+ 7. Use `app_publish_verify` only when explicit live verification is required.
36
+
37
+ ## Field Rules
38
+
39
+ - Use agent-friendly field types where possible: `text`, `multiline`, `select`, `multi_select`, `number`, `amount`, `date`, `datetime`, `member`, `department`, `attachment`, `relation`.
40
+ - Do not create `数据ID`, `编号`, `申请人`, `申请时间`, `创建人`, `创建时间`, `提交人`, `提交时间`, `更新时间`, `更新人`, `当前流程状态`, `当前处理人`, `当前处理节点`, or `流程标题`.
41
+ - Do not create built-in default views such as `全部数据` or `我的数据`; Qingflow provides system views.
42
+
43
+ ## Stop Conditions
44
+
45
+ - If the task actually needs multiple business objects, stop and switch to the complete-system guide.
46
+ - If a write returns `partial_success`, `write_executed=true`, or `safe_to_retry=false`, read back before retrying.
47
+ - If the same validation error repeats twice, re-read `builder_tool_contract` instead of guessing.
@@ -2,6 +2,16 @@
2
2
 
3
3
  Use these when you need a quick reminder of the standard v2 builder sequences.
4
4
 
5
+ ## Create a complete system / app package
6
+
7
+ 1. `package_get` or `package_apply`
8
+ 2. one multi-app `app_schema_apply(package_id=..., apps=[...])`
9
+ 3. use stable `apps[].client_key` values and relation `target_app_ref` for same-call app references
10
+ 4. use returned/readback `app_key` values for layout, views, workflow, charts, buttons, associated resources, and portal work
11
+ 5. `app_publish_verify` only when the user asks for explicit live verification or a final verification pass is required
12
+
13
+ If the multi-app schema write times out or returns `partial_success`, `write_executed=true`, `safe_to_retry=false`, or incomplete readback, the next action is `readback_before_retry`: read package/app/fields first, then retry only verified missing pieces. Do not repeat the whole apps payload, split into single-app rebuilds, or create `V2` / `测试` / random-suffix apps to dodge duplicate names.
14
+
5
15
  ## Create a new app in an existing package
6
16
 
7
17
  1. `package_get`
@@ -21,7 +21,7 @@ If the user asks for multiple forms/modules that relate to each other, this is a
21
21
 
22
22
  Use this split consistently:
23
23
 
24
- - Complete system/package: `package_apply` first, then one `app_schema_apply(apps=[...])`.
24
+ - Complete system/package: `package_apply` first, then one `app_schema_apply(package_id=..., apps=[...])`.
25
25
  - Single app: `app_resolve/app_get` first, then app-scoped apply tools.
26
26
  - Record/task/data operation: leave builder and use record/task skills.
27
27
 
@@ -103,4 +103,4 @@ For object-level updates, the safe partial syntax is `patch_*` with the object's
103
103
  - Do not omit assignees on approval/fill/copy nodes
104
104
  - Do not patch preset flows with brand new approval/fill node ids unless you are intentionally replacing the skeleton; reuse preset ids like `approve_1` and `fill_1`
105
105
  - Do not guess role ids, member ids, or editable field ids; resolve names first
106
- - Do not force agent-authored schema into backend-internal names when public aliases exist: icons may use `icon_config` / `icon:{name,color}`, options may use `{label,value}`, and same-call relation targets may use `target_app_ref` / `target_app`.
106
+ - Do not force agent-authored schema into backend-internal names when public keys exist: write icons as `icon + color` first; `icon_config` / `icon:{name,color}` are compatibility aliases only. Options may use `{label,value}`, and same-call relation targets may use `target_app_ref` / `target_app`.
@@ -7,6 +7,8 @@ metadata:
7
7
 
8
8
  # Qingflow App Builder Code Integrations
9
9
 
10
+ > **Skill 版本**:`qingflow-skills-2026.06.23.1`(入口文档版本;如需确认 CLI 包版本,使用 `qingflow --version` 或 `qingflow --json version`)。
11
+
10
12
  Use this skill when the user wants to build or repair:
11
13
  - `code_block` fields
12
14
  - `q_linker` fields
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import re
3
4
  from enum import Enum
4
5
  from typing import Any
5
6
 
@@ -70,17 +71,36 @@ class PublicExternalVisibilityMode(str, Enum):
70
71
 
71
72
 
72
73
  FIELD_TYPE_ALIASES: dict[str, PublicFieldType] = {
74
+ "multiline": PublicFieldType.long_text,
75
+ "multiline_text": PublicFieldType.long_text,
76
+ "multi_line": PublicFieldType.long_text,
77
+ "multi_line_text": PublicFieldType.long_text,
73
78
  "textarea": PublicFieldType.long_text,
79
+ "longtext": PublicFieldType.long_text,
80
+ "long-text": PublicFieldType.long_text,
74
81
  "amount": PublicFieldType.amount,
75
82
  "currency": PublicFieldType.amount,
76
83
  "mobile": PublicFieldType.phone,
77
84
  "user": PublicFieldType.member,
78
85
  "users": PublicFieldType.member,
79
86
  "select": PublicFieldType.single_select,
87
+ "single_choice": PublicFieldType.single_select,
88
+ "single-choice": PublicFieldType.single_select,
89
+ "single choice": PublicFieldType.single_select,
90
+ "choice": PublicFieldType.single_select,
91
+ "dropdown": PublicFieldType.single_select,
80
92
  "radio": PublicFieldType.single_select,
81
93
  "checkbox": PublicFieldType.multi_select,
82
94
  "multi_select": PublicFieldType.multi_select,
83
95
  "multi-select": PublicFieldType.multi_select,
96
+ "multi select": PublicFieldType.multi_select,
97
+ "multiselect": PublicFieldType.multi_select,
98
+ "multi_choice": PublicFieldType.multi_select,
99
+ "multi-choice": PublicFieldType.multi_select,
100
+ "multi choice": PublicFieldType.multi_select,
101
+ "multiple_choice": PublicFieldType.multi_select,
102
+ "multiple-choice": PublicFieldType.multi_select,
103
+ "multiple choice": PublicFieldType.multi_select,
84
104
  "departments": PublicFieldType.department,
85
105
  "qlinker": PublicFieldType.q_linker,
86
106
  "q_linker": PublicFieldType.q_linker,
@@ -1796,6 +1816,65 @@ class ChartFilterRulePatch(StrictModel):
1796
1816
  return self
1797
1817
 
1798
1818
 
1819
+ class ChartMetricPatch(StrictModel):
1820
+ op: str = "count"
1821
+ field_name: str | None = Field(default=None, validation_alias=AliasChoices("field", "field_name", "fieldName", "name"))
1822
+ alias: str | None = None
1823
+
1824
+ @model_validator(mode="before")
1825
+ @classmethod
1826
+ def normalize_metric(cls, value: Any) -> Any:
1827
+ if isinstance(value, str):
1828
+ raw = value.strip()
1829
+ match = re.fullmatch(r"([A-Za-z_][A-Za-z0-9_]*)\s*\(\s*(.*?)\s*\)", raw)
1830
+ if match:
1831
+ op = match.group(1).strip().lower()
1832
+ field = match.group(2).strip()
1833
+ payload: dict[str, Any] = {"op": op}
1834
+ if field and field != "*":
1835
+ payload["field_name"] = field
1836
+ return payload
1837
+ if raw:
1838
+ return {"op": "sum", "field_name": raw}
1839
+ return {"op": "count"}
1840
+ if not isinstance(value, dict):
1841
+ return value
1842
+ payload = dict(value)
1843
+ if "operation" in payload and "op" not in payload:
1844
+ payload["op"] = payload.pop("operation")
1845
+ if "agg" in payload and "op" not in payload:
1846
+ payload["op"] = payload.pop("agg")
1847
+ if "aggregate" in payload and "op" not in payload:
1848
+ payload["op"] = payload.pop("aggregate")
1849
+ if "aggregation" in payload and "op" not in payload:
1850
+ payload["op"] = payload.pop("aggregation")
1851
+ return payload
1852
+
1853
+ @model_validator(mode="after")
1854
+ def validate_metric(self) -> "ChartMetricPatch":
1855
+ normalized_op = str(self.op or "count").strip().lower()
1856
+ op_aliases = {
1857
+ "average": "avg",
1858
+ "mean": "avg",
1859
+ "total": "sum",
1860
+ "cnt": "count",
1861
+ "count_all": "count",
1862
+ }
1863
+ self.op = op_aliases.get(normalized_op, normalized_op)
1864
+ if self.field_name is not None:
1865
+ field_name = str(self.field_name).strip()
1866
+ self.field_name = field_name or None
1867
+ if self.alias is not None:
1868
+ alias = str(self.alias).strip()
1869
+ self.alias = alias or None
1870
+ supported = {"count", "sum", "avg", "max", "min"}
1871
+ if self.op not in supported:
1872
+ raise ValueError(f"chart metric op must be one of {sorted(supported)}")
1873
+ if self.op != "count" and not self.field_name:
1874
+ raise ValueError(f"chart metric op '{self.op}' requires field")
1875
+ return self
1876
+
1877
+
1799
1878
  class ChartUpsertPatch(StrictModel):
1800
1879
  chart_id: str | None = None
1801
1880
  name: str
@@ -1803,6 +1882,17 @@ class ChartUpsertPatch(StrictModel):
1803
1882
  dimension_field_ids: list[str] = Field(default_factory=list)
1804
1883
  indicator_field_ids: list[str] = Field(default_factory=list)
1805
1884
  filters: list[ChartFilterRulePatch] = Field(default_factory=list)
1885
+ group_by: list[str] = Field(default_factory=list)
1886
+ rows: list[str] = Field(default_factory=list)
1887
+ columns: list[str] = Field(default_factory=list)
1888
+ metric: ChartMetricPatch | None = None
1889
+ metrics: list[ChartMetricPatch] = Field(default_factory=list)
1890
+ x_metric: ChartMetricPatch | None = None
1891
+ y_metric: ChartMetricPatch | None = None
1892
+ left_metric: ChartMetricPatch | None = None
1893
+ right_metric: ChartMetricPatch | None = None
1894
+ value_metric: ChartMetricPatch | None = None
1895
+ target_metric: ChartMetricPatch | None = None
1806
1896
  question_config: list[dict[str, Any]] = Field(default_factory=list)
1807
1897
  user_config: list[dict[str, Any]] = Field(default_factory=list)
1808
1898
  config: dict[str, Any] = Field(default_factory=dict)
@@ -1820,10 +1910,74 @@ class ChartUpsertPatch(StrictModel):
1820
1910
  payload["chart_type"] = payload.pop("type")
1821
1911
  if "dimension_fields" in payload and "dimension_field_ids" not in payload:
1822
1912
  payload["dimension_field_ids"] = payload.pop("dimension_fields")
1913
+ if "dimensions" in payload and "dimension_field_ids" not in payload and "group_by" not in payload:
1914
+ payload["group_by"] = payload.pop("dimensions")
1915
+ if "groupBy" in payload and "group_by" not in payload:
1916
+ payload["group_by"] = payload.pop("groupBy")
1917
+ if "where" in payload and "filters" not in payload:
1918
+ payload["filters"] = payload.pop("where")
1919
+ if "filter_rules" in payload and "filters" not in payload:
1920
+ payload["filters"] = payload.pop("filter_rules")
1921
+ if "filterRules" in payload and "filters" not in payload:
1922
+ payload["filters"] = payload.pop("filterRules")
1823
1923
  if "indicator_fields" in payload and "indicator_field_ids" not in payload:
1824
1924
  payload["indicator_field_ids"] = payload.pop("indicator_fields")
1825
1925
  if "metric_field_ids" in payload and "indicator_field_ids" not in payload:
1826
1926
  payload["indicator_field_ids"] = payload.pop("metric_field_ids")
1927
+ metric_slots: list[Any] = []
1928
+ generic_metric_keys = ("metric", "metrics")
1929
+ axis_metric_keys = (
1930
+ "x_metric",
1931
+ "xMetric",
1932
+ "y_metric",
1933
+ "yMetric",
1934
+ "left_metric",
1935
+ "leftMetric",
1936
+ "right_metric",
1937
+ "rightMetric",
1938
+ "value_metric",
1939
+ "valueMetric",
1940
+ "target_metric",
1941
+ "targetMetric",
1942
+ )
1943
+
1944
+ def has_metric_value(key: str) -> bool:
1945
+ entry = payload.get(key)
1946
+ return entry is not None and entry != "" and entry != []
1947
+
1948
+ if any(has_metric_value(key) for key in generic_metric_keys) and any(has_metric_value(key) for key in axis_metric_keys):
1949
+ raise ValueError(
1950
+ "chart metric input is ambiguous: use either metric/metrics or axis-specific "
1951
+ "x_metric/y_metric, left_metric/right_metric, value_metric/target_metric, not both"
1952
+ )
1953
+ if "metric" in payload and "metrics" not in payload:
1954
+ payload["metrics"] = [payload["metric"]]
1955
+ for key in ("x_metric", "xMetric", "left_metric", "leftMetric", "value_metric", "valueMetric"):
1956
+ if key in payload:
1957
+ slot_value = payload.get(key)
1958
+ if slot_value is not None:
1959
+ metric_slots.append(slot_value)
1960
+ canonical = {
1961
+ "xMetric": "x_metric",
1962
+ "leftMetric": "left_metric",
1963
+ "valueMetric": "value_metric",
1964
+ }.get(key)
1965
+ if canonical and canonical not in payload:
1966
+ payload[canonical] = payload.pop(key)
1967
+ for key in ("y_metric", "yMetric", "right_metric", "rightMetric", "target_metric", "targetMetric"):
1968
+ if key in payload:
1969
+ slot_value = payload.get(key)
1970
+ if slot_value is not None:
1971
+ metric_slots.append(slot_value)
1972
+ canonical = {
1973
+ "yMetric": "y_metric",
1974
+ "rightMetric": "right_metric",
1975
+ "targetMetric": "target_metric",
1976
+ }.get(key)
1977
+ if canonical and canonical not in payload:
1978
+ payload[canonical] = payload.pop(key)
1979
+ if metric_slots and "metrics" not in payload:
1980
+ payload["metrics"] = metric_slots
1827
1981
  raw_type = payload.get("chart_type")
1828
1982
  if isinstance(raw_type, str):
1829
1983
  normalized = raw_type.strip().lower()
@@ -1850,8 +2004,31 @@ class ChartUpsertPatch(StrictModel):
1850
2004
  payload["dimension_field_ids"] = [str(item) for item in payload["dimension_field_ids"] if item is not None and str(item).strip()]
1851
2005
  if isinstance(payload.get("indicator_field_ids"), list):
1852
2006
  payload["indicator_field_ids"] = [str(item) for item in payload["indicator_field_ids"] if item is not None and str(item).strip()]
2007
+ for key in ("group_by", "rows", "columns"):
2008
+ if isinstance(payload.get(key), str):
2009
+ payload[key] = [payload[key]]
2010
+ if isinstance(payload.get(key), list):
2011
+ payload[key] = [str(item) for item in payload[key] if item is not None and str(item).strip()]
1853
2012
  return payload
1854
2013
 
2014
+ @model_validator(mode="after")
2015
+ def apply_semantic_chart_fields(self) -> "ChartUpsertPatch":
2016
+ if self.group_by and not self.dimension_field_ids:
2017
+ self.dimension_field_ids = list(self.group_by)
2018
+ if self.rows and not self.dimension_field_ids:
2019
+ self.dimension_field_ids = list(self.rows)
2020
+ semantic_metrics: list[ChartMetricPatch] = []
2021
+ if self.metrics:
2022
+ semantic_metrics.extend(self.metrics)
2023
+ for metric in (self.x_metric, self.y_metric, self.left_metric, self.right_metric, self.value_metric, self.target_metric):
2024
+ if metric is not None and metric not in semantic_metrics:
2025
+ semantic_metrics.append(metric)
2026
+ if semantic_metrics and not self.metrics:
2027
+ self.metrics = semantic_metrics
2028
+ if self.metrics and not self.metric:
2029
+ self.metric = self.metrics[0]
2030
+ return self
2031
+
1855
2032
 
1856
2033
  class ChartPartialPatch(StrictModel):
1857
2034
  chart_id: str | None = None
@@ -1993,6 +2170,7 @@ class PortalViewRefPatch(StrictModel):
1993
2170
  class PortalSectionPatch(StrictModel):
1994
2171
  title: str
1995
2172
  source_type: str = Field(validation_alias=AliasChoices("source_type", "sourceType"))
2173
+ role: str | None = Field(default=None, validation_alias=AliasChoices("role", "zone", "section_role", "sectionRole"))
1996
2174
  position: PortalComponentPositionPatch | None = None
1997
2175
  dash_style_config: dict[str, Any] | None = Field(default=None, validation_alias=AliasChoices("dash_style_config", "dashStyleConfigBO"))
1998
2176
  config: dict[str, Any] = Field(default_factory=dict)
@@ -2010,6 +2188,9 @@ class PortalSectionPatch(StrictModel):
2010
2188
  raw_type = payload.get("source_type", payload.get("sourceType"))
2011
2189
  if isinstance(raw_type, str):
2012
2190
  payload["source_type"] = raw_type.strip().lower()
2191
+ raw_role = payload.get("role", payload.get("zone", payload.get("section_role", payload.get("sectionRole"))))
2192
+ if isinstance(raw_role, str):
2193
+ payload["role"] = raw_role.strip().lower()
2013
2194
  if "chartRef" in payload and "chart_ref" not in payload:
2014
2195
  payload["chart_ref"] = payload.pop("chartRef")
2015
2196
  if "viewRef" in payload and "view_ref" not in payload:
@@ -2216,6 +2397,8 @@ class ChartGetResponse(StrictModel):
2216
2397
  base: dict[str, Any] = Field(default_factory=dict)
2217
2398
  visibility: dict[str, Any] = Field(default_factory=dict)
2218
2399
  filters: list[list[dict[str, Any]]] = Field(default_factory=list)
2400
+ group_by: list[str] = Field(default_factory=list)
2401
+ metrics: list[dict[str, Any]] = Field(default_factory=list)
2219
2402
  config: dict[str, Any] = Field(default_factory=dict)
2220
2403
 
2221
2404