@qingflow-tech/qingflow-cli 1.1.56 → 1.1.58
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 +4 -4
- package/npm/lib/runtime.mjs +13 -29
- package/package.json +1 -1
- package/pyproject.toml +1 -2
- package/skills/qingflow-cli/SKILL.md +319 -90
- package/skills/qingflow-cli/manifest.yaml +1 -1
- package/skills/qingflow-cli/reference/00-INDEX.md +4 -4
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_ADMIN_CHEATSHEET.md +1 -1
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md +137 -3
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_EXPLORATION_REPORT.md +4 -6
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_FIELD_DATA_TYPES.md +14 -14
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_MEMBER_CHEATSHEET.md +19 -20
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md +14 -18
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md +12 -7
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md +1 -2
- package/skills/qingflow-cli/reference/builder/20-build-complete-system.md +2 -2
- package/skills/qingflow-cli/reference/builder/30-schema-fields.md +5 -9
- package/skills/qingflow-cli/reference/builder/50-views.md +2 -1
- package/skills/qingflow-cli/reference/builder/reference/app-delivery-sop.md +3 -3
- package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_ADMIN_CHEATSHEET.md +1 -1
- package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md +95 -76
- package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_EXPLORATION_REPORT.md +4 -6
- package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_FIELD_DATA_TYPES.md +13 -13
- package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_MEMBER_CHEATSHEET.md +19 -20
- package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md +14 -18
- package/skills/qingflow-cli/reference/record/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md +1 -2
- package/skills/qingflow-cli/reference/record/analysis/README.md +113 -21
- package/skills/qingflow-cli/reference/record/analysis/analysis-gotchas.md +10 -10
- package/skills/qingflow-cli/reference/record/analysis/analysis-patterns.md +9 -9
- package/skills/qingflow-cli/reference/record/analysis/business-context.md +1 -1
- package/skills/qingflow-cli/reference/record/analysis/confidence-reporting.md +10 -10
- package/skills/qingflow-cli/reference/record/analysis/data-access-playbook.md +80 -52
- package/skills/qingflow-cli/reference/record/analysis/pandas-recipes.md +2 -2
- package/skills/qingflow-common/SKILL.md +4 -7
- package/src/qingflow_mcp/app_form_compat.py +0 -3
- package/src/qingflow_mcp/app_form_validator.py +17 -69
- package/src/qingflow_mcp/builder_facade/app_form.py +100 -497
- package/src/qingflow_mcp/builder_facade/models.py +2 -2
- package/src/qingflow_mcp/builder_facade/schemas/app_form/manifest.json +0 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/complete.json +30 -64
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/address.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/amount.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/attachment.json +10 -6
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/code_block.json +10 -6
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/data_relation.json +18 -11
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/date.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/datetime.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/department.json +10 -6
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/email.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/link.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/long_text.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/member.json +10 -6
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/multi_select.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/number.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/phone.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/q_linker.json +10 -6
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/relation.json +15 -46
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/single_choice.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/single_select.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/subtable.json +27 -61
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/text.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/formula.json +0 -43
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/summary.json +12 -7
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/complete.json +35 -6
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/data-permission.json +5 -5
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/summary.json +5 -5
- package/src/qingflow_mcp/builder_facade/service.py +6 -43
- package/src/qingflow_mcp/builder_facade/views_extract.py +12 -2
- package/src/qingflow_mcp/cli/commands/__init__.py +1 -2
- package/src/qingflow_mcp/cli/commands/record.py +1 -1
- package/src/qingflow_mcp/cli/context.py +0 -3
- package/src/qingflow_mcp/cli/formatters.py +1 -97
- package/src/qingflow_mcp/public_surface.py +0 -5
- package/src/qingflow_mcp/response_trim.py +1 -2
- package/src/qingflow_mcp/server.py +0 -2
- package/src/qingflow_mcp/server_app_user.py +0 -4
- package/src/qingflow_mcp/tools/record_tools.py +46 -11
- package/skills/qingflow-cli/reference/data/README.md +0 -32
- package/skills/qingflow-cli/reference/record/QINGFLOW_CLI_RECORD_SPECIALIZED_WORKFLOW.md +0 -50
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/rich_text.json +0 -146
- package/src/qingflow_mcp/cli/commands/data.py +0 -134
- package/src/qingflow_mcp/tools/csv_export.py +0 -168
- package/src/qingflow_mcp/tools/data_tools.py +0 -284
|
@@ -136,10 +136,10 @@
|
|
|
136
136
|
]
|
|
137
137
|
}
|
|
138
138
|
],
|
|
139
|
-
"description": "字段选择器;用于定位应用的数据标题、数据封面、自定义编号取值来源或其他配置引用的字段。至少提供 queId 或 name
|
|
139
|
+
"description": "字段选择器;用于定位应用的数据标题、数据封面、自定义编号取值来源或其他配置引用的字段。至少提供 queId 或 name,两者同时提供时必须指向同一字段。",
|
|
140
140
|
"properties": {
|
|
141
141
|
"name": {
|
|
142
|
-
"description": "
|
|
142
|
+
"description": "字段名称;仅在当前作用域内唯一时可用于解析。",
|
|
143
143
|
"minLength": 1,
|
|
144
144
|
"type": "string"
|
|
145
145
|
},
|
|
@@ -258,9 +258,13 @@
|
|
|
258
258
|
"type": "array"
|
|
259
259
|
},
|
|
260
260
|
"width": {
|
|
261
|
-
"description": "
|
|
262
|
-
"
|
|
263
|
-
|
|
261
|
+
"description": "字段在所在行的宽度百分比;只能选择 25、50、75 或 100。",
|
|
262
|
+
"enum": [
|
|
263
|
+
25,
|
|
264
|
+
50,
|
|
265
|
+
75,
|
|
266
|
+
100
|
|
267
|
+
],
|
|
264
268
|
"type": "integer"
|
|
265
269
|
}
|
|
266
270
|
},
|
|
@@ -330,10 +334,11 @@
|
|
|
330
334
|
"description": "段落判别值。"
|
|
331
335
|
},
|
|
332
336
|
"rows": {
|
|
333
|
-
"description": "
|
|
337
|
+
"description": "段落内的非空有序字段行。",
|
|
334
338
|
"items": {
|
|
335
339
|
"$ref": "#/$defs/FieldRow"
|
|
336
340
|
},
|
|
341
|
+
"minItems": 1,
|
|
337
342
|
"type": "array"
|
|
338
343
|
},
|
|
339
344
|
"sectionId": {
|
|
@@ -378,6 +383,7 @@
|
|
|
378
383
|
],
|
|
379
384
|
"type": "object"
|
|
380
385
|
},
|
|
386
|
+
"minItems": 1,
|
|
381
387
|
"type": "array"
|
|
382
388
|
},
|
|
383
389
|
"sectionId": {
|
|
@@ -689,7 +695,6 @@
|
|
|
689
695
|
"x-qingflow-supported-field-types": [
|
|
690
696
|
"text",
|
|
691
697
|
"long_text",
|
|
692
|
-
"rich_text",
|
|
693
698
|
"number",
|
|
694
699
|
"amount",
|
|
695
700
|
"link",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
}
|
|
76
76
|
]
|
|
77
77
|
},
|
|
78
|
-
"filtersDescription": "数据范围规则,仅 type=custom 且 scope=custom 时生效,编译为 viewgraphLimit。不会叠在 config 或 all/oneself/dept
|
|
78
|
+
"filtersDescription": "数据范围规则,仅 type=custom 且 scope=custom 时生效,编译为 viewgraphLimit。不会叠在 config 或 all/oneself/dept 上。扁平 [rule,...]=单组 AND;嵌套 [[rule,...],...]=组间 OR、组内 AND。",
|
|
79
79
|
"x-backend": "viewgraphLimitType=1 + viewgraphLimit(List<List<MatchRule>> 单组);公式形态(scope=custom 的 OR 组)列 v1alpha2"
|
|
80
80
|
},
|
|
81
81
|
"sort": {
|
|
@@ -311,7 +311,8 @@
|
|
|
311
311
|
"DataPermission": {
|
|
312
312
|
"type": "object",
|
|
313
313
|
"additionalProperties": false,
|
|
314
|
-
"
|
|
314
|
+
"required": ["type", "scope"],
|
|
315
|
+
"dataPermissionDescription": "写出本对象时 type 与 scope 都必填。优先写 custom + all。整段 data_permission 省略时,创建路径才按 custom + all。filters 只在 type=custom 且 scope=custom 时作为数据范围,不会叠在 config 或预设范围上。",
|
|
315
316
|
"properties": {
|
|
316
317
|
"type": {
|
|
317
318
|
"type": "string",
|
|
@@ -320,7 +321,7 @@
|
|
|
320
321
|
"custom"
|
|
321
322
|
],
|
|
322
323
|
"default": "custom",
|
|
323
|
-
"dataPermissionDescription": "
|
|
324
|
+
"dataPermissionDescription": "数据权限来源。写出 data_permission 时必填。config=跟随应用成员授权的数据权限,本视图的 scope 与 filters 不参与查询。custom=使用本视图自己的数据范围(scope)。别名 dataPermissionType(CONFIG/CUSTOM)。",
|
|
324
325
|
"x-backend": "dataPermissionType(CONFIG|CUSTOM)"
|
|
325
326
|
},
|
|
326
327
|
"scope": {
|
|
@@ -329,18 +330,45 @@
|
|
|
329
330
|
"all",
|
|
330
331
|
"oneself",
|
|
331
332
|
"dept",
|
|
332
|
-
"subordinate",
|
|
333
333
|
"custom"
|
|
334
334
|
],
|
|
335
335
|
"default": "all",
|
|
336
|
-
"dataScopeDescription": "
|
|
337
|
-
"x-backend": "dataScope(ALL|ONESELF|DEPT|
|
|
336
|
+
"dataScopeDescription": "写出 data_permission 时必填,且只在 type=custom 时生效。all=不追加数据范围;oneself=仅自己发起;dept=所在部门发起;custom=用 filters 作为数据范围。type=config 时不要用 scope 选择应用权限,应用授权组的范围写在成员授权上。",
|
|
337
|
+
"x-backend": "dataScope(ALL|ONESELF|DEPT|CUSTOM)"
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
340
|
},
|
|
341
341
|
"Visibility": {
|
|
342
342
|
"type": "object",
|
|
343
343
|
"additionalProperties": false,
|
|
344
|
+
"required": ["mode"],
|
|
345
|
+
"visibilityDescription": "写出 visibility 时 mode 必填。mode=specific 时必须带 selectors;selectors 各列表都不是单独必填,空对象表示指定范围但尚未配置成员。mode=everyone 时 external_mode 必须为 workspace。external_mode=specific 时必须带 external_selectors。整段省略时创建路径按 workspace + external_mode=not。",
|
|
346
|
+
"allOf": [
|
|
347
|
+
{
|
|
348
|
+
"if": {
|
|
349
|
+
"properties": { "mode": { "const": "specific" } },
|
|
350
|
+
"required": ["mode"]
|
|
351
|
+
},
|
|
352
|
+
"then": { "required": ["selectors"] }
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
"if": {
|
|
356
|
+
"properties": { "mode": { "const": "everyone" } },
|
|
357
|
+
"required": ["mode"]
|
|
358
|
+
},
|
|
359
|
+
"then": {
|
|
360
|
+
"required": ["external_mode"],
|
|
361
|
+
"properties": { "external_mode": { "const": "workspace" } }
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
"if": {
|
|
366
|
+
"properties": { "external_mode": { "const": "specific" } },
|
|
367
|
+
"required": ["external_mode"]
|
|
368
|
+
},
|
|
369
|
+
"then": { "required": ["external_selectors"] }
|
|
370
|
+
}
|
|
371
|
+
],
|
|
344
372
|
"properties": {
|
|
345
373
|
"mode": {
|
|
346
374
|
"type": "string",
|
|
@@ -373,6 +401,7 @@
|
|
|
373
401
|
"VisibilitySelectors": {
|
|
374
402
|
"type": "object",
|
|
375
403
|
"additionalProperties": false,
|
|
404
|
+
"visibilitySelectorsDescription": "成员、部门、角色用不同列表表达,没有任何一个列表单独必填。mode=specific 时本对象本身必填;空对象是合法回读,表示指定范围但尚未配置成员。要真正授权时,至少填一个非空列表。",
|
|
376
405
|
"properties": {
|
|
377
406
|
"member_uids": {
|
|
378
407
|
"type": "array",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://qingflow.com/schemas/builder/views/v1alpha1/details/data-permission.json",
|
|
4
4
|
"title": "Qingflow Views data permission configuration",
|
|
5
|
-
"dataPermissionDescription": "视图数据权限片段(schema_kind=data-permission)
|
|
5
|
+
"dataPermissionDescription": "视图数据权限片段(schema_kind=data-permission)。写出 data_permission 时 type 与 scope 都必填,优先 {type: custom, scope: all}。整段省略时创建路径才按 custom + all。type 选来源,scope 只在 type=custom 时生效;filters 只在 type=custom 且 scope=custom 时作为数据范围。字段直接并入完整视图声明。",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": false,
|
|
8
8
|
"properties": {
|
|
@@ -36,16 +36,16 @@
|
|
|
36
36
|
"fieldSchema": "字段类型、默认值等表单侧细节用 app_form_schema 传 schema_kind=field 和 field_type 获取(CLI: builder app-form schema --schema-kind field --field-type TYPE)。"
|
|
37
37
|
}
|
|
38
38
|
},
|
|
39
|
-
"x-qingflow-apply-semantics": "
|
|
39
|
+
"x-qingflow-apply-semantics": "写出 data_permission 时 type 与 scope 都必填,优先 type=custom、scope=all。整段省略时创建路径才按 custom + all。只有要跟随应用成员授权时才用 type=config,此时不要用 scope 表达范围。filters 仅在 type=custom 且 scope=custom 时作为数据范围;省略 filters 表示保留现有规则,空数组表示清空。",
|
|
40
40
|
"x-qingflow-mode-requirements": {
|
|
41
41
|
"default": "未声明 data_permission 时按 custom + all 创建。需要限制行时再改 scope,不要默认改成 config。",
|
|
42
|
-
"config": "跟随应用成员授权的数据权限。视图上的 scope、filters
|
|
43
|
-
"custom": "使用视图自己的 dataScope。scope=all 不追加范围;oneself/dept
|
|
42
|
+
"config": "跟随应用成员授权的数据权限。视图上的 scope、filters 不参与查询。应用授权组的范围写在成员授权上,不写在视图声明里。",
|
|
43
|
+
"custom": "使用视图自己的 dataScope。scope=all 不追加范围;oneself/dept 用预设的申请人范围,不读 filters;scope=custom 时 filters 才是数据范围规则。",
|
|
44
44
|
"filters": "只在 type=custom 且 scope=custom 时作为数据权限生效。扁平数组表示一组 AND;嵌套数组表示组间 OR、组内 AND。不是叠在 config 或预设范围上的第二层筛选。"
|
|
45
45
|
},
|
|
46
46
|
"x-qingflow-backend-semantics": {
|
|
47
47
|
"data_permission.type": "dataPermissionType(CONFIG/CUSTOM)",
|
|
48
|
-
"data_permission.scope": "dataScope(ALL/ONESELF/DEPT/
|
|
48
|
+
"data_permission.scope": "dataScope(ALL/ONESELF/DEPT/CUSTOM)",
|
|
49
49
|
"filters": "viewgraphLimitType=1 + viewgraphLimit;空数组清除简易条件。"
|
|
50
50
|
},
|
|
51
51
|
"x-qingflow-unsupported": {
|
|
@@ -199,7 +199,8 @@
|
|
|
199
199
|
"DataPermission": {
|
|
200
200
|
"type": "object",
|
|
201
201
|
"additionalProperties": false,
|
|
202
|
-
"
|
|
202
|
+
"required": ["type", "scope"],
|
|
203
|
+
"dataPermissionDescription": "写出本对象时 type 与 scope 都必填。优先写 custom + all。整段 data_permission 省略时,创建路径才按 custom + all。filters 只在 type=custom 且 scope=custom 时作为数据范围,不会叠在 config 或预设范围上。",
|
|
203
204
|
"properties": {
|
|
204
205
|
"type": {
|
|
205
206
|
"type": "string",
|
|
@@ -208,7 +209,7 @@
|
|
|
208
209
|
"custom"
|
|
209
210
|
],
|
|
210
211
|
"default": "custom",
|
|
211
|
-
"dataPermissionDescription": "
|
|
212
|
+
"dataPermissionDescription": "数据权限来源。写出 data_permission 时必填。config=跟随应用成员授权的数据权限,本视图的 scope 与 filters 不参与查询。custom=使用本视图自己的数据范围(scope)。别名 dataPermissionType(CONFIG/CUSTOM)。",
|
|
212
213
|
"x-backend": "viewgraph.dataPermissionType(CONFIG|CUSTOM)"
|
|
213
214
|
},
|
|
214
215
|
"scope": {
|
|
@@ -217,12 +218,11 @@
|
|
|
217
218
|
"all",
|
|
218
219
|
"oneself",
|
|
219
220
|
"dept",
|
|
220
|
-
"subordinate",
|
|
221
221
|
"custom"
|
|
222
222
|
],
|
|
223
223
|
"default": "all",
|
|
224
|
-
"dataScopeDescription": "
|
|
225
|
-
"x-backend": "viewgraph.dataScope(ALL|ONESELF|DEPT|
|
|
224
|
+
"dataScopeDescription": "写出 data_permission 时必填,且只在 type=custom 时生效。all=不追加数据范围;oneself=仅自己发起;dept=所在部门发起;custom=用 filters 作为数据范围(规则在 complete 或 schema_kind=data-permission)。type=config 时不要用 scope 选择应用权限,应用授权组的范围写在成员授权上。dataset-v0 实测分布:all×560 / custom×74 / oneself×71。",
|
|
225
|
+
"x-backend": "viewgraph.dataScope(ALL|ONESELF|DEPT|CUSTOM)"
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
}
|
|
@@ -163,14 +163,9 @@ FIELD_TYPE_TO_QUESTION_TYPE: dict[str, int] = {
|
|
|
163
163
|
FieldType.relation.value: 25,
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
# code_block / q_linker 输出目标字段类型。后端写链无类型白名单(FormSvcImpl 原样
|
|
167
|
-
# 落库),运行时 DataSvcImpl.getAnswerValueByString:attachment 按拆分 URL 写入、
|
|
168
|
-
# subtable 整表组装(均有专门分支),rich_text/data_relation 走 Question.canImport
|
|
169
|
-
# 兜底按字符串写入(不报错)——四者为存量合法配置,与 validator 侧白名单同口径。
|
|
170
166
|
INTEGRATION_OUTPUT_TARGET_FIELD_TYPES: tuple[str, ...] = (
|
|
171
167
|
FieldType.text.value,
|
|
172
168
|
FieldType.long_text.value,
|
|
173
|
-
"rich_text",
|
|
174
169
|
FieldType.number.value,
|
|
175
170
|
FieldType.amount.value,
|
|
176
171
|
FieldType.date.value,
|
|
@@ -178,9 +173,6 @@ INTEGRATION_OUTPUT_TARGET_FIELD_TYPES: tuple[str, ...] = (
|
|
|
178
173
|
FieldType.single_select.value,
|
|
179
174
|
FieldType.multi_select.value,
|
|
180
175
|
FieldType.boolean.value,
|
|
181
|
-
FieldType.attachment.value,
|
|
182
|
-
"data_relation",
|
|
183
|
-
FieldType.subtable.value,
|
|
184
176
|
)
|
|
185
177
|
|
|
186
178
|
MATCH_TYPE_ACCURACY = 1
|
|
@@ -13776,7 +13768,7 @@ class AiBuilderFacade:
|
|
|
13776
13768
|
schema_version=schema_version,
|
|
13777
13769
|
)
|
|
13778
13770
|
projected: list[dict[str, Any]] = []
|
|
13779
|
-
scope_map = {"ALL": "all", "ONESELF": "oneself", "DEPT": "dept", "
|
|
13771
|
+
scope_map = {"ALL": "all", "ONESELF": "oneself", "DEPT": "dept", "CUSTOM": "custom"}
|
|
13780
13772
|
for view in views if isinstance(views, list) else []:
|
|
13781
13773
|
if not isinstance(view, dict):
|
|
13782
13774
|
continue
|
|
@@ -13805,10 +13797,12 @@ class AiBuilderFacade:
|
|
|
13805
13797
|
if title_que_id is not None and title_que_id in name_by_que_id:
|
|
13806
13798
|
entry.setdefault("title_field", name_by_que_id[title_que_id])
|
|
13807
13799
|
data_permission_type = view.get("dataPermissionType")
|
|
13808
|
-
|
|
13800
|
+
raw_scope = str(view.get("dataScope") or "").upper()
|
|
13801
|
+
public_scope = scope_map.get(raw_scope)
|
|
13802
|
+
if data_permission_type and (not raw_scope or public_scope):
|
|
13809
13803
|
entry["data_permission"] = {
|
|
13810
13804
|
"type": "config" if str(data_permission_type).upper() == "CONFIG" else "custom",
|
|
13811
|
-
"scope":
|
|
13805
|
+
"scope": public_scope or "all",
|
|
13812
13806
|
}
|
|
13813
13807
|
projected.append(entry)
|
|
13814
13808
|
return {
|
|
@@ -23243,23 +23237,6 @@ def _apply_field_mutation(field: dict[str, Any], mutation: Any) -> None:
|
|
|
23243
23237
|
field.pop("_question_rebuild_required", None)
|
|
23244
23238
|
|
|
23245
23239
|
|
|
23246
|
-
class AmbiguousFieldSelector(ValueError):
|
|
23247
|
-
"""name-only selector 命中多个同名字段。
|
|
23248
|
-
|
|
23249
|
-
方案 A(duplicate-field-name):后端以 queId 为字段身份、允许同父级同名;
|
|
23250
|
-
name 命中多个字段时禁止静默取第一个,必须显式失败并给出候选 queId。
|
|
23251
|
-
compiler 包装层(app_form)把它转写为 AMBIGUOUS_FIELD_SELECTOR 错误码。
|
|
23252
|
-
"""
|
|
23253
|
-
|
|
23254
|
-
def __init__(self, location: str, candidates: list[int]) -> None:
|
|
23255
|
-
self.location = location
|
|
23256
|
-
self.candidates = candidates
|
|
23257
|
-
super().__init__(
|
|
23258
|
-
f"{location} matches multiple fields with the same name; "
|
|
23259
|
-
f"use queId to disambiguate (candidate queIds: {sorted(candidates)})"
|
|
23260
|
-
)
|
|
23261
|
-
|
|
23262
|
-
|
|
23263
23240
|
def _resolve_field_selector_with_uniqueness(
|
|
23264
23241
|
*,
|
|
23265
23242
|
fields: list[dict[str, Any]],
|
|
@@ -23275,12 +23252,7 @@ def _resolve_field_selector_with_uniqueness(
|
|
|
23275
23252
|
name = str(selector.name or "").strip()
|
|
23276
23253
|
matched = [field for field in fields if str(field.get("name") or "").strip() == name]
|
|
23277
23254
|
if len(matched) > 1:
|
|
23278
|
-
|
|
23279
|
-
ref
|
|
23280
|
-
for candidate in matched
|
|
23281
|
-
if (ref := _coerce_any_int(candidate.get("que_id"))) is not None
|
|
23282
|
-
]
|
|
23283
|
-
raise AmbiguousFieldSelector(location, candidate_ids)
|
|
23255
|
+
raise ValueError(f"{location} matched multiple fields with the same name; use field_id or que_id")
|
|
23284
23256
|
else:
|
|
23285
23257
|
matched = []
|
|
23286
23258
|
if len(matched) != 1:
|
|
@@ -29960,14 +29932,6 @@ def _build_view_update_payload(
|
|
|
29960
29932
|
payload["viewgraphType"] = "calendarView"
|
|
29961
29933
|
payload["beingShowTitleQue"] = True
|
|
29962
29934
|
payload.pop("groupQueId", None)
|
|
29963
|
-
elif normalized_type == "hierarchy":
|
|
29964
|
-
payload["viewgraphType"] = "hierarchyView"
|
|
29965
|
-
payload["beingShowTitleQue"] = True
|
|
29966
|
-
payload.pop("groupQueId", None)
|
|
29967
|
-
elif normalized_type == "calendar":
|
|
29968
|
-
payload["viewgraphType"] = "calendarView"
|
|
29969
|
-
payload["beingShowTitleQue"] = True
|
|
29970
|
-
payload.pop("groupQueId", None)
|
|
29971
29935
|
return _hydrate_view_backend_payload(
|
|
29972
29936
|
payload=payload,
|
|
29973
29937
|
view_type=normalized_type,
|
|
@@ -30221,7 +30185,6 @@ _DATA_PERMISSION_SCOPE_MAP = {
|
|
|
30221
30185
|
"all": "ALL",
|
|
30222
30186
|
"oneself": "ONESELF",
|
|
30223
30187
|
"dept": "DEPT",
|
|
30224
|
-
"subordinate": "SUBORDINATE",
|
|
30225
30188
|
"custom": "CUSTOM",
|
|
30226
30189
|
}
|
|
30227
30190
|
|
|
@@ -34,7 +34,6 @@ _DATA_SCOPE_TO_PUBLIC = {
|
|
|
34
34
|
"ALL": "all",
|
|
35
35
|
"ONESELF": "oneself",
|
|
36
36
|
"DEPT": "dept",
|
|
37
|
-
"SUBORDINATE": "subordinate",
|
|
38
37
|
"CUSTOM": "custom",
|
|
39
38
|
}
|
|
40
39
|
|
|
@@ -470,7 +469,18 @@ def extract_complete_view_declaration(
|
|
|
470
469
|
declaration["filters"] = filters
|
|
471
470
|
|
|
472
471
|
data_permission_type = str(view_info.get("dataPermissionType") or "").strip().upper()
|
|
473
|
-
|
|
472
|
+
raw_scope = str(view_info.get("dataScope") or "").strip().upper()
|
|
473
|
+
if raw_scope and raw_scope not in _DATA_SCOPE_TO_PUBLIC:
|
|
474
|
+
gaps.append(
|
|
475
|
+
_gap(
|
|
476
|
+
"data_permission.scope",
|
|
477
|
+
"unsupported_data_scope",
|
|
478
|
+
"dataScope is outside the public view contract",
|
|
479
|
+
value_preview=raw_scope,
|
|
480
|
+
)
|
|
481
|
+
)
|
|
482
|
+
return {}, gaps
|
|
483
|
+
data_scope = _DATA_SCOPE_TO_PUBLIC.get(raw_scope)
|
|
474
484
|
if data_permission_type or data_scope:
|
|
475
485
|
declaration["data_permission"] = {
|
|
476
486
|
"type": "config" if data_permission_type == "CONFIG" else "custom",
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
|
|
5
|
-
from . import app, auth, builder, chart,
|
|
5
|
+
from . import app, auth, builder, chart, exports, imports, portal, record, task, view, workspace
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def register_all_commands(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
@@ -13,7 +13,6 @@ def register_all_commands(subparsers: argparse._SubParsersAction[argparse.Argume
|
|
|
13
13
|
view.register(subparsers)
|
|
14
14
|
chart.register(subparsers)
|
|
15
15
|
record.register(subparsers)
|
|
16
|
-
data.register(subparsers)
|
|
17
16
|
imports.register(subparsers)
|
|
18
17
|
exports.register(subparsers)
|
|
19
18
|
task.register(subparsers)
|
|
@@ -9,7 +9,7 @@ from .common import load_list_arg, load_object_arg, raise_config_error, require_
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
12
|
-
parser = subparsers.add_parser("record", help="
|
|
12
|
+
parser = subparsers.add_parser("record", help="记录与表结构")
|
|
13
13
|
record_subparsers = parser.add_subparsers(
|
|
14
14
|
dest="record_command",
|
|
15
15
|
required=True,
|
|
@@ -11,7 +11,6 @@ from ..tools.ai_builder_tools import AiBuilderTools
|
|
|
11
11
|
from ..tools.app_tools import AppTools
|
|
12
12
|
from ..tools.auth_tools import AuthTools
|
|
13
13
|
from ..tools.code_block_tools import CodeBlockTools
|
|
14
|
-
from ..tools.data_tools import DataTools
|
|
15
14
|
from ..tools.export_tools import ExportTools
|
|
16
15
|
from ..tools.feedback_tools import FeedbackTools
|
|
17
16
|
from ..tools.file_tools import FileTools
|
|
@@ -34,7 +33,6 @@ class CliContext:
|
|
|
34
33
|
app: AppTools
|
|
35
34
|
resource: ResourceReadTools
|
|
36
35
|
record: RecordTools
|
|
37
|
-
data: DataTools
|
|
38
36
|
code_block: CodeBlockTools
|
|
39
37
|
imports: ImportTools
|
|
40
38
|
exports: ExportTools
|
|
@@ -66,7 +64,6 @@ def build_cli_context() -> CliContext:
|
|
|
66
64
|
app=AppTools(sessions, backend),
|
|
67
65
|
resource=ResourceReadTools(sessions, backend),
|
|
68
66
|
record=RecordTools(sessions, backend),
|
|
69
|
-
data=DataTools(sessions, backend),
|
|
70
67
|
code_block=CodeBlockTools(sessions, backend),
|
|
71
68
|
imports=ImportTools(sessions, backend),
|
|
72
69
|
exports=ExportTools(sessions, backend),
|
|
@@ -8,10 +8,7 @@ from typing import Any, TextIO
|
|
|
8
8
|
def emit_text_result(result: dict[str, Any], *, hint: str, stream: TextIO) -> None:
|
|
9
9
|
text = _format_cancelled_result(result)
|
|
10
10
|
if text is None:
|
|
11
|
-
if hint
|
|
12
|
-
# 数据面失败也必须保留 field_errors 与列表型 hints,不能先走通用失败裁剪。
|
|
13
|
-
text = _format_data_result(result)
|
|
14
|
-
elif _is_failed_result(result) and hint != "task_action_execute":
|
|
11
|
+
if _is_failed_result(result) and hint != "task_action_execute":
|
|
15
12
|
text = _format_failed_result(result)
|
|
16
13
|
else:
|
|
17
14
|
formatter = _FORMATTERS.get(hint, _format_generic)
|
|
@@ -93,92 +90,6 @@ def _format_generic(result: dict[str, Any]) -> str:
|
|
|
93
90
|
return "\n".join(lines) + "\n"
|
|
94
91
|
|
|
95
92
|
|
|
96
|
-
def _format_data_result(result: dict[str, Any]) -> str:
|
|
97
|
-
lines = [f"Status: {result.get('status') or ('success' if result.get('ok') is not False else 'failed')}"]
|
|
98
|
-
if result.get("format") == "csv":
|
|
99
|
-
files = result.get("files") if isinstance(result.get("files"), list) else []
|
|
100
|
-
lines.extend(
|
|
101
|
-
[
|
|
102
|
-
f"Local dir: {result.get('local_dir') or '-'}",
|
|
103
|
-
f"Files: {len(files)}",
|
|
104
|
-
]
|
|
105
|
-
)
|
|
106
|
-
for item in files:
|
|
107
|
-
if isinstance(item, dict):
|
|
108
|
-
lines.append(
|
|
109
|
-
f"- part {item.get('part')}: {item.get('local_path')} ({item.get('row_count')} rows)"
|
|
110
|
-
)
|
|
111
|
-
lines.extend(
|
|
112
|
-
[
|
|
113
|
-
f"Rows: {result.get('row_count')}",
|
|
114
|
-
f"Complete: {result.get('complete')}",
|
|
115
|
-
f"Truncated: {result.get('truncated')}",
|
|
116
|
-
f"Safe for final conclusion: {result.get('safe_for_final_conclusion')}",
|
|
117
|
-
f"Stopped reason: {result.get('stopped_reason') or '-'}",
|
|
118
|
-
]
|
|
119
|
-
)
|
|
120
|
-
access = result.get("access") if isinstance(result.get("access"), dict) else {}
|
|
121
|
-
if access:
|
|
122
|
-
sources = access.get("scope_sources") if isinstance(access.get("scope_sources"), list) else []
|
|
123
|
-
lines.append(
|
|
124
|
-
f"Access: level={access.get('level') or '-'} / can_write={access.get('can_write')}"
|
|
125
|
-
f" / scope_sources={','.join(str(item) for item in sources) or '-'}"
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
records = result.get("records") if isinstance(result.get("records"), dict) else {}
|
|
129
|
-
if records:
|
|
130
|
-
items = records.get("items") if isinstance(records.get("items"), list) else []
|
|
131
|
-
lines.append(
|
|
132
|
-
"Pagination: "
|
|
133
|
-
f"page={records.get('page')} / requested_page_size={records.get('requested_page_size')} / "
|
|
134
|
-
f"page_size={records.get('page_size')} / total={records.get('total')} / "
|
|
135
|
-
f"has_more={records.get('has_more')} / returned={len(items)}"
|
|
136
|
-
)
|
|
137
|
-
record = result.get("record") if isinstance(result.get("record"), dict) else {}
|
|
138
|
-
if record:
|
|
139
|
-
lines.append(f"Record ID: {record.get('record_id') or '-'}")
|
|
140
|
-
items = result.get("items") if isinstance(result.get("items"), list) else []
|
|
141
|
-
if items:
|
|
142
|
-
lines.append(f"Items: {len(items)}")
|
|
143
|
-
|
|
144
|
-
for key, label in (
|
|
145
|
-
("error_code", "Error Code"),
|
|
146
|
-
("message", "Message"),
|
|
147
|
-
("write_executed", "Write Executed"),
|
|
148
|
-
("retry_safe", "Retry Safe"),
|
|
149
|
-
):
|
|
150
|
-
if key in result:
|
|
151
|
-
lines.append(f"{label}: {result.get(key)}")
|
|
152
|
-
|
|
153
|
-
field_errors = result.get("field_errors") if isinstance(result.get("field_errors"), list) else []
|
|
154
|
-
if field_errors:
|
|
155
|
-
lines.append("Field Errors:")
|
|
156
|
-
_append_data_field_errors(lines, field_errors)
|
|
157
|
-
for index, item in enumerate(items):
|
|
158
|
-
if not isinstance(item, dict) or not isinstance(item.get("field_errors"), list):
|
|
159
|
-
continue
|
|
160
|
-
lines.append(f"Item {index} Field Errors:")
|
|
161
|
-
_append_data_field_errors(lines, item["field_errors"])
|
|
162
|
-
|
|
163
|
-
hints = result.get("hints") if isinstance(result.get("hints"), list) else []
|
|
164
|
-
lines.append(f"Hints: {len(hints)}")
|
|
165
|
-
for hint in hints:
|
|
166
|
-
if isinstance(hint, dict):
|
|
167
|
-
lines.append(f"- {hint.get('code') or '-'}: {hint.get('message') or '-'}")
|
|
168
|
-
else:
|
|
169
|
-
lines.append(f"- {hint}")
|
|
170
|
-
return "\n".join(lines) + "\n"
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
def _append_data_field_errors(lines: list[str], field_errors: list[Any]) -> None:
|
|
174
|
-
for error in field_errors:
|
|
175
|
-
if isinstance(error, dict):
|
|
176
|
-
lines.append(
|
|
177
|
-
f"- {error.get('path') or '-'}: {error.get('message') or '-'}"
|
|
178
|
-
f" / fix_hint={error.get('fix_hint') or '-'}"
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
|
|
182
93
|
def _format_whoami(result: dict[str, Any]) -> str:
|
|
183
94
|
lines = [
|
|
184
95
|
f"User: {result.get('nick_name') or '-'} ({result.get('email') or '-'})",
|
|
@@ -1758,11 +1669,4 @@ _FORMATTERS = {
|
|
|
1758
1669
|
"builder_summary": _format_builder_summary,
|
|
1759
1670
|
"file_upload_local": _format_file_upload_local,
|
|
1760
1671
|
"feedback_submit": _format_feedback_submit,
|
|
1761
|
-
"data_query": _format_data_result,
|
|
1762
|
-
"data_get": _format_data_result,
|
|
1763
|
-
"data_insert": _format_data_result,
|
|
1764
|
-
"data_update": _format_data_result,
|
|
1765
|
-
"data_delete": _format_data_result,
|
|
1766
1672
|
}
|
|
1767
|
-
|
|
1768
|
-
_DATA_FORMAT_HINTS = {"data_query", "data_get", "data_insert", "data_update", "data_delete"}
|
|
@@ -40,11 +40,6 @@ USER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
|
|
|
40
40
|
PublicToolSpec(USER_DOMAIN, "workspace_select", ("workspace_select",), ("workspace", "select")),
|
|
41
41
|
PublicToolSpec(USER_DOMAIN, "app_list", ("app_list",), ("app", "list"), cli_show_effective_context=True),
|
|
42
42
|
PublicToolSpec(USER_DOMAIN, "app_get", ("app_get",), ("app", "get"), cli_show_effective_context=True),
|
|
43
|
-
PublicToolSpec(USER_DOMAIN, "data_query", ("data_query",), ("data", "query"), cli_show_effective_context=True),
|
|
44
|
-
PublicToolSpec(USER_DOMAIN, "data_get", ("data_get",), ("data", "get"), cli_show_effective_context=True),
|
|
45
|
-
PublicToolSpec(USER_DOMAIN, "data_insert", ("data_insert",), ("data", "insert"), cli_show_effective_context=True, cli_context_write=True),
|
|
46
|
-
PublicToolSpec(USER_DOMAIN, "data_update", ("data_update",), ("data", "update"), cli_show_effective_context=True, cli_context_write=True),
|
|
47
|
-
PublicToolSpec(USER_DOMAIN, "data_delete", ("data_delete",), ("data", "delete"), cli_show_effective_context=True, cli_context_write=True),
|
|
48
43
|
PublicToolSpec(USER_DOMAIN, "portal_list", ("portal_list",), ("portal", "list"), cli_show_effective_context=True),
|
|
49
44
|
PublicToolSpec(USER_DOMAIN, "portal_get", ("portal_get",), ("portal", "get"), cli_show_effective_context=True),
|
|
50
45
|
PublicToolSpec(USER_DOMAIN, "view_get", ("view_get",), ("view", "get"), cli_show_effective_context=True),
|
|
@@ -51,9 +51,8 @@ COMMON_ERROR_DROP_TOP = {
|
|
|
51
51
|
|
|
52
52
|
SUCCESS_POLICY_BY_TOOL: dict[str, TransformFn] = {}
|
|
53
53
|
|
|
54
|
-
#:
|
|
54
|
+
#: 这两个校验命令的产出契约明确包含 ok 字段(供阶段 2 SKILL.md 引用),
|
|
55
55
|
#: 需在 success 裁剪时保留 ok(默认 COMMON_SUCCESS_DROP_TOP 会丢弃 ok)。
|
|
56
|
-
#: `builder:app_views_validate`(views 声明式校验)同属校验类工具,ok 同样进产出契约。
|
|
57
56
|
_KEEP_OK_TOOLS = {
|
|
58
57
|
"builder:app_form_validate_formula",
|
|
59
58
|
"builder:app_form_validate_code_block",
|
|
@@ -7,7 +7,6 @@ from mcp.server.fastmcp import FastMCP
|
|
|
7
7
|
from .backend_client import BackendClient
|
|
8
8
|
from .session_store import SessionStore
|
|
9
9
|
from .tools.app_tools import AppTools
|
|
10
|
-
from .tools.data_tools import DataTools
|
|
11
10
|
from .tools.auth_tools import AuthTools
|
|
12
11
|
from .tools.code_block_tools import CodeBlockTools
|
|
13
12
|
from .tools.feedback_tools import FeedbackTools
|
|
@@ -250,7 +249,6 @@ If the current MCP capability is unsupported, the workflow is awkward, or the us
|
|
|
250
249
|
TaskContextTools(sessions, backend).register(server)
|
|
251
250
|
RoleTools(sessions, backend).register(server)
|
|
252
251
|
AppTools(sessions, backend).register(server)
|
|
253
|
-
DataTools(sessions, backend).register(server)
|
|
254
252
|
QingbiReportTools(sessions, backend).register(server)
|
|
255
253
|
PackageTools(sessions, backend).register(server)
|
|
256
254
|
NavigationTools(sessions, backend).register(server)
|
|
@@ -10,7 +10,6 @@ from .config import DEFAULT_PROFILE
|
|
|
10
10
|
from .response_trim import USER_SERVER_METHOD_MAP, trim_error_response, trim_public_response, wrap_trimmed_methods
|
|
11
11
|
from .session_store import SessionStore
|
|
12
12
|
from .tools.app_tools import AppTools
|
|
13
|
-
from .tools.data_tools import DataTools
|
|
14
13
|
from .tools.auth_tools import AuthTools
|
|
15
14
|
from .tools.code_block_tools import CodeBlockTools
|
|
16
15
|
from .tools.directory_tools import DirectoryTools
|
|
@@ -243,7 +242,6 @@ If the current MCP capability is unsupported, the workflow is awkward, or the us
|
|
|
243
242
|
backend = BackendClient()
|
|
244
243
|
auth = wrap_trimmed_methods(AuthTools(sessions, backend), USER_SERVER_METHOD_MAP)
|
|
245
244
|
apps = wrap_trimmed_methods(AppTools(sessions, backend), USER_SERVER_METHOD_MAP)
|
|
246
|
-
data_tools = wrap_trimmed_methods(DataTools(sessions, backend), USER_SERVER_METHOD_MAP)
|
|
247
245
|
workspace = wrap_trimmed_methods(WorkspaceTools(sessions, backend), USER_SERVER_METHOD_MAP)
|
|
248
246
|
file_tools = wrap_trimmed_methods(FileTools(sessions, backend), USER_SERVER_METHOD_MAP)
|
|
249
247
|
imports = wrap_trimmed_methods(ImportTools(sessions, backend), USER_SERVER_METHOD_MAP)
|
|
@@ -397,8 +395,6 @@ If the current MCP capability is unsupported, the workflow is awkward, or the us
|
|
|
397
395
|
def app_get(profile: str = DEFAULT_PROFILE, app_key: str = "") -> dict:
|
|
398
396
|
return apps.app_get(profile=profile, app_key=app_key)
|
|
399
397
|
|
|
400
|
-
data_tools.register(server)
|
|
401
|
-
|
|
402
398
|
@server.tool()
|
|
403
399
|
def portal_list(profile: str = DEFAULT_PROFILE) -> dict:
|
|
404
400
|
return resources.portal_list(profile=profile)
|