@qingflow-tech/qingflow-cli 1.1.60 → 1.1.61-beta.2
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 +3 -3
- package/package.json +1 -1
- package/pyproject.toml +3 -1
- package/skills/qingflow-cli/SKILL.md +120 -8
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md +1 -1
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_EXPLORATION_REPORT.md +1 -1
- package/skills/qingflow-cli/reference/builder/20-build-complete-system.md +2 -2
- package/skills/qingflow-cli/reference/builder/30-schema-fields.md +9 -5
- package/skills/qingflow-cli/reference/builder/reference/app-delivery-sop.md +3 -3
- package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md +4 -4
- package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_EXPLORATION_REPORT.md +1 -1
- package/skills/qingflow-cli/reference/data/README.md +20 -0
- package/skills/qingflow-cli/reference/record/QINGFLOW_CLI_RECORD_SPECIALIZED_WORKFLOW.md +7 -1
- package/skills/qingflow-common/SKILL.md +5 -2
- package/src/qingflow_mcp/app_form_compat.py +3 -0
- package/src/qingflow_mcp/app_form_validator.py +69 -17
- package/src/qingflow_mcp/backend_client.py +33 -8
- package/src/qingflow_mcp/builder_facade/app_form.py +497 -100
- package/src/qingflow_mcp/builder_facade/schemas/app_form/manifest.json +1 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/complete.json +64 -30
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/address.json +4 -8
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/amount.json +4 -8
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/attachment.json +6 -10
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/code_block.json +6 -10
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/data_relation.json +11 -18
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/date.json +4 -8
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/datetime.json +4 -8
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/department.json +6 -10
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/email.json +4 -8
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/link.json +4 -8
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/long_text.json +4 -8
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/member.json +6 -10
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/multi_select.json +4 -8
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/number.json +4 -8
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/phone.json +4 -8
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/q_linker.json +6 -10
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/relation.json +46 -15
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/rich_text.json +146 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/single_choice.json +4 -8
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/single_select.json +4 -8
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/subtable.json +61 -27
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/text.json +4 -8
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/formula.json +43 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/summary.json +7 -12
- package/src/qingflow_mcp/builder_facade/service.py +39 -1
- package/src/qingflow_mcp/cli/commands/data.py +13 -3
- package/src/qingflow_mcp/cli/formatters.py +7 -0
- package/src/qingflow_mcp/cli/main.py +34 -1
- package/src/qingflow_mcp/cloud_context.py +16 -1
- package/src/qingflow_mcp/public_surface.py +67 -8
- package/src/qingflow_mcp/response_trim.py +2 -1
- package/src/qingflow_mcp/server.py +13 -7
- package/src/qingflow_mcp/server_app_builder.py +9 -5
- package/src/qingflow_mcp/server_app_user.py +10 -6
- package/src/qingflow_mcp/server_cloud.py +43 -7
- package/src/qingflow_mcp/tool_catalog.py +387 -0
- package/src/qingflow_mcp/tool_catalog_i18n.py +185 -0
- package/src/qingflow_mcp/tool_policy.py +197 -0
- package/src/qingflow_mcp/tools/data_files.py +174 -0
- package/src/qingflow_mcp/tools/data_tools.py +42 -13
- package/src/qingflow_mcp/tools/record_tools.py +14 -72
- package/src/qingflow_mcp/tools/storage_download.py +89 -0
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
]
|
|
112
112
|
},
|
|
113
113
|
"fileSize": {
|
|
114
|
-
"minimum":
|
|
114
|
+
"minimum": 0,
|
|
115
115
|
"type": [
|
|
116
116
|
"integer",
|
|
117
117
|
"null"
|
|
@@ -131,7 +131,7 @@
|
|
|
131
131
|
]
|
|
132
132
|
},
|
|
133
133
|
"maxFileLimit": {
|
|
134
|
-
"minimum":
|
|
134
|
+
"minimum": 0,
|
|
135
135
|
"type": [
|
|
136
136
|
"integer",
|
|
137
137
|
"null"
|
|
@@ -229,7 +229,7 @@
|
|
|
229
229
|
},
|
|
230
230
|
"DataRelationCondition": {
|
|
231
231
|
"additionalProperties": false,
|
|
232
|
-
"description": "一条数据关联过滤条件;sourceField 必须属于数据来源,operator 必须适用于该字段类型。固定值请使用 literal operand,不要直接声明后端 matchRules。",
|
|
232
|
+
"description": "一条数据关联过滤条件;sourceField 必须属于数据来源,operator 必须适用于该字段类型。固定值请使用 literal operand,不要直接声明后端 matchRules。sourceField 省略为存量读回形态(历史脏公式的源字段缺失,如 #queId_0$,写回时以 queId=0 透传),新建条件应提供 sourceField。",
|
|
233
233
|
"properties": {
|
|
234
234
|
"operand": {
|
|
235
235
|
"$ref": "#/$defs/DataRelationOperand",
|
|
@@ -265,11 +265,10 @@
|
|
|
265
265
|
},
|
|
266
266
|
"sourceField": {
|
|
267
267
|
"$ref": "#/$defs/FieldSelector",
|
|
268
|
-
"description": "
|
|
268
|
+
"description": "被过滤的数据源字段;存量读回形态可能缺失(后端反解析出的条件源字段 queId=0 且无名称)。"
|
|
269
269
|
}
|
|
270
270
|
},
|
|
271
271
|
"required": [
|
|
272
|
-
"sourceField",
|
|
273
272
|
"operator",
|
|
274
273
|
"operand"
|
|
275
274
|
],
|
|
@@ -277,7 +276,7 @@
|
|
|
277
276
|
},
|
|
278
277
|
"DataRelationConfig": {
|
|
279
278
|
"additionalProperties": false,
|
|
280
|
-
"description": "数据关联字段配置;从应用、Q-Linker 或代码块输出中读取数据。",
|
|
279
|
+
"description": "数据关联字段配置;从应用、Q-Linker 或代码块输出中读取数据。source 省略表示该字段当前无关联来源配置(存量表单读回形态:后端 GET 对 displayed 目标缺失的 relation 行不回传,FormSvcImpl#getQuestionRelationsBeans continue 分支;新建数据关联字段应提供 source)。",
|
|
281
280
|
"properties": {
|
|
282
281
|
"aggregation": {
|
|
283
282
|
"description": "可选聚合方式;null 表示不聚合。",
|
|
@@ -312,12 +311,10 @@
|
|
|
312
311
|
},
|
|
313
312
|
"source": {
|
|
314
313
|
"$ref": "#/$defs/DataRelationSource",
|
|
315
|
-
"description": "数据来源;按 kind
|
|
314
|
+
"description": "数据来源;按 kind 选择一种来源结构。存量读回形态可能无 source(后端读面不回传关联配置),此时写回不重建该字段的关联。"
|
|
316
315
|
}
|
|
317
316
|
},
|
|
318
|
-
"required": [
|
|
319
|
-
"source"
|
|
320
|
-
],
|
|
317
|
+
"required": [],
|
|
321
318
|
"type": "object"
|
|
322
319
|
},
|
|
323
320
|
"DataRelationCustomButton": {
|
|
@@ -989,7 +986,7 @@
|
|
|
989
986
|
"description": "普通字段的默认值;后端 queDefaultValue 为字符串,因此数字等字面量也必须以字符串传入。defaultType 为 1 时表示静态文本,为 3 时表示公式文本,为 2 时不携带字面量默认值。表格、地址、附件、引用等结构化默认值必须放在具体字段 config.defaultValues。显式 null 表示清除默认值。"
|
|
990
987
|
},
|
|
991
988
|
"name": {
|
|
992
|
-
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId
|
|
989
|
+
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId 时省略;同父级允许同名(后端以 queId 为字段身份),配置引用建议携带 queId。",
|
|
993
990
|
"minLength": 1,
|
|
994
991
|
"type": "string"
|
|
995
992
|
},
|
|
@@ -1010,13 +1007,9 @@
|
|
|
1010
1007
|
"type": "string"
|
|
1011
1008
|
},
|
|
1012
1009
|
"width": {
|
|
1013
|
-
"description": "
|
|
1014
|
-
"
|
|
1015
|
-
|
|
1016
|
-
50,
|
|
1017
|
-
75,
|
|
1018
|
-
100
|
|
1019
|
-
],
|
|
1010
|
+
"description": "字段在所在行的宽度百分比(1-100 整数);后端不校验总和与取值,存量数据存在 33/34 等三等分布值。",
|
|
1011
|
+
"maximum": 100,
|
|
1012
|
+
"minimum": 1,
|
|
1020
1013
|
"type": "integer"
|
|
1021
1014
|
}
|
|
1022
1015
|
},
|
|
@@ -1036,10 +1029,10 @@
|
|
|
1036
1029
|
]
|
|
1037
1030
|
}
|
|
1038
1031
|
],
|
|
1039
|
-
"description": "字段选择器;用于定位应用的数据标题、数据封面、自定义编号取值来源或其他配置引用的字段。至少提供 queId 或 name
|
|
1032
|
+
"description": "字段选择器;用于定位应用的数据标题、数据封面、自定义编号取值来源或其他配置引用的字段。至少提供 queId 或 name,两者同时提供时必须指向同一字段。后端允许同父级同名字段,queId 是稳定身份;name 仅在唯一时可解析,命中多个同名时报 AMBIGUOUS_FIELD_SELECTOR,此时必须改用 queId。",
|
|
1040
1033
|
"properties": {
|
|
1041
1034
|
"name": {
|
|
1042
|
-
"description": "
|
|
1035
|
+
"description": "字段名称;仅在当前作用域内唯一时可用于解析,同名命中多个字段时报 AMBIGUOUS_FIELD_SELECTOR,应改用 queId。",
|
|
1043
1036
|
"minLength": 1,
|
|
1044
1037
|
"type": "string"
|
|
1045
1038
|
},
|
|
@@ -1401,7 +1394,7 @@
|
|
|
1401
1394
|
},
|
|
1402
1395
|
"RelationConfig": {
|
|
1403
1396
|
"additionalProperties": false,
|
|
1404
|
-
"description": "
|
|
1397
|
+
"description": "引用字段配置;引用其他应用中的数据。展示字段与权限以 displayFields 为准(后端 referQuestions 全量有序回写,queAuth 1=可编辑/2=隐藏/3=仅可见);displayField/visibleFields 为已废弃旧形态,apply 兼容接收一个版本窗口(displayField 同时视为双向绑定目标)。",
|
|
1405
1398
|
"properties": {
|
|
1406
1399
|
"beingDefaultFormulaAutoFillEnabled": {
|
|
1407
1400
|
"description": "是否启用引用字段的默认值公式自动填充。",
|
|
@@ -1439,7 +1432,32 @@
|
|
|
1439
1432
|
},
|
|
1440
1433
|
"displayField": {
|
|
1441
1434
|
"$ref": "#/$defs/FieldSelector",
|
|
1442
|
-
"description": "
|
|
1435
|
+
"description": "已废弃:主展示字段;apply 兼容翻译为 twoWayBoundField + displayFields 首项,后续版本移除。"
|
|
1436
|
+
},
|
|
1437
|
+
"displayFields": {
|
|
1438
|
+
"description": "有序展示字段全量列表(含隐藏字段),按 ordinal 保序回写为后端 referQuestions。可为空列表:空 = 双向绑定未配展示字段的合法存量形态(后端写链路对空 referQuestions 跳过插入、读面容忍空),round-trip 空↔空稳定;与已废弃 displayField/visibleFields 不可混用,条目 queId 不可重复。",
|
|
1439
|
+
"items": {
|
|
1440
|
+
"additionalProperties": false,
|
|
1441
|
+
"properties": {
|
|
1442
|
+
"auth": {
|
|
1443
|
+
"default": "visible",
|
|
1444
|
+
"description": "展示字段权限;editable=可编辑(1), hidden=隐藏(2), visible=仅可见(3);缺失按 visible。",
|
|
1445
|
+
"enum": [
|
|
1446
|
+
"editable",
|
|
1447
|
+
"hidden",
|
|
1448
|
+
"visible"
|
|
1449
|
+
]
|
|
1450
|
+
},
|
|
1451
|
+
"field": {
|
|
1452
|
+
"$ref": "#/$defs/FieldSelector"
|
|
1453
|
+
}
|
|
1454
|
+
},
|
|
1455
|
+
"required": [
|
|
1456
|
+
"field"
|
|
1457
|
+
],
|
|
1458
|
+
"type": "object"
|
|
1459
|
+
},
|
|
1460
|
+
"type": "array"
|
|
1443
1461
|
},
|
|
1444
1462
|
"relationMode": {
|
|
1445
1463
|
"default": "single",
|
|
@@ -1454,8 +1472,19 @@
|
|
|
1454
1472
|
"minLength": 1,
|
|
1455
1473
|
"type": "string"
|
|
1456
1474
|
},
|
|
1475
|
+
"twoWayBoundField": {
|
|
1476
|
+
"description": "双向引用绑定的目标字段(后端 referQueId);null/省略 = 单向引用。get 读面在 referQueId 非空时必产出。",
|
|
1477
|
+
"oneOf": [
|
|
1478
|
+
{
|
|
1479
|
+
"type": "null"
|
|
1480
|
+
},
|
|
1481
|
+
{
|
|
1482
|
+
"$ref": "#/$defs/FieldSelector"
|
|
1483
|
+
}
|
|
1484
|
+
]
|
|
1485
|
+
},
|
|
1457
1486
|
"visibleFields": {
|
|
1458
|
-
"description": "
|
|
1487
|
+
"description": "已废弃:额外展示字段;apply 兼容翻译为 displayFields 后续项,后续版本移除。",
|
|
1459
1488
|
"items": {
|
|
1460
1489
|
"$ref": "#/$defs/FieldSelector"
|
|
1461
1490
|
},
|
|
@@ -1464,8 +1493,7 @@
|
|
|
1464
1493
|
}
|
|
1465
1494
|
},
|
|
1466
1495
|
"required": [
|
|
1467
|
-
"targetAppKey"
|
|
1468
|
-
"displayField"
|
|
1496
|
+
"targetAppKey"
|
|
1469
1497
|
],
|
|
1470
1498
|
"type": "object"
|
|
1471
1499
|
},
|
|
@@ -1584,7 +1612,10 @@
|
|
|
1584
1612
|
"$ref": "#/$defs/NumberConfig"
|
|
1585
1613
|
},
|
|
1586
1614
|
"questionType": {
|
|
1587
|
-
"
|
|
1615
|
+
"enum": [
|
|
1616
|
+
"number",
|
|
1617
|
+
"amount"
|
|
1618
|
+
]
|
|
1588
1619
|
}
|
|
1589
1620
|
},
|
|
1590
1621
|
"required": [
|
|
@@ -1597,7 +1628,10 @@
|
|
|
1597
1628
|
"$ref": "#/$defs/EmptyConfig"
|
|
1598
1629
|
},
|
|
1599
1630
|
"questionType": {
|
|
1600
|
-
"
|
|
1631
|
+
"enum": [
|
|
1632
|
+
"date",
|
|
1633
|
+
"datetime"
|
|
1634
|
+
]
|
|
1601
1635
|
}
|
|
1602
1636
|
},
|
|
1603
1637
|
"required": [
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"description": "普通字段的默认值;后端 queDefaultValue 为字符串,因此数字等字面量也必须以字符串传入。defaultType 为 1 时表示静态文本,为 3 时表示公式文本,为 2 时不携带字面量默认值。表格、地址、附件、引用等结构化默认值必须放在具体字段 config.defaultValues。显式 null 表示清除默认值。"
|
|
79
79
|
},
|
|
80
80
|
"name": {
|
|
81
|
-
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId
|
|
81
|
+
"description": "字段展示名称;新增字段必填,已有字段可在携带 queId 时省略;同父级允许同名(后端以 queId 为字段身份),配置引用建议携带 queId。",
|
|
82
82
|
"minLength": 1,
|
|
83
83
|
"type": "string"
|
|
84
84
|
},
|
|
@@ -99,13 +99,9 @@
|
|
|
99
99
|
"type": "string"
|
|
100
100
|
},
|
|
101
101
|
"width": {
|
|
102
|
-
"description": "
|
|
103
|
-
"
|
|
104
|
-
|
|
105
|
-
50,
|
|
106
|
-
75,
|
|
107
|
-
100
|
|
108
|
-
],
|
|
102
|
+
"description": "字段在所在行的宽度百分比(1-100 整数);后端不校验总和与取值,存量数据存在 33/34 等三等分布值。",
|
|
103
|
+
"maximum": 100,
|
|
104
|
+
"minimum": 1,
|
|
109
105
|
"type": "integer"
|
|
110
106
|
}
|
|
111
107
|
},
|
|
@@ -236,6 +236,28 @@
|
|
|
236
236
|
"DATEDELTA(date, number, unit)"
|
|
237
237
|
]
|
|
238
238
|
},
|
|
239
|
+
"DATEDIF": {
|
|
240
|
+
"category": "date",
|
|
241
|
+
"example": "DATEDIF(#queId_102$, #queId_101$)",
|
|
242
|
+
"meaning": "历史日期差函数名,仅存在于存量公式中(当前公式引擎未注册,日期差请使用 DAYS(end, start))。为使存量声明的 validate/round-trip 通过而保留声明,不建议新公式使用。",
|
|
243
|
+
"parameters": [
|
|
244
|
+
{
|
|
245
|
+
"description": "结束日期或列表",
|
|
246
|
+
"name": "end"
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
"description": "开始日期或列表",
|
|
250
|
+
"name": "start"
|
|
251
|
+
}
|
|
252
|
+
],
|
|
253
|
+
"returns": {
|
|
254
|
+
"description": "天数差文本列表",
|
|
255
|
+
"type": "list"
|
|
256
|
+
},
|
|
257
|
+
"signatures": [
|
|
258
|
+
"DATEDIF(end, start)"
|
|
259
|
+
]
|
|
260
|
+
},
|
|
239
261
|
"DATETOTIMESTAMP": {
|
|
240
262
|
"category": "date",
|
|
241
263
|
"example": "DATETOTIMESTAMP(#queId_101$)",
|
|
@@ -363,6 +385,19 @@
|
|
|
363
385
|
"GETUSEREMAIL()"
|
|
364
386
|
]
|
|
365
387
|
},
|
|
388
|
+
"GETUSERENAME": {
|
|
389
|
+
"category": "context",
|
|
390
|
+
"example": "GETUSERENAME()",
|
|
391
|
+
"meaning": "历史拼写(GETUSERNAME 的讹写),仅存在于存量公式中(当前公式引擎未注册,请使用 GETUSERNAME())。为使存量声明的 validate/round-trip 通过而保留声明,不建议新公式使用。",
|
|
392
|
+
"parameters": [],
|
|
393
|
+
"returns": {
|
|
394
|
+
"description": "当前用户显示名称;无法取得时为空字符串",
|
|
395
|
+
"type": "string"
|
|
396
|
+
},
|
|
397
|
+
"signatures": [
|
|
398
|
+
"GETUSERENAME()"
|
|
399
|
+
]
|
|
400
|
+
},
|
|
366
401
|
"GETUSERNAME": {
|
|
367
402
|
"category": "context",
|
|
368
403
|
"example": "GETUSERNAME()",
|
|
@@ -1590,5 +1625,13 @@
|
|
|
1590
1625
|
"需要拼接字段当前值时,将该字段的实际 queId 写成未加引号的 #queId_<字段ID>$;字段 name、字段标题和字段默认值都不能替代字段引用。",
|
|
1591
1626
|
"新建应用的同一次 create 声明中尚未获得 queId 时,不得虚构 queId 或以字段 name 代替:先创建字段,app_form_get 回读实际 queId 后,再提交公式默认值更新。"
|
|
1592
1627
|
]
|
|
1628
|
+
},
|
|
1629
|
+
"x-qingflow-validation": {
|
|
1630
|
+
"functionCallPolicy": "validate 会识别公式文本中引号外的 函数名( 调用;函数名必须在 x-qingflow-functions 中声明。未知函数会被拒绝,字符串字面量中的 NAME() 不视为函数调用。",
|
|
1631
|
+
"scope": [
|
|
1632
|
+
"defaultType=3 的 defaultValue",
|
|
1633
|
+
"config.defaultValueFormula",
|
|
1634
|
+
"data_relation config.filters.mode=advanced 的 config.filters.formula"
|
|
1635
|
+
]
|
|
1593
1636
|
}
|
|
1594
1637
|
}
|
|
@@ -136,10 +136,10 @@
|
|
|
136
136
|
]
|
|
137
137
|
}
|
|
138
138
|
],
|
|
139
|
-
"description": "字段选择器;用于定位应用的数据标题、数据封面、自定义编号取值来源或其他配置引用的字段。至少提供 queId 或 name
|
|
139
|
+
"description": "字段选择器;用于定位应用的数据标题、数据封面、自定义编号取值来源或其他配置引用的字段。至少提供 queId 或 name,两者同时提供时必须指向同一字段。后端允许同父级同名字段,queId 是稳定身份;name 仅在唯一时可解析,命中多个同名时报 AMBIGUOUS_FIELD_SELECTOR,此时必须改用 queId。",
|
|
140
140
|
"properties": {
|
|
141
141
|
"name": {
|
|
142
|
-
"description": "
|
|
142
|
+
"description": "字段名称;仅在当前作用域内唯一时可用于解析,同名命中多个字段时报 AMBIGUOUS_FIELD_SELECTOR,应改用 queId。",
|
|
143
143
|
"minLength": 1,
|
|
144
144
|
"type": "string"
|
|
145
145
|
},
|
|
@@ -258,13 +258,9 @@
|
|
|
258
258
|
"type": "array"
|
|
259
259
|
},
|
|
260
260
|
"width": {
|
|
261
|
-
"description": "
|
|
262
|
-
"
|
|
263
|
-
|
|
264
|
-
50,
|
|
265
|
-
75,
|
|
266
|
-
100
|
|
267
|
-
],
|
|
261
|
+
"description": "字段在所在行的宽度百分比(1-100 整数);后端不校验总和与取值,存量数据存在 33/34 等三等分布值。",
|
|
262
|
+
"maximum": 100,
|
|
263
|
+
"minimum": 1,
|
|
268
264
|
"type": "integer"
|
|
269
265
|
}
|
|
270
266
|
},
|
|
@@ -334,11 +330,10 @@
|
|
|
334
330
|
"description": "段落判别值。"
|
|
335
331
|
},
|
|
336
332
|
"rows": {
|
|
337
|
-
"description": "
|
|
333
|
+
"description": "段落内的有序字段行;可为空数组(空 = 存量空段落形态,后端容忍,round-trip 空↔空稳定)。",
|
|
338
334
|
"items": {
|
|
339
335
|
"$ref": "#/$defs/FieldRow"
|
|
340
336
|
},
|
|
341
|
-
"minItems": 1,
|
|
342
337
|
"type": "array"
|
|
343
338
|
},
|
|
344
339
|
"sectionId": {
|
|
@@ -383,7 +378,6 @@
|
|
|
383
378
|
],
|
|
384
379
|
"type": "object"
|
|
385
380
|
},
|
|
386
|
-
"minItems": 1,
|
|
387
381
|
"type": "array"
|
|
388
382
|
},
|
|
389
383
|
"sectionId": {
|
|
@@ -695,6 +689,7 @@
|
|
|
695
689
|
"x-qingflow-supported-field-types": [
|
|
696
690
|
"text",
|
|
697
691
|
"long_text",
|
|
692
|
+
"rich_text",
|
|
698
693
|
"number",
|
|
699
694
|
"amount",
|
|
700
695
|
"link",
|
|
@@ -163,9 +163,14 @@ 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 侧白名单同口径。
|
|
166
170
|
INTEGRATION_OUTPUT_TARGET_FIELD_TYPES: tuple[str, ...] = (
|
|
167
171
|
FieldType.text.value,
|
|
168
172
|
FieldType.long_text.value,
|
|
173
|
+
"rich_text",
|
|
169
174
|
FieldType.number.value,
|
|
170
175
|
FieldType.amount.value,
|
|
171
176
|
FieldType.date.value,
|
|
@@ -173,6 +178,9 @@ INTEGRATION_OUTPUT_TARGET_FIELD_TYPES: tuple[str, ...] = (
|
|
|
173
178
|
FieldType.single_select.value,
|
|
174
179
|
FieldType.multi_select.value,
|
|
175
180
|
FieldType.boolean.value,
|
|
181
|
+
FieldType.attachment.value,
|
|
182
|
+
"data_relation",
|
|
183
|
+
FieldType.subtable.value,
|
|
176
184
|
)
|
|
177
185
|
|
|
178
186
|
MATCH_TYPE_ACCURACY = 1
|
|
@@ -23237,6 +23245,23 @@ def _apply_field_mutation(field: dict[str, Any], mutation: Any) -> None:
|
|
|
23237
23245
|
field.pop("_question_rebuild_required", None)
|
|
23238
23246
|
|
|
23239
23247
|
|
|
23248
|
+
class AmbiguousFieldSelector(ValueError):
|
|
23249
|
+
"""name-only selector 命中多个同名字段。
|
|
23250
|
+
|
|
23251
|
+
方案 A(duplicate-field-name):后端以 queId 为字段身份、允许同父级同名;
|
|
23252
|
+
name 命中多个字段时禁止静默取第一个,必须显式失败并给出候选 queId。
|
|
23253
|
+
compiler 包装层(app_form)把它转写为 AMBIGUOUS_FIELD_SELECTOR 错误码。
|
|
23254
|
+
"""
|
|
23255
|
+
|
|
23256
|
+
def __init__(self, location: str, candidates: list[int]) -> None:
|
|
23257
|
+
self.location = location
|
|
23258
|
+
self.candidates = candidates
|
|
23259
|
+
super().__init__(
|
|
23260
|
+
f"{location} matches multiple fields with the same name; "
|
|
23261
|
+
f"use queId to disambiguate (candidate queIds: {sorted(candidates)})"
|
|
23262
|
+
)
|
|
23263
|
+
|
|
23264
|
+
|
|
23240
23265
|
def _resolve_field_selector_with_uniqueness(
|
|
23241
23266
|
*,
|
|
23242
23267
|
fields: list[dict[str, Any]],
|
|
@@ -23252,7 +23277,12 @@ def _resolve_field_selector_with_uniqueness(
|
|
|
23252
23277
|
name = str(selector.name or "").strip()
|
|
23253
23278
|
matched = [field for field in fields if str(field.get("name") or "").strip() == name]
|
|
23254
23279
|
if len(matched) > 1:
|
|
23255
|
-
|
|
23280
|
+
candidate_ids = [
|
|
23281
|
+
ref
|
|
23282
|
+
for candidate in matched
|
|
23283
|
+
if (ref := _coerce_any_int(candidate.get("que_id"))) is not None
|
|
23284
|
+
]
|
|
23285
|
+
raise AmbiguousFieldSelector(location, candidate_ids)
|
|
23256
23286
|
else:
|
|
23257
23287
|
matched = []
|
|
23258
23288
|
if len(matched) != 1:
|
|
@@ -29932,6 +29962,14 @@ def _build_view_update_payload(
|
|
|
29932
29962
|
payload["viewgraphType"] = "calendarView"
|
|
29933
29963
|
payload["beingShowTitleQue"] = True
|
|
29934
29964
|
payload.pop("groupQueId", None)
|
|
29965
|
+
elif normalized_type == "hierarchy":
|
|
29966
|
+
payload["viewgraphType"] = "hierarchyView"
|
|
29967
|
+
payload["beingShowTitleQue"] = True
|
|
29968
|
+
payload.pop("groupQueId", None)
|
|
29969
|
+
elif normalized_type == "calendar":
|
|
29970
|
+
payload["viewgraphType"] = "calendarView"
|
|
29971
|
+
payload["beingShowTitleQue"] = True
|
|
29972
|
+
payload.pop("groupQueId", None)
|
|
29935
29973
|
return _hydrate_view_backend_payload(
|
|
29936
29974
|
payload=payload,
|
|
29937
29975
|
view_type=normalized_type,
|
|
@@ -20,6 +20,8 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
20
20
|
items.json [{"fields": {"101": "A"}}]
|
|
21
21
|
fields.json {"101": "B"}
|
|
22
22
|
query/get/insert/update 默认返回 field_capabilities;可用 --no-schema 关闭。
|
|
23
|
+
下载附件:data get --app-key APP_KEY --record-id RECORD_ID --download-attachments,
|
|
24
|
+
从 file_assets.items[].local_path 获取本地文件。
|
|
23
25
|
""").strip(),
|
|
24
26
|
)
|
|
25
27
|
commands = parser.add_subparsers(dest="data_command", required=True, metavar="{query,get,insert,update,delete}")
|
|
@@ -27,7 +29,9 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
27
29
|
query = commands.add_parser(
|
|
28
30
|
"query", help="分页查询应用数据", formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
29
31
|
epilog='最小示例:qingflow data query --app-key APP_1 --where-file where.json\n'
|
|
30
|
-
'where.json: [{"field_id": 101, "op": "eq", "value": "A"}]'
|
|
32
|
+
'where.json: [{"field_id": 101, "op": "eq", "value": "A"}]\n'
|
|
33
|
+
'查询不下载附件;对目标记录执行 data get --app-key APP_KEY --record-id RECORD_ID\n'
|
|
34
|
+
'--download-attachments,使用 file_assets.items[].local_path。',
|
|
31
35
|
)
|
|
32
36
|
query.add_argument("--app-key", required=True)
|
|
33
37
|
query.add_argument("--columns-file", help='JSON 数组,例如 [101, 102]')
|
|
@@ -44,10 +48,15 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
44
48
|
query.add_argument("--output", choices=("json", "csv"), default="json")
|
|
45
49
|
query.set_defaults(handler=_handle_query, format_hint="data_query")
|
|
46
50
|
|
|
47
|
-
get = commands.add_parser(
|
|
51
|
+
get = commands.add_parser(
|
|
52
|
+
"get", help="读取单条应用数据", formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
53
|
+
epilog='默认只读取字段;下载附件或图片时加 --download-attachments,并保留 schema。\n'
|
|
54
|
+
'使用 file_assets.items[].local_path;部分下载失败时读取 file_assets.warnings。',
|
|
55
|
+
)
|
|
48
56
|
get.add_argument("--app-key", required=True)
|
|
49
57
|
get.add_argument("--record-id", required=True)
|
|
50
58
|
get.add_argument("--no-schema", action="store_false", dest="include_schema", default=True)
|
|
59
|
+
get.add_argument("--download-attachments", action="store_true", help="下载当前记录可见附件到本地(需要 schema)")
|
|
51
60
|
get.set_defaults(handler=_handle_get, format_hint="data_get")
|
|
52
61
|
|
|
53
62
|
insert = commands.add_parser(
|
|
@@ -102,7 +111,8 @@ def _handle_query(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
102
111
|
|
|
103
112
|
def _handle_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
104
113
|
return context.data.data_get(
|
|
105
|
-
profile=args.profile, app_key=args.app_key, record_id=args.record_id, include_schema=args.include_schema
|
|
114
|
+
profile=args.profile, app_key=args.app_key, record_id=args.record_id, include_schema=args.include_schema,
|
|
115
|
+
download_attachments=args.download_attachments,
|
|
106
116
|
)
|
|
107
117
|
|
|
108
118
|
|
|
@@ -137,6 +137,13 @@ def _format_data_result(result: dict[str, Any]) -> str:
|
|
|
137
137
|
record = result.get("record") if isinstance(result.get("record"), dict) else {}
|
|
138
138
|
if record:
|
|
139
139
|
lines.append(f"Record ID: {record.get('record_id') or '-'}")
|
|
140
|
+
file_assets = result.get("file_assets")
|
|
141
|
+
if isinstance(file_assets, dict):
|
|
142
|
+
lines.append(f"Attachments: {file_assets.get('status') or '-'}")
|
|
143
|
+
for asset in file_assets.get("items", []):
|
|
144
|
+
if isinstance(asset, dict):
|
|
145
|
+
lines.append(f"- {asset.get('file_name')}: {asset.get('access_status')} / {asset.get('local_path') or '-'}")
|
|
146
|
+
_append_warnings(lines, file_assets.get("warnings"))
|
|
140
147
|
items = result.get("items") if isinstance(result.get("items"), list) else []
|
|
141
148
|
if items:
|
|
142
149
|
lines.append(f"Items: {len(items)}")
|
|
@@ -7,11 +7,22 @@ import sys
|
|
|
7
7
|
from typing import Any, Callable, TextIO
|
|
8
8
|
|
|
9
9
|
from ..errors import QingflowApiError, backend_code_value_int, message_looks_like_invalid_token
|
|
10
|
-
from ..public_surface import
|
|
10
|
+
from ..public_surface import (
|
|
11
|
+
NON_TOOL_CLI_HANDLER_ROUTES,
|
|
12
|
+
cli_public_tool_spec_from_namespace,
|
|
13
|
+
cli_route_from_namespace,
|
|
14
|
+
cli_trim_key_from_namespace,
|
|
15
|
+
)
|
|
11
16
|
from ..resource_uri import cli_resource_base_url
|
|
12
17
|
from ..response_trim import resolve_cli_tool_name, trim_error_response, trim_public_response
|
|
13
18
|
from ..tools.ai_builder_tools import _attach_builder_apply_envelope
|
|
14
19
|
from ..version import get_cli_version, get_cli_version_info
|
|
20
|
+
from ..tool_policy import (
|
|
21
|
+
TOOL_POLICY_ENV,
|
|
22
|
+
TOOL_POLICY_INVALID,
|
|
23
|
+
ToolPolicyViolation,
|
|
24
|
+
parse_tool_policy,
|
|
25
|
+
)
|
|
15
26
|
from .context import CliContext, build_cli_context
|
|
16
27
|
from .formatters import emit_json_result, emit_text_result
|
|
17
28
|
from .commands import register_all_commands
|
|
@@ -90,6 +101,14 @@ def run(
|
|
|
90
101
|
if handler is None:
|
|
91
102
|
parser.print_help(out)
|
|
92
103
|
return 2
|
|
104
|
+
violation = _cli_tool_policy_violation(args)
|
|
105
|
+
if violation is not None:
|
|
106
|
+
return _emit_error(
|
|
107
|
+
violation.payload(),
|
|
108
|
+
json_mode=bool(args.json),
|
|
109
|
+
stdout=out,
|
|
110
|
+
stderr=err,
|
|
111
|
+
)
|
|
93
112
|
try:
|
|
94
113
|
if _should_force_json_output(args):
|
|
95
114
|
setattr(args, "json", True)
|
|
@@ -122,6 +141,20 @@ def run(
|
|
|
122
141
|
return exit_code
|
|
123
142
|
|
|
124
143
|
|
|
144
|
+
def _cli_tool_policy_violation(args: argparse.Namespace) -> ToolPolicyViolation | None:
|
|
145
|
+
stable_id = cli_trim_key_from_namespace(args)
|
|
146
|
+
if stable_id is None:
|
|
147
|
+
# 仅显式登记的非业务分发器可跳过闸门;无子命令的新增 handler 也必须默认拒绝。
|
|
148
|
+
if cli_route_from_namespace(args) in NON_TOOL_CLI_HANDLER_ROUTES:
|
|
149
|
+
return None
|
|
150
|
+
return ToolPolicyViolation(
|
|
151
|
+
TOOL_POLICY_INVALID,
|
|
152
|
+
"user:unregistered",
|
|
153
|
+
"CLI 业务命令未登记稳定工具 ID",
|
|
154
|
+
)
|
|
155
|
+
return parse_tool_policy(os.getenv(TOOL_POLICY_ENV)).violation_for(stable_id)
|
|
156
|
+
|
|
157
|
+
|
|
125
158
|
def _handle_version(_args: argparse.Namespace, _context: CliContext) -> dict[str, Any]:
|
|
126
159
|
info = get_cli_version_info()
|
|
127
160
|
return {
|
|
@@ -2,11 +2,14 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from contextvars import ContextVar
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
|
-
from typing import Callable
|
|
5
|
+
from typing import TYPE_CHECKING, Callable
|
|
6
6
|
|
|
7
7
|
from .backend_client import BackendRequestContext
|
|
8
8
|
from .session_store import BackendSession, SessionProfile
|
|
9
9
|
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from .tool_policy import ToolPolicySnapshot
|
|
12
|
+
|
|
10
13
|
|
|
11
14
|
CLOUD_PROFILE_NAME = "__cloud_mcp__"
|
|
12
15
|
|
|
@@ -73,3 +76,15 @@ current_call_platform: ContextVar[str] = ContextVar(
|
|
|
73
76
|
"qingflow_mcp_current_call_platform",
|
|
74
77
|
default="",
|
|
75
78
|
)
|
|
79
|
+
|
|
80
|
+
# 远端 MCP 每个 HTTP 请求独立绑定策略原文;缺头用 None 表示,不回退进程环境变量。
|
|
81
|
+
current_tool_policy_header: ContextVar[str | None] = ContextVar(
|
|
82
|
+
"qingflow_mcp_current_tool_policy_header",
|
|
83
|
+
default=None,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# 在 HTTP 入口合成一次快照,列表与调用共用,避免将 URL 选择保存到会话或共享环境。
|
|
87
|
+
current_http_tool_policy: ContextVar[ToolPolicySnapshot | None] = ContextVar(
|
|
88
|
+
"qingflow_mcp_current_http_tool_policy",
|
|
89
|
+
default=None,
|
|
90
|
+
)
|