@qingflow-tech/qingflow-app-user-mcp 1.0.33 → 1.0.35

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-user-mcp@1.0.33
6
+ npm install @qingflow-tech/qingflow-app-user-mcp@1.0.35
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @qingflow-tech/qingflow-app-user-mcp@1.0.33 qingflow-app-user-mcp
12
+ npx -y -p @qingflow-tech/qingflow-app-user-mcp@1.0.35 qingflow-app-user-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-user-mcp",
3
- "version": "1.0.33",
3
+ "version": "1.0.35",
4
4
  "description": "Operational end-user MCP for Qingflow records, tasks, comments, and directory 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.33"
7
+ version = "1.0.35"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -32,6 +32,23 @@ Default modeling rules:
32
32
  - Another business object -> a separate app, not a text field
33
33
  - Cross-object links -> relation fields, not text fields
34
34
 
35
+ ## Route First
36
+
37
+ Before any builder write, classify the request:
38
+
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.
40
+ - **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
+ - **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
+
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.
44
+
45
+ Builder schema inputs should follow agent-intuitive semantics:
46
+
47
+ - icons may be `icon/color`, `icon_name/icon_color`, `icon_config`, or `icon: {name, color}`
48
+ - `single_select` / `multi_select` options may be strings or objects such as `{label, value}`; tools normalize to option labels
49
+ - relation fields need a target plus `display_field` and `visible_fields`
50
+ - 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"`
51
+
35
52
  ## Public Tools You Should Think In
36
53
 
37
54
  - Package: `package_get`, `package_apply`
@@ -51,7 +68,7 @@ Treat these as the official surface. Do not default to `package_create`, `packag
51
68
  - use `package_apply(...)` for package creation, rename, icon, visibility, grouping, ordering, and app/portal layout
52
69
  - Multi-app schema work:
53
70
  - use one `app_schema_apply(apps=[...])` / CLI `builder schema apply --apps-file` when creating several apps in one package
54
- - same-call relation fields may use `target_app_ref` to point at another `apps[].client_key`
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`
55
72
  - App base permissions:
56
73
  - trust `app_get.editability.can_edit_app_base` for app base-info writes like app name, icon, and visibility
57
74
  - `can_edit_form` only means schema/form-route capability; it no longer implies app base-info write capability
@@ -68,6 +85,7 @@ Treat these as the official surface. Do not default to `package_create`, `packag
68
85
  - dataset BI reports are not created or edited by `app_charts_apply` yet; create them in QingBI first, then attach the existing report with `app_associated_resources_apply` and `report_source="dataset"`
69
86
  - it does not attach the report to the Qingflow app associated-resource display; use `app_associated_resources_apply` for that
70
87
  - supported `chart_type` values include legacy public aliases `target`, `table` and QingBI types such as `summary`, `columnar`, `area`, `stacked_area`, `funnel`, `waterfall`, `gauge`, `heatmap`, `histogram`, `treemap`, `radar`, `stacked_bar`, `stacked_column`, `scatter`, `ring`, `rose`, `dualaxes`, `map`, and `timeline`
88
+ - chart `filters` use the unified fixed-filter DSL: `field_name + operator + value/values`; the tool compiles them to QingBI string judge types
71
89
  - use `patch_charts` for changing a chart name, visibility, filters, or one config fragment on an existing chart
72
90
  - `visibility` is a public capability and should be treated as a base-only permission update
73
91
  - do not model chart visibility changes as raw config rewrites
@@ -92,14 +110,15 @@ Treat these as the official surface. Do not default to `package_create`, `packag
92
110
  - before creating a resource, check `app_get.associated_resources` for the same `target_app_key + view_key/chart_key`; if it already exists, use `patch_resources` with that `associated_item_id`
93
111
  - `client_key` is only a same-call reference for `view_configs[].associated_item_refs`; it is not saved and cannot deduplicate later calls
94
112
  - do not pass backend raw `sourceType`; view resources infer the internal Qingflow view source, report/chart resources default to BI app reports, and existing dataset reports use `report_source="dataset"`
95
- - use `match_mappings` for associated view/report filters; dynamic conditions use `source_field`, static conditions use `value`
113
+ - use `match_mappings` for associated view/report filters; dynamic context matches use `source_field`, static filters use `value`; do not handwrite raw `matchRules`
96
114
  - if field type compatibility is unclear, read [references/match-rules.md](references/match-rules.md)
97
115
  - Views and flows:
98
116
  - stay on `app_views_apply` / `app_flow_apply`
99
117
  - use `patch_views` for existing-view parameter changes such as `query_conditions`, `filters`, `columns`, or visibility
100
118
  - builder view writes use raw `view_key` values from `app_get.views`; `custom:<viewKey>` is a record-data `view_id` form, not a builder `view_key`
119
+ - do not create platform default/system views named `全部数据`, `我的数据`, `我发起的`, `待办`, `已办`, or `抄送`; they are built in. New views must use business-specific names. To change a built-in view, patch by its existing raw `view_key`
101
120
  - use `builder_tool_contract` whenever the minimal legal shape is unclear
102
- - keep view `filters` and `query_conditions` separate: `filters` are fixed saved filters; `query_conditions` configure the frontend query panel fields and only take effect after users enter query values
121
+ - keep view `filters` and `query_conditions` separate: `filters` use `field_name + operator + value/values` and are fixed saved filters; `query_conditions` configure the frontend query panel fields and only take effect after users enter query values
103
122
  - new views default the associated report/view display area to visible with `limit_type="all"`; existing views preserve their current associated-resource display unless `associated_resources` is explicitly patched
104
123
  - configure associated reports/views through `app_associated_resources_apply`, not through `app_views_apply`
105
124
 
@@ -107,11 +126,15 @@ Treat these as the official surface. Do not default to `package_create`, `packag
107
126
 
108
127
  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
109
128
  2. Confirm whether the task is read-only or write-impacting
110
- 3. Resolve the smallest stable target:
129
+ 3. Classify the build scope:
130
+ - complete system/package -> `package_apply` or `package_get`, then one `app_schema_apply(apps=[...])`
131
+ - single app -> `app_resolve` / `app_get`, then app-scoped apply tools
132
+ - record/task/data request -> leave builder and use the matching record/task skill
133
+ 4. Resolve the smallest stable target:
111
134
  - app-scoped work -> `app_resolve`
112
135
  - package-scoped work with known id -> `package_get`
113
136
  - portal inventory -> `portal_list`
114
- 4. Read only the smallest config slice needed:
137
+ 5. Read only the smallest config slice needed:
115
138
  - app map -> `app_get` (default entry; includes compact views, charts, custom buttons, and associated resource pool)
116
139
  - fields -> `app_get_fields`
117
140
  - layout -> `app_get_layout`
@@ -119,8 +142,8 @@ Treat these as the official surface. Do not default to `package_create`, `packag
119
142
  - flow -> `app_get_flow`
120
143
  - charts -> `app_get_charts` only when the app_get compact list is not enough
121
144
  - portal -> `portal_get`
122
- 5. If the public shape is unclear, call `builder_tool_contract`
123
- 6. Apply the smallest patch tool that fits:
145
+ 6. If the public shape is unclear, call `builder_tool_contract`
146
+ 7. Apply the smallest patch tool that fits:
124
147
  - fields -> `app_schema_apply`
125
148
  - layout -> `app_layout_apply`
126
149
  - flow -> `app_flow_apply`
@@ -130,7 +153,7 @@ Treat these as the official surface. Do not default to `package_create`, `packag
130
153
  - existing charts -> `app_charts_apply.patch_charts`; new/full charts -> `app_charts_apply.upsert_charts`
131
154
  - portal -> `portal_apply`
132
155
  - package metadata/layout -> `package_apply`
133
- 7. Use `app_publish_verify` only when the user explicitly wants final publish/live verification or you need a dedicated verification pass
156
+ 8. Use `app_publish_verify` only when the user explicitly wants final publish/live verification or you need a dedicated verification pass
134
157
 
135
158
  ## Safe Usage Rules
136
159
 
@@ -141,6 +164,7 @@ Treat these as the official surface. Do not default to `package_create`, `packag
141
164
  - Do not use raw `portal_*` writes or raw `qingbi_report_*` writes as the default builder strategy.
142
165
  - `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.
143
166
  - When creating or updating fields, configure the app data title/cover directly in field JSON: `as_data_title: true` is required on exactly one readable top-level title field; `as_data_cover: true` is optional and only valid on one top-level `attachment` field.
167
+ - Do not add platform system fields to `add_fields`; if the user asks for record id, number, applicant, creation/update time, or workflow status, use the built-in data/list/readback fields instead of creating duplicate controls.
144
168
  - If the same validation error repeats twice, stop guessing and re-read `builder_tool_contract`.
145
169
  - For workflow assignees, prefer `role_search` over explicit members unless the user explicitly wants named members.
146
170
  - Public flow building is still intentionally limited to stable linear workflows. If a requirement sounds like branches/conditions, explain the limitation instead of freehanding unsupported graph shapes.
@@ -9,6 +9,7 @@ Do not use this playbook when the user is really asking for a system/package wit
9
9
  2. create the related apps in one `app_schema_apply(apps=[...])` call
10
10
  3. keep package ownership on the public `package_id` path instead of a separate attach step
11
11
  4. add same-call relation fields with `target_app_ref` when one new app references another new app
12
+ 5. choose explicit non-template `icon + color` for each new package/app
12
13
 
13
14
  Hierarchy reminder:
14
15
 
@@ -42,6 +43,8 @@ Use this pattern instead:
42
43
 
43
44
  ## Example
44
45
 
46
+ When designing fields, do not add platform system fields such as `数据ID`, `编号`, `申请人`, `申请时间`, `创建人`, `创建时间`, `提交人`, `提交时间`, `更新时间`, `更新人`, `当前流程状态`, `当前处理人`, `当前处理节点`, or `流程标题`. Qingflow provides them automatically; create only business fields.
47
+
45
48
  Create a new package only after the user confirms package creation:
46
49
 
47
50
  ```json
@@ -50,7 +53,8 @@ Create a new package only after the user confirms package creation:
50
53
  "arguments": {
51
54
  "profile": "default",
52
55
  "package_name": "研发项目管理",
53
- "create_if_missing": true
56
+ "create_if_missing": true,
57
+ "icon": {"name": "briefcase", "color": "azure"}
54
58
  }
55
59
  }
56
60
  ```
@@ -76,6 +80,7 @@ Apply schema for a new app:
76
80
  "profile": "default",
77
81
  "app_name": "客户订单",
78
82
  "package_id": 1218950,
83
+ "icon": {"name": "delivery-box-1", "color": "emerald"},
79
84
  "create_if_missing": true,
80
85
  "publish": true,
81
86
  "add_fields": [
@@ -83,7 +88,7 @@ Apply schema for a new app:
83
88
  {"name": "客户名称", "type": "text", "required": true},
84
89
  {"name": "订单封面", "type": "attachment", "as_data_cover": true},
85
90
  {"name": "订单金额", "type": "amount"},
86
- {"name": "状态", "type": "single_select", "options": ["草稿", "进行中", "已完成"], "required": true}
91
+ {"name": "状态", "type": "single_select", "options": [{"label": "草稿"}, {"label": "进行中"}, {"label": "已完成"}], "required": true}
87
92
  ],
88
93
  "update_fields": [],
89
94
  "remove_fields": []
@@ -105,6 +110,7 @@ Apply schema for multiple apps in one call:
105
110
  {
106
111
  "client_key": "customer",
107
112
  "app_name": "客户",
113
+ "icon": {"name": "business-personalcard", "color": "emerald"},
108
114
  "add_fields": [
109
115
  {"name": "客户名称", "type": "text", "required": true, "as_data_title": true}
110
116
  ]
@@ -112,6 +118,7 @@ Apply schema for multiple apps in one call:
112
118
  {
113
119
  "client_key": "order",
114
120
  "app_name": "订单",
121
+ "icon": {"name": "delivery-box-1", "color": "blue"},
115
122
  "add_fields": [
116
123
  {"name": "订单编号", "type": "text", "required": true, "as_data_title": true},
117
124
  {
@@ -4,6 +4,12 @@ Use this reference for builder-side field matching rules in custom buttons and a
4
4
 
5
5
  This is not the same thing as `record_access` analysis filters or normal view `filters`.
6
6
 
7
+ Unified public semantics:
8
+
9
+ - Fixed filters use `field_name + operator + value/values`, for example view `filters` and chart `filters`.
10
+ - Current-record context matches use `target_field + operator + source_field/value`, for example associated resource `match_mappings` and add-data button mappings.
11
+ - Do not handwrite `judgeType`, `judgeValues`, or `matchRules`; the CLI compiles them for the target backend protocol.
12
+
7
13
  ## Where These Rules Apply
8
14
 
9
15
  - `app_custom_buttons_apply.upsert_buttons[].trigger_add_data_config.field_mappings`
@@ -80,6 +86,15 @@ Use `match_mappings` for associated view/report filters:
80
86
 
81
87
  Dynamic conditions use `source_field`; static conditions use `value`.
82
88
 
89
+ Recommended operators are `eq`, `neq`, `in`, `contains`, `gte`, `lte`, `is_empty`, and `not_empty`; aliases such as `equal`, `equals`, `=`, `!=`, `any_of`, `one_of`, and `empty` are accepted. `field_name` and `field` can be used as aliases for `target_field`. Static single-value filters should use `value`; a single-item `values` array is also accepted.
90
+
91
+ Examples:
92
+
93
+ ```json
94
+ {"target_field": "客户ID", "operator": "eq", "source_field": "数据ID"}
95
+ {"target_field": "状态", "operator": "eq", "value": "有效"}
96
+ ```
97
+
83
98
  For an existing associated resource, put the same filter mapping under `patch_resources[].set`:
84
99
 
85
100
  ```json
@@ -19,6 +19,12 @@ Before picking tools, decide which layer the request targets:
19
19
 
20
20
  If the user asks for multiple forms/modules that relate to each other, this is a package-level multi-app task, not a single-app create.
21
21
 
22
+ Use this split consistently:
23
+
24
+ - Complete system/package: `package_apply` first, then one `app_schema_apply(apps=[...])`.
25
+ - Single app: `app_resolve/app_get` first, then app-scoped apply tools.
26
+ - Record/task/data operation: leave builder and use record/task skills.
27
+
22
28
  ## Resolve
23
29
 
24
30
  - `package_get`: read one known package by `package_id`; it reads package `baseInfo` first and may warn `PACKAGE_DETAIL_READ_DEGRADED` when the richer detail endpoint needs package edit/add-app permission. Treat that warning as degraded detail, not as package-read failure.
@@ -48,8 +54,8 @@ These execute normalized patches. Some app apply tools publish by default and st
48
54
  - `app_flow_apply`: replace workflow
49
55
  - `app_views_apply`: use `patch_views` for existing-view parameter replacement; use `upsert_views` for creation or full target config; remove views by key; new views default associated report/view display to visible with `limit_type="all"`
50
56
  - `app_custom_buttons_apply`: use `patch_buttons` for existing-button parameter replacement; use `upsert_buttons` for creation or full target config; configure add-data field mappings/default values; bind buttons to header/detail/list view positions; `placement=list` maps to the backend `INSIDE` row/list button position; merge-mode view configs require `buttons`; use `view_configs[].mode="replace"` or `buttons=[]` to clear a view's custom button bindings. For child-record creation linked to the current source record, map `source_field: "数据ID"` to the target relation field.
51
- - `app_associated_resources_apply`: attach existing BI reports/views to the Qingflow app associated-resource pool and per-view display area; it does not create or edit QingBI report bodies/configs. Permission is split like the backend: resource pool writes use EditAppAuth, while `view_configs` uses the view config/DataManageAuth path. Use `patch_resources` for existing associated-resource parameter replacement; use `upsert_resources` for creation or full target config; `view_configs`, remove, and reorder may reference existing resources by internal `associated_item_id` or by `chart_id`/`chart_key`/`view_key`; use `match_mappings` for associated view/report filters; publishes after successful writes; omit raw `sourceType`, and use `report_source="dataset"` only to attach an existing BI dataset report. Before `upsert_resources`, read `app_get.associated_resources` and reuse an existing matching `target_app_key + view_key/chart_key`; repeated upsert can create duplicates because `client_key` is only valid inside one apply call.
52
- - `app_charts_apply`: create/edit/remove/reorder app-source QingBI report bodies/configs with `dataSourceType=qingflow`; it does not create/edit dataset BI reports and does not attach reports to Qingflow app associated-resource display. Use `patch_charts` for existing-chart parameter replacement; use `upsert_charts` for creation or full target config; supports `target/table` aliases plus QingBI chart types such as `summary`, `columnar`, `area`, `funnel`, `radar`, `scatter`, `dualaxes`, and `map`; charts are immediate-live and do not publish; use `chart_id` when names are not unique
57
+ - `app_associated_resources_apply`: attach existing BI reports/views to the Qingflow app associated-resource pool and per-view display area; it does not create or edit QingBI report bodies/configs. Permission is split like the backend: resource pool writes use EditAppAuth, while `view_configs` uses the view config/DataManageAuth path. Use `patch_resources` for existing associated-resource parameter replacement; use `upsert_resources` for creation or full target config; `view_configs`, remove, and reorder may reference existing resources by internal `associated_item_id` or by `chart_id`/`chart_key`/`view_key`; use `match_mappings` with `target_field + operator + source_field/value` for associated view/report filters; publishes after successful writes; omit raw `sourceType`, and use `report_source="dataset"` only to attach an existing BI dataset report. Before `upsert_resources`, read `app_get.associated_resources` and reuse an existing matching `target_app_key + view_key/chart_key`; repeated upsert can create duplicates because `client_key` is only valid inside one apply call.
58
+ - `app_charts_apply`: create/edit/remove/reorder app-source QingBI report bodies/configs with `dataSourceType=qingflow`; it does not create/edit dataset BI reports and does not attach reports to Qingflow app associated-resource display. Use `filters` with `field_name + operator + value/values`; the tool compiles them to QingBI string judge types. Use `patch_charts` for existing-chart parameter replacement; use `upsert_charts` for creation or full target config; supports `target/table` aliases plus QingBI chart types such as `summary`, `columnar`, `area`, `funnel`, `radar`, `scatter`, `dualaxes`, and `map`; charts are immediate-live and do not publish; use `chart_id` when names are not unique
53
59
  - `portal_apply`: create or replace-update portal pages; use `dash_key` for update mode or `package_id + dash_name` for create mode; create mode only prechecks package add_app, matching backend portal creation; edit mode may omit `sections` for base-info-only updates; when sections are supplied they still use replace semantics
54
60
 
55
61
  For object-level updates, the safe partial syntax is `patch_*` with the object's real selector field plus `set` and optional `unset`. `selector` is only a concept, not a literal key. Examples: `patch_views: [{"view_key": "VIEW_KEY", "set": {...}}]`, `patch_buttons: [{"button_id": 1001, "set": {...}}]`, `patch_resources: [{"associated_item_id": 123, "set": {...}}]`, `patch_charts: [{"chart_id": 456, "set": {...}}]`. The tool reads the current backend config, merges the patch, then submits the full backend payload internally. Do not send a partial `upsert_*` and expect missing required fields to be preserved.
@@ -65,7 +71,7 @@ For object-level updates, the safe partial syntax is `patch_*` with the object's
65
71
  - Create a brand new package, then create one app in it:
66
72
  `package_apply(create_if_missing=true) -> app_schema_apply`
67
73
  - Create a brand new multi-app system/package:
68
- `package_apply(create_if_missing=true) -> app_schema_apply(apps[])`
74
+ `package_apply(create_if_missing=true) -> app_schema_apply(apps[])`; use `apps[].client_key` plus `target_app_ref`, or `target_app` when referencing by app name
69
75
  - Update fields on an existing app:
70
76
  `app_resolve -> app_get_fields -> app_schema_apply`
71
77
  - Tidy layout:
@@ -97,3 +103,4 @@ For object-level updates, the safe partial syntax is `patch_*` with the object's
97
103
  - Do not omit assignees on approval/fill/copy nodes
98
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`
99
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`.
@@ -10,6 +10,8 @@ Use this when the app already exists and the task is only about fields.
10
10
 
11
11
  ## Example
12
12
 
13
+ Do not add platform system fields as form controls: `数据ID`, `编号`, `申请人`, `申请时间`, `创建人`, `创建时间`, `提交人`, `提交时间`, `更新时间`, `更新人`, `当前流程状态`, `当前处理人`, `当前处理节点`, `流程标题`. They are generated by Qingflow; use readback/list fields when you need them.
14
+
13
15
  Read current fields first:
14
16
 
15
17
  ```json
@@ -70,3 +72,4 @@ Treat it as uncertain write state. Read back with `app_get_fields` before retryi
70
72
  - `checkbox -> multi_select`
71
73
  - `as_data_title: true` marks the app data title; the final form must have exactly one top-level data title field
72
74
  - `as_data_cover: true` marks the app data cover; the cover is optional and must be a top-level `attachment` field
75
+ - `数据ID`, `编号`, applicant/creation/update time, and workflow status fields are system-generated and must not appear in `add_fields`
@@ -28,16 +28,18 @@ Canonical rules before any example:
28
28
  - Always use `columns`
29
29
  - Do not emit `column_names`
30
30
  - Treat `fields` only as a legacy alias the MCP may normalize, not as the preferred shape
31
- - Use `filters` with canonical keys `field_name`, `operator`, `value`/`values`
31
+ - Use `filters` with the unified fixed-filter DSL: `field_name`, `operator`, `value`/`values`; do not write raw `judgeType` / `judgeValues`
32
32
  - Use `query_conditions` for the frontend query panel. Do not put query-panel fields into `filters`.
33
+ - Do not create views named `全部数据`, `我的数据`, `我发起的`, `待办`, `已办`, or `抄送`. These are built-in default/system views. New views must use business-specific names such as `订单台账视图`, `客户跟进视图`, or `逾期任务看板`.
34
+ - To change a built-in default/system view, first read the existing raw `view_key` from `app_get.views` or `app_get_views`, then use `patch_views` or a full `upsert_views` item with that `view_key`.
33
35
  - New views created by `app_views_apply` default the frontend associated report/view area to visible with `limit_type="all"`. Existing views preserve their current associated-resource display unless `associated_resources` is explicitly patched.
34
- - Use `app_associated_resources_apply` for the associated report/view resource pool, selected resources, and match rules. Permission is split like the backend: resource pool writes use EditAppAuth, while `view_configs` uses the view config/DataManageAuth path. `associated_item_id` is the internal associated-resource id from `app_get.associated_resources`; `view_configs`, remove, and reorder may also pass an existing resource's `chart_id`/`chart_key`/`view_key`, which the tool resolves to the internal id. Do not write backend raw `sourceType`; reports default to BI app reports, and dataset reports use `report_source="dataset"`. Use `match_mappings` for associated view/report filters; read `match-rules.md` if field type compatibility is unclear.
36
+ - Use `app_associated_resources_apply` for the associated report/view resource pool, selected resources, and match rules. Permission is split like the backend: resource pool writes use EditAppAuth, while `view_configs` uses the view config/DataManageAuth path. `associated_item_id` is the internal associated-resource id from `app_get.associated_resources`; `view_configs`, remove, and reorder may also pass an existing resource's `chart_id`/`chart_key`/`view_key`, which the tool resolves to the internal id. Do not write backend raw `sourceType`; reports default to BI app reports, and dataset reports use `report_source="dataset"`. Use `match_mappings` with `target_field + operator + source_field/value` for associated view/report filters; read `match-rules.md` if field type compatibility is unclear.
35
37
  - For gantt, use `start_field`, `end_field`, and optionally `title_field`
36
38
  - If `app_get.views` or `app_get_views` shows duplicate view names, include `view_key` in `upsert_views[]` and update that exact target
37
39
  - Builder view writes always use the raw `view_key` from `app_get.views[].view_key`, such as `emsrao25rs02`. Do not pass `custom:emsrao25rs02`; that prefixed form is only for record-data `view_id`.
38
40
  - For an existing view, prefer `patch_views` for parameter replacement. Do not send a partial `upsert_views` object such as only `name/type/query_conditions`; backend view saves require other type-specific fields, and the patch path preserves them for you.
39
41
 
40
- Apply a default table view:
42
+ Apply a business table view:
41
43
 
42
44
  ```json
43
45
  {
@@ -48,7 +50,7 @@ Apply a default table view:
48
50
  "publish": true,
49
51
  "upsert_views": [
50
52
  {
51
- "name": "全部订单",
53
+ "name": "订单台账视图",
52
54
  "view_key": "VIEW_KEY_IF_DUPLICATE_NAMES_EXIST",
53
55
  "type": "table",
54
56
  "columns": ["订单编号", "客户名称", "订单金额", "状态"]
@@ -79,6 +79,7 @@ Without `record_id` / `workflow_node_id` / `fields-file`, the result is a static
79
79
  16. If post-write detail context matters, read `record_get.fields[]`, `media_assets.items[].local_path`, `file_assets.items[].local_path`, `file_assets.items[].extraction.text_path`, and `semantic_context`; `record_get` follows the frontend storage cookie redirect path for Qingflow attachments, so prefer local paths over remote URLs and do not expect legacy flat record shapes
80
80
  17. Treat nested schema shape as guidance, not a brittle contract; do not hard-code transient implementation details like optional nested `field_id` shape when composing inserts
81
81
  18. For `partial_success`, read `created_record_ids`, then repair only the failed `items[].row_number` using `failed_fields`; never retry the whole batch after any row has `write_executed=true`
82
+ 19. Do not put Qingflow system fields in `fields`: `数据ID`, `编号`, `申请人`, `申请时间`, `创建人`, `创建时间`, `提交人`, `提交时间`, `更新时间`, `更新人`, `当前流程状态`, `当前处理人`, `当前处理节点`, `流程标题`. They are generated by the platform and can be read after creation, not manually inserted.
82
83
 
83
84
  ## Field Notes
84
85
 
@@ -120,6 +121,7 @@ qingflow --json record insert --app-key APP_KEY --items-file records.json
120
121
 
121
122
  - Do not skip `record_insert_schema_get`
122
123
  - Do not invent missing required fields
124
+ - Do not fill platform system fields such as `数据ID`, `编号`, `申请人`, `创建时间`, or `更新时间`
123
125
  - Do not flatten subtable leaf fields to the top level
124
126
  - Do not pre-query or silently guess member / department / relation ids when a natural string is enough
125
127
  - Do not retry a whole batch after `created_record_ids` is non-empty
@@ -62,7 +62,7 @@ Use one of these two modes:
62
62
  2. Discover the exact target with `task_list`
63
63
  3. If the target or action requirements are ambiguous, read `task_get`; otherwise go straight to `task_action_execute`
64
64
  4. Execute through `task_action_execute`
65
- 5. After actions, report the exact `app_key`, `record_id`, `workflow_node_id`, executed action, and any warnings
65
+ 5. After actions, report whether it succeeded, the `task_id`, the executed action, the final route/status, and any warnings
66
66
 
67
67
  ## Task-Center Rules
68
68
 
@@ -111,7 +111,7 @@ Use one of these two modes:
111
111
  - Do not execute `task_action_execute` until the user explicitly confirms the chosen action
112
112
  - Exception: if the user has already explicitly authorized a concrete action on exact targets, you may execute directly after exact target resolution
113
113
  - Avoid actions on ambiguous tasks or records
114
- - Summarize the final action and the exact `app_key / record_id / workflow_node_id`
114
+ - Summarize the final action by `task_id`; include `app_key`, `record_id`, or `workflow_node_id` only as read-only context when the tool returns them, not as the action locator
115
115
  - `reject` requires `payload.audit_feedback`
116
116
  - For approve/reject, trust the current task detail or an explicit frontend-provided `formId`; app baseInfo is only a fallback. A baseInfo `40002` is not final task-action denial when `formId` is already known.
117
117
  - `save_only` requires non-empty `fields` and is only available when the backend exposes editable fields for the current node
@@ -1259,11 +1259,30 @@ class CustomButtonFieldMappingPatch(StrictModel):
1259
1259
 
1260
1260
 
1261
1261
  class FieldMatchMappingPatch(StrictModel):
1262
- target_field: Any = Field(validation_alias=AliasChoices("target_field", "targetField", "target", "field"))
1262
+ target_field: Any = Field(
1263
+ validation_alias=AliasChoices("target_field", "targetField", "target", "field", "field_name", "fieldName")
1264
+ )
1263
1265
  source_field: Any | None = Field(default=None, validation_alias=AliasChoices("source_field", "sourceField", "source"))
1264
1266
  value: Any | None = Field(default=None, validation_alias=AliasChoices("value", "static_value", "staticValue"))
1265
1267
  operator: str = Field(default="eq", validation_alias=AliasChoices("operator", "op", "judge_type", "judgeType"))
1266
1268
 
1269
+ @model_validator(mode="before")
1270
+ @classmethod
1271
+ def normalize_semantic_aliases(cls, value: Any) -> Any:
1272
+ if not isinstance(value, dict):
1273
+ return value
1274
+ payload = dict(value)
1275
+ has_static_value = any(key in payload for key in ("value", "static_value", "staticValue"))
1276
+ if "values" in payload:
1277
+ values = payload.pop("values")
1278
+ if has_static_value:
1279
+ return payload
1280
+ if isinstance(values, list) and len(values) == 1:
1281
+ payload["value"] = values[0]
1282
+ else:
1283
+ payload["value"] = values
1284
+ return payload
1285
+
1267
1286
  @model_validator(mode="after")
1268
1287
  def validate_shape(self) -> "FieldMatchMappingPatch":
1269
1288
  has_source = self.source_field is not None and str(self.source_field).strip() != ""
@@ -2292,6 +2311,8 @@ def _normalize_field_payload(value: Any) -> Any:
2292
2311
  payload = dict(value)
2293
2312
  if "fields" in payload and "subfields" not in payload:
2294
2313
  payload["subfields"] = payload.pop("fields")
2314
+ if "options" in payload:
2315
+ payload["options"] = _normalize_field_options(payload.get("options"))
2295
2316
  raw_type = payload.get("type")
2296
2317
  if isinstance(raw_type, int):
2297
2318
  normalized_from_id = FIELD_TYPE_ID_ALIASES.get(raw_type)
@@ -2324,6 +2345,34 @@ def _normalize_field_payload(value: Any) -> Any:
2324
2345
  return payload
2325
2346
 
2326
2347
 
2348
+ def _normalize_field_options(value: Any) -> Any:
2349
+ if not isinstance(value, list):
2350
+ return value
2351
+ normalized: list[str] = []
2352
+ for item in value:
2353
+ if isinstance(item, str):
2354
+ normalized.append(item)
2355
+ continue
2356
+ if isinstance(item, (int, float, bool)):
2357
+ normalized.append(str(item))
2358
+ continue
2359
+ if isinstance(item, dict):
2360
+ label = (
2361
+ item.get("label")
2362
+ or item.get("name")
2363
+ or item.get("title")
2364
+ or item.get("value")
2365
+ or item.get("text")
2366
+ or item.get("optValue")
2367
+ or item.get("optName")
2368
+ )
2369
+ if label is not None:
2370
+ normalized.append(str(label))
2371
+ continue
2372
+ normalized.append(str(item))
2373
+ return normalized
2374
+
2375
+
2327
2376
  def _slugify_title(title: str) -> str:
2328
2377
  normalized = "".join(ch.lower() if ch.isalnum() else "_" for ch in str(title or ""))
2329
2378
  collapsed = "_".join(part for part in normalized.split("_") if part)
@@ -15049,14 +15049,14 @@ def _build_public_chart_filter_matrix(
15049
15049
  return []
15050
15050
  group: list[dict[str, Any]] = []
15051
15051
  judge_map = {
15052
- ViewFilterOperator.eq.value: 0,
15053
- ViewFilterOperator.neq.value: 1,
15054
- ViewFilterOperator.gte.value: 5,
15055
- ViewFilterOperator.lte.value: 7,
15056
- ViewFilterOperator.in_.value: 9,
15057
- ViewFilterOperator.contains.value: 19,
15058
- ViewFilterOperator.is_empty.value: 15,
15059
- ViewFilterOperator.not_empty.value: 16,
15052
+ ViewFilterOperator.eq.value: "equal",
15053
+ ViewFilterOperator.neq.value: "unequal",
15054
+ ViewFilterOperator.gte.value: "greaterOrEqual",
15055
+ ViewFilterOperator.lte.value: "lessOrEqual",
15056
+ ViewFilterOperator.in_.value: "anyMatch",
15057
+ ViewFilterOperator.contains.value: "include",
15058
+ ViewFilterOperator.is_empty.value: "isNull",
15059
+ ViewFilterOperator.not_empty.value: "notNull",
15060
15060
  }
15061
15061
  for rule in rules:
15062
15062
  qingbi_field = _resolve_qingbi_chart_field(
@@ -15074,7 +15074,7 @@ def _build_public_chart_filter_matrix(
15074
15074
  "fieldId": field_id,
15075
15075
  "fieldName": qingbi_field.get("fieldName") or form_field.get("name") or field_id,
15076
15076
  "fieldType": qingbi_field.get("fieldType") or _qingbi_field_type_from_public_field(str(form_field.get("type") or "")),
15077
- "judgeType": judge_map.get(operator, 0),
15077
+ "judgeType": judge_map.get(operator, "equal"),
15078
15078
  "judgeValues": values,
15079
15079
  "matchType": 1,
15080
15080
  }
@@ -4,6 +4,7 @@ import argparse
4
4
 
5
5
  from ..context import CliContext
6
6
  from .common import load_list_arg, load_object_arg, raise_config_error, require_list_arg
7
+ from ..json_io import load_json_value
7
8
 
8
9
 
9
10
  def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
@@ -476,26 +477,30 @@ def _handle_chart_get(args: argparse.Namespace, context: CliContext) -> dict:
476
477
 
477
478
 
478
479
  def _handle_schema_apply(args: argparse.Namespace, context: CliContext) -> dict:
479
- apps = load_list_arg(args.apps_file, option_name="--apps-file")
480
+ apps_payload = _load_apps_file_arg(args.apps_file)
481
+ apps = apps_payload["apps"]
482
+ package_id = args.package_id
483
+ if package_id is None and apps_payload.get("package_id") is not None:
484
+ package_id = int(apps_payload["package_id"])
480
485
  if args.apps_file:
481
486
  if not apps:
482
487
  raise_config_error(
483
488
  "schema apply multi-app mode requires a non-empty --apps-file.",
484
- fix_hint="Pass a JSON array with at least one app item.",
489
+ fix_hint="Pass a JSON array, or a JSON object like {\"package_id\":1001,\"apps\":[...]} with at least one app item.",
485
490
  )
486
491
  if args.app_key or args.app_name or args.app_title or args.add_fields_file or args.update_fields_file or args.remove_fields_file:
487
492
  raise_config_error(
488
493
  "schema apply multi-app mode accepts --package-id/--create-if-missing plus --apps-file only.",
489
494
  fix_hint="Use `--apps-file` for batch mode, or remove `--apps-file` and use the single-app arguments.",
490
495
  )
491
- if args.package_id is None:
496
+ if package_id is None:
492
497
  raise_config_error(
493
498
  "schema apply multi-app mode requires --package-id.",
494
- fix_hint="Pass `--package-id` and app names inside --apps-file.",
499
+ fix_hint="Pass `--package-id`, or put `package_id` at the top level of --apps-file. `package_name` alone does not create the package here; run `builder package apply` first.",
495
500
  )
496
501
  return context.builder.app_schema_apply(
497
502
  profile=args.profile,
498
- package_id=args.package_id,
503
+ package_id=package_id,
499
504
  visibility=load_object_arg(args.visibility_file, option_name="--visibility-file"),
500
505
  create_if_missing=bool(args.create_if_missing),
501
506
  publish=bool(args.publish),
@@ -537,6 +542,29 @@ def _handle_schema_apply(args: argparse.Namespace, context: CliContext) -> dict:
537
542
  )
538
543
 
539
544
 
545
+ def _load_apps_file_arg(path: str | None) -> dict[str, object]:
546
+ if not path:
547
+ return {"apps": []}
548
+ payload = load_json_value(path, option_name="--apps-file")
549
+ if isinstance(payload, list):
550
+ return {"apps": payload}
551
+ if isinstance(payload, dict):
552
+ apps = payload.get("apps")
553
+ if not isinstance(apps, list):
554
+ raise_config_error(
555
+ "--apps-file JSON object requires an apps array.",
556
+ fix_hint="Use {\"package_id\":1001,\"apps\":[...]} or pass a raw JSON array.",
557
+ )
558
+ result: dict[str, object] = {"apps": apps}
559
+ if payload.get("package_id") is not None:
560
+ result["package_id"] = payload.get("package_id")
561
+ return result
562
+ raise_config_error(
563
+ "--apps-file must be a JSON array or an object containing apps.",
564
+ fix_hint="Use [{...}] or {\"package_id\":1001,\"apps\":[...]}",
565
+ )
566
+
567
+
540
568
  def _handle_layout_apply(args: argparse.Namespace, context: CliContext) -> dict:
541
569
  return context.builder.app_layout_apply(
542
570
  profile=args.profile,
@@ -146,8 +146,11 @@ class AiBuilderTools(ToolBase):
146
146
  package_id: int | None = None,
147
147
  package_name: str | None = None,
148
148
  create_if_missing: bool = False,
149
- icon: str | None = None,
149
+ icon: str | JSONObject | None = None,
150
150
  color: str | None = None,
151
+ icon_name: str | None = None,
152
+ icon_color: str | None = None,
153
+ icon_config: JSONObject | None = None,
151
154
  visibility: JSONObject | None = None,
152
155
  items: list[dict] | None = None,
153
156
  allow_detach: bool = False,
@@ -159,6 +162,9 @@ class AiBuilderTools(ToolBase):
159
162
  create_if_missing=create_if_missing,
160
163
  icon=icon,
161
164
  color=color,
165
+ icon_name=icon_name,
166
+ icon_color=icon_color,
167
+ icon_config=icon_config,
162
168
  visibility=visibility,
163
169
  items=items or None,
164
170
  allow_detach=allow_detach,
@@ -364,8 +370,11 @@ class AiBuilderTools(ToolBase):
364
370
  package_id: int | None = None,
365
371
  app_name: str = "",
366
372
  app_title: str = "",
367
- icon: str = "",
373
+ icon: str | JSONObject = "",
368
374
  color: str = "",
375
+ icon_name: str | None = None,
376
+ icon_color: str | None = None,
377
+ icon_config: JSONObject | None = None,
369
378
  visibility: JSONObject | None = None,
370
379
  create_if_missing: bool = False,
371
380
  publish: bool = True,
@@ -423,6 +432,9 @@ class AiBuilderTools(ToolBase):
423
432
  app_title=app_title,
424
433
  icon=icon,
425
434
  color=color,
435
+ icon_name=icon_name,
436
+ icon_color=icon_color,
437
+ icon_config=icon_config,
426
438
  visibility=visibility,
427
439
  create_if_missing=create_if_missing,
428
440
  publish=publish,
@@ -733,13 +745,23 @@ class AiBuilderTools(ToolBase):
733
745
  package_id: int | None = None,
734
746
  package_name: str | None = None,
735
747
  create_if_missing: bool = False,
736
- icon: str | None = None,
748
+ icon: str | JSONObject | None = None,
737
749
  color: str | None = None,
750
+ icon_name: str | None = None,
751
+ icon_color: str | None = None,
752
+ icon_config: JSONObject | None = None,
738
753
  visibility: JSONObject | None = None,
739
754
  items: list[dict] | None = None,
740
755
  allow_detach: bool = False,
741
756
  ) -> JSONObject:
742
757
  """执行分组与包相关逻辑。"""
758
+ icon, color = _normalize_builder_icon_args(
759
+ icon=icon,
760
+ color=color,
761
+ icon_name=icon_name,
762
+ icon_color=icon_color,
763
+ icon_config=icon_config,
764
+ )
743
765
  visibility_patch = None
744
766
  if visibility is not None:
745
767
  try:
@@ -1459,6 +1481,22 @@ class AiBuilderTools(ToolBase):
1459
1481
  remove_fields: list[JSONObject],
1460
1482
  ) -> JSONObject:
1461
1483
  """执行应用相关逻辑。"""
1484
+ system_field_failure = _reserved_system_field_name_failure(
1485
+ tool_name="app_schema_plan",
1486
+ profile=profile,
1487
+ app_key=app_key,
1488
+ package_id=package_id,
1489
+ app_name=app_name,
1490
+ icon=icon,
1491
+ color=color,
1492
+ visibility=visibility,
1493
+ create_if_missing=create_if_missing,
1494
+ add_fields=add_fields,
1495
+ update_fields=update_fields,
1496
+ remove_fields=remove_fields,
1497
+ )
1498
+ if system_field_failure is not None:
1499
+ return system_field_failure
1462
1500
  try:
1463
1501
  request = SchemaPlanRequest.model_validate(
1464
1502
  {
@@ -1609,6 +1647,15 @@ class AiBuilderTools(ToolBase):
1609
1647
  preset: str | None = None,
1610
1648
  ) -> JSONObject:
1611
1649
  """执行应用相关逻辑。"""
1650
+ reserved_failure = _reserved_system_view_name_failure(
1651
+ tool_name="app_views_plan",
1652
+ profile=profile,
1653
+ app_key=app_key,
1654
+ upsert_views=upsert_views,
1655
+ remove_views=remove_views,
1656
+ )
1657
+ if reserved_failure is not None:
1658
+ return reserved_failure
1612
1659
  try:
1613
1660
  request = ViewsPlanRequest.model_validate(
1614
1661
  {
@@ -1650,8 +1697,11 @@ class AiBuilderTools(ToolBase):
1650
1697
  package_id: int | None = None,
1651
1698
  app_name: str = "",
1652
1699
  app_title: str = "",
1653
- icon: str = "",
1700
+ icon: str | JSONObject = "",
1654
1701
  color: str = "",
1702
+ icon_name: str | None = None,
1703
+ icon_color: str | None = None,
1704
+ icon_config: JSONObject | None = None,
1655
1705
  visibility: JSONObject | None = None,
1656
1706
  create_if_missing: bool = False,
1657
1707
  publish: bool = True,
@@ -1661,7 +1711,15 @@ class AiBuilderTools(ToolBase):
1661
1711
  apps: list[JSONObject] | None = None,
1662
1712
  ) -> JSONObject:
1663
1713
  """执行应用相关逻辑。"""
1714
+ icon, color = _normalize_builder_icon_args(
1715
+ icon=icon,
1716
+ color=color,
1717
+ icon_name=icon_name,
1718
+ icon_color=icon_color,
1719
+ icon_config=icon_config,
1720
+ )
1664
1721
  if apps:
1722
+ apps = [_normalize_schema_app_item(item) if isinstance(item, dict) else item for item in apps]
1665
1723
  result = self._app_schema_apply_multi(
1666
1724
  profile=profile,
1667
1725
  package_id=package_id,
@@ -1725,6 +1783,17 @@ class AiBuilderTools(ToolBase):
1725
1783
  }
1726
1784
  if visibility is not None:
1727
1785
  normalized_args["visibility"] = deepcopy(visibility)
1786
+ system_field_failure = _reserved_system_field_name_failure_for_apps(
1787
+ tool_name="app_schema_apply",
1788
+ profile=profile,
1789
+ package_id=package_id,
1790
+ visibility=visibility,
1791
+ create_if_missing=create_if_missing,
1792
+ publish=publish,
1793
+ apps=apps,
1794
+ )
1795
+ if system_field_failure is not None:
1796
+ return system_field_failure
1728
1797
  if package_id is None:
1729
1798
  return _config_failure(
1730
1799
  tool_name="app_schema_apply",
@@ -1801,6 +1870,7 @@ class AiBuilderTools(ToolBase):
1801
1870
  )
1802
1871
 
1803
1872
  client_key_to_app_key: dict[str, str] = {}
1873
+ app_name_to_app_key: dict[str, str] = {}
1804
1874
  created_app_keys: list[str] = []
1805
1875
  results: list[JSONObject] = []
1806
1876
  any_write_executed = False
@@ -1864,11 +1934,14 @@ class AiBuilderTools(ToolBase):
1864
1934
  created_app_keys.append(resolved_key)
1865
1935
  if client_key:
1866
1936
  client_key_to_app_key[client_key] = resolved_key
1937
+ resolved_name = str(public_shell.get("app_name_after") or public_shell.get("app_name") or app_name or "").strip()
1938
+ if resolved_name:
1939
+ app_name_to_app_key[resolved_name] = resolved_key
1867
1940
  results.append({
1868
1941
  "index": index,
1869
1942
  "row_number": index + 1,
1870
1943
  "client_key": client_key or None,
1871
- "app_name": str(public_shell.get("app_name_after") or public_shell.get("app_name") or app_name or "").strip() or None,
1944
+ "app_name": resolved_name or None,
1872
1945
  "app_key": resolved_key,
1873
1946
  "status": "shell_ready",
1874
1947
  "created": bool(public_shell.get("created")),
@@ -1888,7 +1961,7 @@ class AiBuilderTools(ToolBase):
1888
1961
  item = deepcopy(raw_item)
1889
1962
  app_key = str(existing.get("app_key") or "").strip()
1890
1963
  try:
1891
- compiled_item = _compile_multi_app_schema_item_refs(item, client_key_to_app_key)
1964
+ compiled_item = _compile_multi_app_schema_item_refs(item, client_key_to_app_key, app_name_to_app_key)
1892
1965
  except ValueError as error:
1893
1966
  final_items.append({
1894
1967
  **{key: existing.get(key) for key in ("index", "row_number", "client_key", "app_name", "app_key", "created")},
@@ -2012,6 +2085,23 @@ class AiBuilderTools(ToolBase):
2012
2085
  ) -> JSONObject:
2013
2086
  """执行内部辅助逻辑。"""
2014
2087
  effective_app_name = app_name or app_title
2088
+ system_field_failure = _reserved_system_field_name_failure(
2089
+ tool_name="app_schema_apply",
2090
+ profile=profile,
2091
+ app_key=app_key,
2092
+ package_id=package_id,
2093
+ app_name=effective_app_name,
2094
+ icon=icon,
2095
+ color=color,
2096
+ visibility=visibility,
2097
+ create_if_missing=create_if_missing,
2098
+ publish=publish,
2099
+ add_fields=add_fields,
2100
+ update_fields=update_fields,
2101
+ remove_fields=remove_fields,
2102
+ )
2103
+ if system_field_failure is not None:
2104
+ return system_field_failure
2015
2105
  icon_failure = _validate_workspace_icon_for_builder(
2016
2106
  tool_name="app_schema_apply",
2017
2107
  icon=icon,
@@ -2343,6 +2433,17 @@ class AiBuilderTools(ToolBase):
2343
2433
  remove_views: list[str],
2344
2434
  ) -> JSONObject:
2345
2435
  """执行内部辅助逻辑。"""
2436
+ reserved_failure = _reserved_system_view_name_failure(
2437
+ tool_name="app_views_apply",
2438
+ profile=profile,
2439
+ app_key=app_key,
2440
+ publish=publish,
2441
+ upsert_views=upsert_views,
2442
+ patch_views=patch_views,
2443
+ remove_views=remove_views,
2444
+ )
2445
+ if reserved_failure is not None:
2446
+ return reserved_failure
2346
2447
  if patch_views:
2347
2448
  try:
2348
2449
  parsed_views = [ViewUpsertPatch.model_validate(item) for item in (upsert_views or [])]
@@ -2417,7 +2518,7 @@ class AiBuilderTools(ToolBase):
2417
2518
  "profile": profile,
2418
2519
  "app_key": str(plan_args.get("app_key") or app_key),
2419
2520
  "publish": publish,
2420
- "upsert_views": plan_args.get("upsert_views") or [{"name": "全部数据", "type": "table", "columns": ["字段A"]}],
2521
+ "upsert_views": plan_args.get("upsert_views") or [{"name": "业务台账视图", "type": "table", "columns": ["字段A"]}],
2421
2522
  "remove_views": plan_args.get("remove_views") or [],
2422
2523
  },
2423
2524
  },
@@ -2786,8 +2887,58 @@ def _multi_app_item_failure(index: int, item: object, error_code: str, message:
2786
2887
  }
2787
2888
 
2788
2889
 
2789
- def _compile_multi_app_schema_item_refs(item: JSONObject, client_key_to_app_key: dict[str, str]) -> JSONObject:
2890
+ def _normalize_builder_icon_args(
2891
+ *,
2892
+ icon: str | JSONObject | None,
2893
+ color: str | None,
2894
+ icon_name: str | None = None,
2895
+ icon_color: str | None = None,
2896
+ icon_config: JSONObject | None = None,
2897
+ ) -> tuple[str | None, str | None]:
2898
+ payloads: list[JSONObject] = []
2899
+ if isinstance(icon_config, dict):
2900
+ payloads.append(icon_config)
2901
+ if isinstance(icon, dict):
2902
+ payloads.append(icon)
2903
+
2904
+ normalized_icon = None if isinstance(icon, dict) else icon
2905
+ normalized_color = color
2906
+ for payload in payloads:
2907
+ normalized_icon = normalized_icon or payload.get("icon") or payload.get("icon_name") or payload.get("iconName") or payload.get("name")
2908
+ normalized_color = normalized_color or payload.get("color") or payload.get("icon_color") or payload.get("iconColor")
2909
+ normalized_icon = normalized_icon or icon_name
2910
+ normalized_color = normalized_color or icon_color
2911
+ return (
2912
+ str(normalized_icon).strip() if normalized_icon is not None else None,
2913
+ str(normalized_color).strip() if normalized_color is not None else None,
2914
+ )
2915
+
2916
+
2917
+ def _normalize_schema_app_item(item: JSONObject) -> JSONObject:
2918
+ normalized = deepcopy(item)
2919
+ icon, color = _normalize_builder_icon_args(
2920
+ icon=normalized.get("icon"),
2921
+ color=normalized.get("color"),
2922
+ icon_name=normalized.get("icon_name") or normalized.get("iconName"),
2923
+ icon_color=normalized.get("icon_color") or normalized.get("iconColor"),
2924
+ icon_config=normalized.get("icon_config") or normalized.get("iconConfig"),
2925
+ )
2926
+ if icon:
2927
+ normalized["icon"] = icon
2928
+ if color:
2929
+ normalized["color"] = color
2930
+ for key in ("icon_name", "iconName", "icon_color", "iconColor", "icon_config", "iconConfig"):
2931
+ normalized.pop(key, None)
2932
+ return normalized
2933
+
2934
+
2935
+ def _compile_multi_app_schema_item_refs(
2936
+ item: JSONObject,
2937
+ client_key_to_app_key: dict[str, str],
2938
+ app_name_to_app_key: dict[str, str] | None = None,
2939
+ ) -> JSONObject:
2790
2940
  compiled = deepcopy(item)
2941
+ app_name_to_app_key = app_name_to_app_key or {}
2791
2942
 
2792
2943
  def visit(value):
2793
2944
  if isinstance(value, list):
@@ -2807,6 +2958,13 @@ def _compile_multi_app_schema_item_refs(item: JSONObject, client_key_to_app_key:
2807
2958
  if not target_app_key:
2808
2959
  raise ValueError(f"target_app_ref '{ref_key}' did not match any apps[].client_key")
2809
2960
  payload["target_app_key"] = target_app_key
2961
+ target_app = payload.pop("target_app", None) or payload.pop("targetApp", None)
2962
+ if target_app is not None and not str(payload.get("target_app_key") or "").strip():
2963
+ target_name = str(target_app or "").strip()
2964
+ target_app_key = app_name_to_app_key.get(target_name)
2965
+ if not target_app_key:
2966
+ raise ValueError(f"target_app '{target_name}' did not match any apps[].app_name in the same call")
2967
+ payload["target_app_key"] = target_app_key
2810
2968
  return payload
2811
2969
 
2812
2970
  return visit(compiled)
@@ -2855,7 +3013,7 @@ def _contains_multi_app_target_ref(value: object) -> bool:
2855
3013
  if not isinstance(value, dict):
2856
3014
  return False
2857
3015
  for key, entry in value.items():
2858
- if key in {"target_app_ref", "targetAppRef", "target_app_client_key", "targetAppClientKey"}:
3016
+ if key in {"target_app_ref", "targetAppRef", "target_app_client_key", "targetAppClientKey", "target_app", "targetApp"}:
2859
3017
  return True
2860
3018
  if _contains_multi_app_target_ref(entry):
2861
3019
  return True
@@ -2968,6 +3126,197 @@ def _visibility_validation_failure(
2968
3126
  return result
2969
3127
 
2970
3128
 
3129
+ _RESERVED_SYSTEM_FIELD_NAMES = {
3130
+ "数据ID",
3131
+ "编号",
3132
+ "申请人",
3133
+ "申请时间",
3134
+ "创建人",
3135
+ "创建时间",
3136
+ "提交人",
3137
+ "提交时间",
3138
+ "更新时间",
3139
+ "更新人",
3140
+ "当前流程状态",
3141
+ "当前处理人",
3142
+ "当前处理节点",
3143
+ "流程标题",
3144
+ }
3145
+
3146
+
3147
+ def _field_patch_name(item: object) -> str:
3148
+ if not isinstance(item, dict):
3149
+ return ""
3150
+ value = item.get("name")
3151
+ if value is None:
3152
+ value = item.get("title")
3153
+ if value is None:
3154
+ value = item.get("field_name")
3155
+ if value is None:
3156
+ value = item.get("fieldName")
3157
+ return str(value or "").strip()
3158
+
3159
+
3160
+ def _reserved_system_field_name_failure(
3161
+ *,
3162
+ tool_name: str,
3163
+ profile: str,
3164
+ app_key: str = "",
3165
+ package_id: int | None = None,
3166
+ app_name: str = "",
3167
+ icon: str | None = None,
3168
+ color: str | None = None,
3169
+ visibility: JSONObject | None = None,
3170
+ create_if_missing: bool = False,
3171
+ publish: bool | None = None,
3172
+ add_fields: list[JSONObject] | None = None,
3173
+ update_fields: list[JSONObject] | None = None,
3174
+ remove_fields: list[JSONObject] | None = None,
3175
+ app_index: int | None = None,
3176
+ ) -> JSONObject | None:
3177
+ for index, item in enumerate(add_fields or []):
3178
+ name = _field_patch_name(item)
3179
+ if name not in _RESERVED_SYSTEM_FIELD_NAMES:
3180
+ continue
3181
+ suggested_add_fields = [field for field in (add_fields or []) if _field_patch_name(field) not in _RESERVED_SYSTEM_FIELD_NAMES]
3182
+ arguments: JSONObject = {
3183
+ "profile": profile,
3184
+ "app_key": app_key,
3185
+ "package_id": package_id,
3186
+ "app_name": app_name,
3187
+ "icon": icon or "",
3188
+ "color": color or "",
3189
+ "visibility": visibility,
3190
+ "create_if_missing": create_if_missing,
3191
+ "add_fields": suggested_add_fields,
3192
+ "update_fields": update_fields or [],
3193
+ "remove_fields": remove_fields or [],
3194
+ }
3195
+ if publish is not None:
3196
+ arguments["publish"] = publish
3197
+ return _config_failure(
3198
+ tool_name=tool_name,
3199
+ error_code="RESERVED_SYSTEM_FIELD_NAME",
3200
+ message=f"add_fields[{index}].name uses built-in system field name '{name}'",
3201
+ fix_hint="Do not create form fields named 数据ID, 编号, 申请人, 申请时间, 创建人, 创建时间, 提交人, 提交时间, 更新时间, 更新人, 当前流程状态, 当前处理人, 当前处理节点, or 流程标题. These fields are provided by Qingflow. Remove them from add_fields; only reference them where the tool explicitly supports system fields, such as button source_field 数据ID.",
3202
+ details={
3203
+ "reserved_field_names": sorted(_RESERVED_SYSTEM_FIELD_NAMES),
3204
+ "blocked_index": index,
3205
+ "blocked_name": name,
3206
+ "app_index": app_index,
3207
+ },
3208
+ suggested_next_call={"tool_name": tool_name, "arguments": arguments},
3209
+ )
3210
+ return None
3211
+
3212
+
3213
+ def _reserved_system_field_name_failure_for_apps(
3214
+ *,
3215
+ tool_name: str,
3216
+ profile: str,
3217
+ package_id: int | None,
3218
+ visibility: JSONObject | None,
3219
+ create_if_missing: bool,
3220
+ publish: bool,
3221
+ apps: list[JSONObject],
3222
+ ) -> JSONObject | None:
3223
+ for index, item in enumerate(apps or []):
3224
+ if not isinstance(item, dict):
3225
+ continue
3226
+ failure = _reserved_system_field_name_failure(
3227
+ tool_name=tool_name,
3228
+ profile=profile,
3229
+ app_key=str(item.get("app_key") or item.get("appKey") or ""),
3230
+ package_id=package_id,
3231
+ app_name=str(item.get("app_name") or item.get("appName") or ""),
3232
+ icon=str(item.get("icon") or ""),
3233
+ color=str(item.get("color") or ""),
3234
+ visibility=item.get("visibility") if isinstance(item.get("visibility"), dict) else visibility,
3235
+ create_if_missing=create_if_missing,
3236
+ publish=publish,
3237
+ add_fields=list(item.get("add_fields") or item.get("addFields") or []),
3238
+ update_fields=list(item.get("update_fields") or item.get("updateFields") or []),
3239
+ remove_fields=list(item.get("remove_fields") or item.get("removeFields") or []),
3240
+ app_index=index,
3241
+ )
3242
+ if failure is not None:
3243
+ suggested = deepcopy(failure.get("suggested_next_call") or {})
3244
+ arguments = suggested.get("arguments")
3245
+ if isinstance(arguments, dict):
3246
+ fixed_apps = deepcopy(apps)
3247
+ if isinstance(fixed_apps[index], dict):
3248
+ fixed_apps[index]["add_fields"] = arguments.get("add_fields") or []
3249
+ arguments = {
3250
+ "profile": profile,
3251
+ "package_id": package_id,
3252
+ "visibility": visibility,
3253
+ "create_if_missing": create_if_missing,
3254
+ "publish": publish,
3255
+ "apps": fixed_apps,
3256
+ }
3257
+ failure["suggested_next_call"] = {"tool_name": tool_name, "arguments": arguments}
3258
+ return failure
3259
+ return None
3260
+
3261
+
3262
+ _RESERVED_SYSTEM_VIEW_NAMES = {
3263
+ "全部数据",
3264
+ "我的数据",
3265
+ "我发起的",
3266
+ "待办",
3267
+ "已办",
3268
+ "抄送",
3269
+ }
3270
+
3271
+
3272
+ def _reserved_system_view_name_failure(
3273
+ *,
3274
+ tool_name: str,
3275
+ profile: str,
3276
+ app_key: str,
3277
+ publish: bool | None = None,
3278
+ upsert_views: list[JSONObject] | None = None,
3279
+ patch_views: list[JSONObject] | None = None,
3280
+ remove_views: list[str] | None = None,
3281
+ ) -> JSONObject | None:
3282
+ for index, item in enumerate(upsert_views or []):
3283
+ if not isinstance(item, dict):
3284
+ continue
3285
+ name = str(item.get("name") or "").strip()
3286
+ view_key = str(item.get("view_key") or item.get("viewKey") or "").strip()
3287
+ if name in _RESERVED_SYSTEM_VIEW_NAMES and not view_key:
3288
+ suggested: JSONObject = {
3289
+ "tool_name": tool_name,
3290
+ "arguments": {
3291
+ "profile": profile,
3292
+ "app_key": app_key,
3293
+ "upsert_views": [
3294
+ {
3295
+ **item,
3296
+ "name": "业务台账视图",
3297
+ }
3298
+ ],
3299
+ "patch_views": patch_views or [],
3300
+ "remove_views": remove_views or [],
3301
+ },
3302
+ }
3303
+ if publish is not None:
3304
+ suggested["arguments"]["publish"] = publish
3305
+ return _config_failure(
3306
+ tool_name=tool_name,
3307
+ error_code="RESERVED_SYSTEM_VIEW_NAME",
3308
+ message=f"upsert_views[{index}].name uses built-in system view name '{name}' without view_key",
3309
+ fix_hint="Do not create business views named 全部数据, 我的数据, 我发起的, 待办, 已办, or 抄送. Use a business-specific view name for new views; to modify a built-in view, pass its existing raw view_key or use patch_views.",
3310
+ details={
3311
+ "reserved_view_names": sorted(_RESERVED_SYSTEM_VIEW_NAMES),
3312
+ "blocked_index": index,
3313
+ "blocked_name": name,
3314
+ },
3315
+ suggested_next_call=suggested,
3316
+ )
3317
+ return None
3318
+
3319
+
2971
3320
  def _config_failure(
2972
3321
  *,
2973
3322
  tool_name: str,
@@ -2976,6 +3325,7 @@ def _config_failure(
2976
3325
  error_code: str = "CONFIG_ERROR",
2977
3326
  details: JSONObject | None = None,
2978
3327
  allowed_values: JSONObject | None = None,
3328
+ suggested_next_call: JSONObject | None = None,
2979
3329
  ) -> JSONObject:
2980
3330
  contract = _BUILDER_TOOL_CONTRACTS.get(tool_name or "")
2981
3331
  public_allowed_values = deepcopy(contract.get("allowed_values", {})) if isinstance(contract, dict) else {}
@@ -2996,7 +3346,7 @@ def _config_failure(
2996
3346
  "missing_fields": [],
2997
3347
  "allowed_values": public_allowed_values,
2998
3348
  "details": public_details,
2999
- "suggested_next_call": None,
3349
+ "suggested_next_call": suggested_next_call,
3000
3350
  "request_id": None,
3001
3351
  "backend_code": None,
3002
3352
  "http_status": None,
@@ -4054,13 +4404,29 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
4054
4404
  },
4055
4405
  },
4056
4406
  "package_apply": {
4057
- "allowed_keys": ["package_id", "package_name", "create_if_missing", "icon", "color", "visibility", "items", "allow_detach"],
4407
+ "allowed_keys": [
4408
+ "package_id",
4409
+ "package_name",
4410
+ "create_if_missing",
4411
+ "icon",
4412
+ "color",
4413
+ "icon_name",
4414
+ "icon_color",
4415
+ "icon_config",
4416
+ "visibility",
4417
+ "items",
4418
+ "allow_detach",
4419
+ ],
4058
4420
  "aliases": {
4059
4421
  "packageId": "package_id",
4060
4422
  "packageName": "package_name",
4061
4423
  "createIfMissing": "create_if_missing",
4062
4424
  "iconName": "icon",
4063
4425
  "iconColor": "color",
4426
+ "icon_config.name": "icon",
4427
+ "icon_config.icon_name": "icon",
4428
+ "icon_config.color": "color",
4429
+ "icon_config.icon_color": "color",
4064
4430
  "allowDetach": "allow_detach",
4065
4431
  },
4066
4432
  "allowed_values": {
@@ -4072,6 +4438,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
4072
4438
  "execution_notes": [
4073
4439
  "create or update package metadata, visibility, grouping, and ordering in one call",
4074
4440
  "creating a package requires explicit icon + color; icon=template is blocked because it is too generic",
4441
+ "icon can be passed as icon/color, icon_name/icon_color, icon_config, or icon={name,color}; all forms normalize to icon/color",
4075
4442
  "updating a package preserves existing icon/color when omitted; explicit icon/color values are still validated",
4076
4443
  "call workspace_icon_catalog_get or `qingflow --json builder icon catalog` for supported icon/color candidates",
4077
4444
  "metadata keys omitted on update are preserved",
@@ -4536,6 +4903,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
4536
4903
  "create mode may set visibility for the new app; edit mode may update visibility on an existing app",
4537
4904
  "create mode should include explicit non-template icon + color; apply mode enforces this before writing",
4538
4905
  "call workspace_icon_catalog_get or `qingflow --json builder icon catalog` for supported icon/color candidates",
4906
+ "do not add form fields named 数据ID, 编号, 申请人, 申请时间, 创建人, 创建时间, 提交人, 提交时间, 更新时间, 更新人, 当前流程状态, 当前处理人, 当前处理节点, or 流程标题; these are Qingflow built-in system fields, not fields to create",
4539
4907
  *_VISIBILITY_EXECUTION_NOTES,
4540
4908
  ],
4541
4909
  "minimal_example": {
@@ -4575,6 +4943,9 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
4575
4943
  "app_title",
4576
4944
  "icon",
4577
4945
  "color",
4946
+ "icon_name",
4947
+ "icon_color",
4948
+ "icon_config",
4578
4949
  "visibility",
4579
4950
  "create_if_missing",
4580
4951
  "publish",
@@ -4587,11 +4958,15 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
4587
4958
  "apps[].app_name",
4588
4959
  "apps[].icon",
4589
4960
  "apps[].color",
4961
+ "apps[].icon_name",
4962
+ "apps[].icon_color",
4963
+ "apps[].icon_config",
4590
4964
  "apps[].visibility",
4591
4965
  "apps[].add_fields",
4592
4966
  "apps[].update_fields",
4593
4967
  "apps[].remove_fields",
4594
4968
  "apps[].add_fields[].target_app_ref",
4969
+ "apps[].add_fields[].target_app",
4595
4970
  ],
4596
4971
  "aliases": {
4597
4972
  "app_title": "app_name",
@@ -4601,8 +4976,16 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
4601
4976
  "apps[].appKey": "apps[].app_key",
4602
4977
  "apps[].appName": "apps[].app_name",
4603
4978
  "apps[].appTitle": "apps[].app_name",
4979
+ "apps[].iconName": "apps[].icon",
4980
+ "apps[].iconColor": "apps[].color",
4981
+ "apps[].icon_config.name": "apps[].icon",
4982
+ "apps[].icon_config.color": "apps[].color",
4604
4983
  "field.targetAppRef": "field.target_app_ref",
4605
4984
  "field.targetAppClientKey": "field.target_app_ref",
4985
+ "field.targetApp": "field.target_app",
4986
+ "field.options[].label": "field.options[]",
4987
+ "field.options[].value": "field.options[]",
4988
+ "field.options[].optValue": "field.options[]",
4606
4989
  "field.title": "field.name",
4607
4990
  "field.label": "field.name",
4608
4991
  "field.fields": "field.subfields",
@@ -4642,12 +5025,16 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
4642
5025
  "create mode follows backend CreateAppBean: package add_app permission is checked on the target package; package edit_app is not required for the create precheck",
4643
5026
  "multi-app mode: pass package_id + create_if_missing + apps[]; do not mix apps with top-level app_key/app_name/add_fields/update_fields/remove_fields",
4644
5027
  "multi-app relation fields may use target_app_ref to point at another apps[].client_key; the tool creates/resolves app shells first and compiles it to target_app_key",
5028
+ "multi-app relation fields may also use target_app with another apps[].app_name; prefer target_app_ref/client_key when names may collide",
4645
5029
  "multi-app mode is not transactional; read created_app_keys and apps[].status before retrying, and retry only failed app items",
4646
5030
  "create mode defaults new app visibility to workspace/not when visibility is omitted; edit mode preserves current visibility when omitted",
4647
5031
  "create mode requires explicit icon + color; icon=template is blocked because it is too generic",
5032
+ "icon can be passed as icon/color, icon_name/icon_color, icon_config, or icon={name,color}; all forms normalize to icon/color",
4648
5033
  "multi-app create mode requires each new app item to include a distinct non-template icon and a valid color",
5034
+ "single_select and multi_select options accept strings or objects such as {label,value}; builder normalizes them to option labels before writing",
4649
5035
  "edit mode preserves existing icon/color when omitted; explicit icon/color values are still validated",
4650
5036
  "call workspace_icon_catalog_get or `qingflow --json builder icon catalog` for supported icon/color candidates",
5037
+ "do not add form fields named 数据ID, 编号, 申请人, 申请时间, 创建人, 创建时间, 提交人, 提交时间, 更新时间, 更新人, 当前流程状态, 当前处理人, 当前处理节点, or 流程标题; these are Qingflow built-in system fields, not fields to create",
4651
5038
  *_VISIBILITY_EXECUTION_NOTES,
4652
5039
  "update_fields is the field-level partial update path; it reads current form schema and preserves untouched field config",
4653
5040
  "multiple relation fields are backend-risky; read verification.relation_field_limit_verified and warnings before declaring the schema stable",
@@ -5047,6 +5434,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
5047
5434
  "use patch_views for partial parameter replacement on existing views; the tool reads current config, merges patch_views[].set/unset, then submits the backend full-save payload internally",
5048
5435
  "remove_views accepts a raw view_key or an exact unique view name; after DELETE the tool verifies deletion by single view_key readback, not by a full app view list",
5049
5436
  "deleted views return verification.by_view[].delete_executed, readback_status, and safe_to_retry_delete=false; if readback is pending, do not blindly repeat the delete",
5437
+ "do not create business views named 全部数据, 我的数据, 我发起的, 待办, 已办, or 抄送; these are built-in system/default views. Use business-specific names for new views, and pass the existing raw view_key or patch_views when changing a built-in view",
5050
5438
  "new views created by app_views_apply default associated report/view display to visible with limit_type=all; existing views preserve their current associated display unless patch_views/upsert_views explicitly changes associated_resources",
5051
5439
  "associated report/view resource pool and per-view selected resources are configured through app_associated_resources_apply; app_views_apply only keeps legacy associated_resources input compatible",
5052
5440
  "for multi-value operators such as in, pass values as a list; value may also be used as an alias when it already contains a list",
@@ -5055,7 +5443,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
5055
5443
  "minimal_example": {
5056
5444
  "profile": "default",
5057
5445
  "app_key": "APP_KEY",
5058
- "upsert_views": [{"name": "全部数据", "type": "table", "columns": ["项目名称"], "visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE)}],
5446
+ "upsert_views": [{"name": "项目台账视图", "type": "table", "columns": ["项目名称"], "visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE)}],
5059
5447
  "remove_views": [],
5060
5448
  },
5061
5449
  "query_conditions_example": {
@@ -5176,6 +5564,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
5176
5564
  "use patch_views for partial parameter replacement on existing views; the public update mode is patch even though the backend save is still a full view payload",
5177
5565
  "remove_views accepts a raw view_key or an exact unique view name; after DELETE the tool verifies deletion by single view_key readback, not by a full app view list",
5178
5566
  "deleted views return verification.by_view[].delete_executed, readback_status, and safe_to_retry_delete=false; if readback is pending, do not blindly repeat the delete",
5567
+ "do not create business views named 全部数据, 我的数据, 我发起的, 待办, 已办, or 抄送; these are built-in system/default views. Use business-specific names for new views, and pass the existing raw view_key or patch_views when changing a built-in view",
5179
5568
  "new views created by app_views_apply default associated report/view display to visible with limit_type=all; existing views preserve their current associated display unless patch_views/upsert_views explicitly changes associated_resources",
5180
5569
  "associated report/view resource pool and per-view selected resources are configured through app_associated_resources_apply; app_views_apply keeps legacy associated_resources input compatible but it is no longer the recommended public contract",
5181
5570
  "for multi-value operators such as in, pass values as a list; value may also be used as an alias when it already contains a list",
@@ -5185,7 +5574,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
5185
5574
  "profile": "default",
5186
5575
  "app_key": "APP_KEY",
5187
5576
  "publish": True,
5188
- "upsert_views": [{"name": "全部数据", "type": "table", "columns": ["项目名称"], "visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE)}],
5577
+ "upsert_views": [{"name": "项目台账视图", "type": "table", "columns": ["项目名称"], "visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE)}],
5189
5578
  "remove_views": [],
5190
5579
  },
5191
5580
  "query_conditions_example": {
@@ -75,6 +75,22 @@ MAX_SUMMARY_PREVIEW_COLUMN_LIMIT = 6
75
75
  VERIFY_TASK_FALLBACK_PAGE_SIZE = 50
76
76
  VERIFY_TASK_FALLBACK_MAX_PAGES = 3
77
77
  BACKEND_LIST_SEARCH_FIELD_LIMIT = 10
78
+ RECORD_WRITE_SYSTEM_FIELD_NAMES = {
79
+ "数据ID",
80
+ "编号",
81
+ "申请人",
82
+ "申请时间",
83
+ "创建人",
84
+ "创建时间",
85
+ "提交人",
86
+ "提交时间",
87
+ "更新时间",
88
+ "更新人",
89
+ "当前流程状态",
90
+ "当前处理人",
91
+ "当前处理节点",
92
+ "流程标题",
93
+ }
78
94
  LOOKUP_RESOLUTION_MIN_SCORE = 0.92
79
95
  LOOKUP_RESOLUTION_MIN_MARGIN = 0.08
80
96
  LOOKUP_CONFIRMATION_CANDIDATE_LIMIT = 5
@@ -3649,6 +3665,7 @@ class RecordTools(ToolBase):
3649
3665
  raise_tool_error(QingflowApiError.config_error("app_key is required"))
3650
3666
  if items is not None:
3651
3667
  normalized_items = self._normalize_public_record_insert_batch_items(fields=fields, items=items)
3668
+ self._reject_record_insert_system_fields([cast(JSONObject, item["fields"]) for item in normalized_items])
3652
3669
  return self._record_insert_public_batch(
3653
3670
  profile=profile,
3654
3671
  app_key=app_key,
@@ -3658,15 +3675,35 @@ class RecordTools(ToolBase):
3658
3675
  )
3659
3676
  if fields is not None and not isinstance(fields, dict):
3660
3677
  raise_tool_error(QingflowApiError.config_error("fields must be an object map keyed by field title"))
3678
+ normalized_fields = cast(JSONObject, fields or {})
3679
+ self._reject_record_insert_system_fields([normalized_fields])
3661
3680
  return self._record_insert_public_single(
3662
3681
  profile=profile,
3663
3682
  app_key=app_key,
3664
- fields=cast(JSONObject, fields or {}),
3683
+ fields=normalized_fields,
3665
3684
  verify_write=verify_write,
3666
3685
  output_profile=normalized_output_profile,
3667
3686
  capture_exceptions=False,
3668
3687
  )
3669
3688
 
3689
+ def _reject_record_insert_system_fields(self, field_maps: list[JSONObject]) -> None:
3690
+ for row_index, field_map in enumerate(field_maps):
3691
+ for field_name in field_map:
3692
+ normalized_name = str(field_name or "").strip()
3693
+ if normalized_name in RECORD_WRITE_SYSTEM_FIELD_NAMES:
3694
+ raise_tool_error(
3695
+ QingflowApiError.config_error(
3696
+ f"record_insert fields must not include built-in system field '{normalized_name}'",
3697
+ details={
3698
+ "error_code": "RESERVED_SYSTEM_FIELD_NAME",
3699
+ "row_number": row_index + 1,
3700
+ "field_name": normalized_name,
3701
+ "reserved_field_names": sorted(RECORD_WRITE_SYSTEM_FIELD_NAMES),
3702
+ "fix_hint": "Remove Qingflow built-in system fields from record_insert payload. They are generated by the platform and can be read after creation, not manually inserted.",
3703
+ },
3704
+ )
3705
+ )
3706
+
3670
3707
  def _record_insert_public_single(
3671
3708
  self,
3672
3709
  *,