qingflow-mcp 0.3.22 → 0.3.23
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 +40 -3
- package/dist/server.js +275 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -117,7 +117,7 @@ npm i -g git+https://github.com/853046310/qingflow-mcp.git
|
|
|
117
117
|
Install from npm (pinned version):
|
|
118
118
|
|
|
119
119
|
```bash
|
|
120
|
-
npm i -g qingflow-mcp@0.3.
|
|
120
|
+
npm i -g qingflow-mcp@0.3.23
|
|
121
121
|
```
|
|
122
122
|
|
|
123
123
|
Or one-click installer:
|
|
@@ -153,7 +153,7 @@ MCP client config example:
|
|
|
153
153
|
## Recommended Flow
|
|
154
154
|
|
|
155
155
|
1. `qf_apps_list` to pick app.
|
|
156
|
-
2. `qf_form_get` to inspect field ids/titles.
|
|
156
|
+
2. `qf_form_get` to inspect field ids/titles and `field_summaries[].write_format`.
|
|
157
157
|
3. `qf_record_create` or `qf_record_update`.
|
|
158
158
|
4. If create/update returns only `request_id`, call `qf_operation_get` to resolve async result.
|
|
159
159
|
|
|
@@ -180,6 +180,43 @@ Full calling contract (Chinese):
|
|
|
180
180
|
|
|
181
181
|
- [MCP 调用规范](./docs/MCP_CALLING_SPEC.md)
|
|
182
182
|
|
|
183
|
+
## Write Format Discovery
|
|
184
|
+
|
|
185
|
+
For create/update, `0.3.23` now makes special write formats explicit:
|
|
186
|
+
|
|
187
|
+
1. `qf_form_get`
|
|
188
|
+
- `field_summaries[].write_format` is populated for member/department fields.
|
|
189
|
+
2. `qf_tool_spec_get`
|
|
190
|
+
- `qf_record_create` / `qf_record_update` include member/department examples in `limits.special_field_write_formats`.
|
|
191
|
+
3. `qf_record_create` / `qf_record_update`
|
|
192
|
+
- invalid member/department values fail fast with `FIELD_VALUE_FORMAT_ERROR`.
|
|
193
|
+
|
|
194
|
+
Examples:
|
|
195
|
+
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"fields": {
|
|
199
|
+
"归属销售": [
|
|
200
|
+
{ "userId": "u_123", "userName": "张三" }
|
|
201
|
+
],
|
|
202
|
+
"归属部门": [
|
|
203
|
+
{ "deptId": 111, "deptName": "销售部" }
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Invalid examples that will now fail:
|
|
210
|
+
|
|
211
|
+
```json
|
|
212
|
+
{
|
|
213
|
+
"fields": {
|
|
214
|
+
"归属销售": "张三",
|
|
215
|
+
"归属部门": "销售部"
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
183
220
|
## Unified Query (`qf_query`)
|
|
184
221
|
|
|
185
222
|
`qf_query` is the recommended read entry for agents.
|
|
@@ -429,7 +466,7 @@ If you see runtime errors around `Headers` or missing web APIs:
|
|
|
429
466
|
2. Upgrade package to latest:
|
|
430
467
|
|
|
431
468
|
```bash
|
|
432
|
-
npm i -g qingflow-mcp@
|
|
469
|
+
npm i -g qingflow-mcp@0.3.23
|
|
433
470
|
```
|
|
434
471
|
|
|
435
472
|
3. Verify runtime:
|
package/dist/server.js
CHANGED
|
@@ -68,7 +68,9 @@ const REQUEST_TIMEOUT_MS = toPositiveInt(process.env.QINGFLOW_REQUEST_TIMEOUT_MS
|
|
|
68
68
|
const EXECUTION_BUDGET_MS = toPositiveInt(process.env.QINGFLOW_EXECUTION_BUDGET_MS) ?? 20000;
|
|
69
69
|
const WAIT_RESULT_DEFAULT_TIMEOUT_MS = toPositiveInt(process.env.QINGFLOW_WAIT_RESULT_TIMEOUT_MS) ?? 5000;
|
|
70
70
|
const WAIT_RESULT_POLL_INTERVAL_MS = toPositiveInt(process.env.QINGFLOW_WAIT_RESULT_POLL_INTERVAL_MS) ?? 500;
|
|
71
|
-
const SERVER_VERSION = "0.3.
|
|
71
|
+
const SERVER_VERSION = "0.3.23";
|
|
72
|
+
const MEMBER_QUE_TYPE_KEYWORDS = ["member", "user", "成员", "人员"];
|
|
73
|
+
const DEPARTMENT_QUE_TYPE_KEYWORDS = ["department", "dept", "部门"];
|
|
72
74
|
const accessToken = process.env.QINGFLOW_ACCESS_TOKEN;
|
|
73
75
|
const baseUrl = process.env.QINGFLOW_BASE_URL;
|
|
74
76
|
if (!accessToken) {
|
|
@@ -192,6 +194,15 @@ const fieldSummarySchema = z.object({
|
|
|
192
194
|
que_id: z.union([z.number(), z.string(), z.null()]),
|
|
193
195
|
que_title: z.string().nullable(),
|
|
194
196
|
que_type: z.unknown(),
|
|
197
|
+
write_format: z
|
|
198
|
+
.object({
|
|
199
|
+
kind: z.enum(["member_list", "department_list"]),
|
|
200
|
+
description: z.string(),
|
|
201
|
+
item_shape: z.record(z.string()),
|
|
202
|
+
example: z.array(z.record(z.unknown())),
|
|
203
|
+
resolution_hint: z.string()
|
|
204
|
+
})
|
|
205
|
+
.nullable(),
|
|
195
206
|
has_sub_fields: z.boolean(),
|
|
196
207
|
sub_field_count: z.number().int().nonnegative()
|
|
197
208
|
});
|
|
@@ -2388,7 +2399,8 @@ server.registerTool("qf_record_create", {
|
|
|
2388
2399
|
}, async (args) => {
|
|
2389
2400
|
try {
|
|
2390
2401
|
const parsedArgs = createInputSchema.parse(args);
|
|
2391
|
-
const
|
|
2402
|
+
const shouldFetchForm = hasWritePayload(parsedArgs.answers, parsedArgs.fields) || Boolean(parsedArgs.force_refresh_form);
|
|
2403
|
+
const form = shouldFetchForm
|
|
2392
2404
|
? await getFormCached(parsedArgs.app_key, parsedArgs.user_id, Boolean(parsedArgs.force_refresh_form))
|
|
2393
2405
|
: null;
|
|
2394
2406
|
const normalizedAnswers = resolveAnswers({
|
|
@@ -2464,16 +2476,18 @@ server.registerTool("qf_record_update", {
|
|
|
2464
2476
|
}, async (args) => {
|
|
2465
2477
|
try {
|
|
2466
2478
|
const parsedArgs = updateInputSchema.parse(args);
|
|
2467
|
-
const
|
|
2468
|
-
|
|
2479
|
+
const resolvedAppKey = parsedArgs.app_key ?? getCachedApplyAppKey(parsedArgs.apply_id);
|
|
2480
|
+
const requiresFormByTitle = needsFormResolution(parsedArgs.fields);
|
|
2481
|
+
if (requiresFormByTitle && !resolvedAppKey) {
|
|
2469
2482
|
throw missingRequiredFieldError({
|
|
2470
2483
|
field: "app_key",
|
|
2471
2484
|
tool: "qf_record_update",
|
|
2472
2485
|
fixHint: "Provide app_key when fields uses title-based keys, or switch fields to numeric que_id."
|
|
2473
2486
|
});
|
|
2474
2487
|
}
|
|
2475
|
-
const form =
|
|
2476
|
-
|
|
2488
|
+
const form = (hasWritePayload(parsedArgs.answers, parsedArgs.fields) || Boolean(parsedArgs.force_refresh_form)) &&
|
|
2489
|
+
resolvedAppKey
|
|
2490
|
+
? await getFormCached(resolvedAppKey, parsedArgs.user_id, Boolean(parsedArgs.force_refresh_form))
|
|
2477
2491
|
: null;
|
|
2478
2492
|
const normalizedAnswers = resolveAnswers({
|
|
2479
2493
|
explicitAnswers: parsedArgs.answers,
|
|
@@ -3534,7 +3548,8 @@ function buildToolSpecCatalog() {
|
|
|
3534
3548
|
tool: "qf_form_get",
|
|
3535
3549
|
required: ["app_key"],
|
|
3536
3550
|
limits: {
|
|
3537
|
-
app_key: "required string"
|
|
3551
|
+
app_key: "required string",
|
|
3552
|
+
write_format_hint: "field_summaries[].write_format is populated for member/department fields so agents can discover exact create/update payload shapes before writing."
|
|
3538
3553
|
},
|
|
3539
3554
|
aliases: collectAliasHints(["app_key", "user_id", "force_refresh"], {}),
|
|
3540
3555
|
minimal_example: {
|
|
@@ -3751,7 +3766,11 @@ function buildToolSpecCatalog() {
|
|
|
3751
3766
|
write_mode: "Provide either answers[] or fields{}",
|
|
3752
3767
|
wait_result: "optional boolean; when true, polls qf_operation_get internally and returns resolved apply_id directly",
|
|
3753
3768
|
wait_timeout_ms: "optional int (max 20000); default 5000ms",
|
|
3754
|
-
input_contract: "strict JSON only; answers must be array and fields must be object"
|
|
3769
|
+
input_contract: "strict JSON only; answers must be array and fields must be object",
|
|
3770
|
+
special_field_write_formats: {
|
|
3771
|
+
member_list: [{ userId: "u_123", userName: "张三" }],
|
|
3772
|
+
department_list: [{ deptId: 111, deptName: "销售部" }]
|
|
3773
|
+
}
|
|
3755
3774
|
},
|
|
3756
3775
|
aliases: {},
|
|
3757
3776
|
minimal_example: {
|
|
@@ -3769,7 +3788,11 @@ function buildToolSpecCatalog() {
|
|
|
3769
3788
|
write_mode: "Provide either answers[] or fields{}",
|
|
3770
3789
|
wait_result: "optional boolean; when true, polls qf_operation_get internally and returns resolved result directly",
|
|
3771
3790
|
wait_timeout_ms: "optional int (max 20000); default 5000ms",
|
|
3772
|
-
input_contract: "strict JSON only; answers must be array and fields must be object"
|
|
3791
|
+
input_contract: "strict JSON only; answers must be array and fields must be object",
|
|
3792
|
+
special_field_write_formats: {
|
|
3793
|
+
member_list: [{ userId: "u_123", userName: "张三" }],
|
|
3794
|
+
department_list: [{ deptId: 111, deptName: "销售部" }]
|
|
3795
|
+
}
|
|
3773
3796
|
},
|
|
3774
3797
|
aliases: {},
|
|
3775
3798
|
minimal_example: {
|
|
@@ -7533,8 +7556,9 @@ function normalizeRecordItem(raw, includeAnswers) {
|
|
|
7533
7556
|
return normalized;
|
|
7534
7557
|
}
|
|
7535
7558
|
function resolveAnswers(params) {
|
|
7536
|
-
const
|
|
7537
|
-
const
|
|
7559
|
+
const index = params.form ? buildFieldIndex(params.form) : null;
|
|
7560
|
+
const normalizedFromFields = resolveFieldAnswers(params.fields, index, params.tool);
|
|
7561
|
+
const normalizedExplicit = normalizeExplicitAnswers(params.explicitAnswers, index, params.tool);
|
|
7538
7562
|
const merged = new Map();
|
|
7539
7563
|
for (const answer of normalizedFromFields) {
|
|
7540
7564
|
merged.set(String(answer.queId), answer);
|
|
@@ -7547,12 +7571,12 @@ function resolveAnswers(params) {
|
|
|
7547
7571
|
}
|
|
7548
7572
|
return Array.from(merged.values());
|
|
7549
7573
|
}
|
|
7550
|
-
function normalizeExplicitAnswers(answers) {
|
|
7574
|
+
function normalizeExplicitAnswers(answers, index, tool = "qf_record_create") {
|
|
7551
7575
|
if (!answers?.length) {
|
|
7552
7576
|
return [];
|
|
7553
7577
|
}
|
|
7554
7578
|
const output = [];
|
|
7555
|
-
for (const item of answers) {
|
|
7579
|
+
for (const [itemIndex, item] of answers.entries()) {
|
|
7556
7580
|
const queId = item.que_id ?? item.queId;
|
|
7557
7581
|
if (queId === undefined || queId === null || String(queId).trim() === "") {
|
|
7558
7582
|
throw new Error("answer item requires que_id or queId");
|
|
@@ -7578,27 +7602,32 @@ function normalizeExplicitAnswers(answers) {
|
|
|
7578
7602
|
if (values === undefined) {
|
|
7579
7603
|
throw new Error(`answer item ${String(queId)} requires values or table_values`);
|
|
7580
7604
|
}
|
|
7581
|
-
|
|
7605
|
+
const field = resolveExplicitAnswerField(item, index);
|
|
7606
|
+
normalized.values = values.map((value, valueIndex) => normalizeAnswerValue(value, {
|
|
7607
|
+
field,
|
|
7608
|
+
tool,
|
|
7609
|
+
location: `answers[${itemIndex}].values[${valueIndex}]`
|
|
7610
|
+
}));
|
|
7582
7611
|
output.push(normalized);
|
|
7583
7612
|
}
|
|
7584
7613
|
return output;
|
|
7585
7614
|
}
|
|
7586
|
-
function resolveFieldAnswers(fields,
|
|
7615
|
+
function resolveFieldAnswers(fields, index, tool = "qf_record_create") {
|
|
7587
7616
|
const entries = Object.entries(fields ?? {});
|
|
7588
7617
|
if (entries.length === 0) {
|
|
7589
7618
|
return [];
|
|
7590
7619
|
}
|
|
7591
|
-
const
|
|
7620
|
+
const resolvedIndex = index ?? { byId: new Map(), byTitle: new Map() };
|
|
7592
7621
|
const answers = [];
|
|
7593
7622
|
for (const [fieldKey, fieldValue] of entries) {
|
|
7594
7623
|
let field;
|
|
7595
7624
|
if (isNumericKey(fieldKey)) {
|
|
7596
|
-
field = resolveFieldByKey(fieldKey,
|
|
7625
|
+
field = resolveFieldByKey(fieldKey, resolvedIndex);
|
|
7597
7626
|
}
|
|
7598
7627
|
else {
|
|
7599
7628
|
field = resolveFieldSelectorStrict({
|
|
7600
7629
|
fieldKey,
|
|
7601
|
-
index,
|
|
7630
|
+
index: resolvedIndex,
|
|
7602
7631
|
tool,
|
|
7603
7632
|
location: `fields.${fieldKey}`
|
|
7604
7633
|
});
|
|
@@ -7612,15 +7641,15 @@ function resolveFieldAnswers(fields, form, tool = "qf_record_create") {
|
|
|
7612
7641
|
tool,
|
|
7613
7642
|
location: `fields.${fieldKey}`,
|
|
7614
7643
|
requested: fieldKey,
|
|
7615
|
-
suggestions: buildFieldSuggestions(fieldKey,
|
|
7644
|
+
suggestions: buildFieldSuggestions(fieldKey, resolvedIndex)
|
|
7616
7645
|
}
|
|
7617
7646
|
});
|
|
7618
7647
|
}
|
|
7619
|
-
answers.push(makeAnswerFromField(field, fieldValue));
|
|
7648
|
+
answers.push(makeAnswerFromField(field, fieldValue, tool));
|
|
7620
7649
|
}
|
|
7621
7650
|
return answers;
|
|
7622
7651
|
}
|
|
7623
|
-
function makeAnswerFromField(field, value) {
|
|
7652
|
+
function makeAnswerFromField(field, value, tool = "qf_record_create") {
|
|
7624
7653
|
const base = {
|
|
7625
7654
|
queId: field.queId
|
|
7626
7655
|
};
|
|
@@ -7641,7 +7670,11 @@ function makeAnswerFromField(field, value) {
|
|
|
7641
7670
|
if ("values" in objectValue) {
|
|
7642
7671
|
return {
|
|
7643
7672
|
...base,
|
|
7644
|
-
values: asArray(objectValue.values).map((item) => normalizeAnswerValue(item
|
|
7673
|
+
values: asArray(objectValue.values).map((item, index) => normalizeAnswerValue(item, {
|
|
7674
|
+
field,
|
|
7675
|
+
tool,
|
|
7676
|
+
location: `fields.${String(field.queTitle ?? field.queId ?? "field")}.values[${index}]`
|
|
7677
|
+
}))
|
|
7645
7678
|
};
|
|
7646
7679
|
}
|
|
7647
7680
|
}
|
|
@@ -7654,10 +7687,38 @@ function makeAnswerFromField(field, value) {
|
|
|
7654
7687
|
const valueArray = Array.isArray(value) ? value : [value];
|
|
7655
7688
|
return {
|
|
7656
7689
|
...base,
|
|
7657
|
-
values: valueArray.map((item) => normalizeAnswerValue(item
|
|
7690
|
+
values: valueArray.map((item, index) => normalizeAnswerValue(item, {
|
|
7691
|
+
field,
|
|
7692
|
+
tool,
|
|
7693
|
+
location: `fields.${String(field.queTitle ?? field.queId ?? "field")}.values[${index}]`
|
|
7694
|
+
}))
|
|
7658
7695
|
};
|
|
7659
7696
|
}
|
|
7660
|
-
function
|
|
7697
|
+
function resolveExplicitAnswerField(item, index) {
|
|
7698
|
+
if (index) {
|
|
7699
|
+
const rawQueId = item.que_id ?? item.queId;
|
|
7700
|
+
if (rawQueId !== undefined && rawQueId !== null) {
|
|
7701
|
+
const hit = index.byId.get(String(normalizeQueId(rawQueId)));
|
|
7702
|
+
if (hit) {
|
|
7703
|
+
return hit;
|
|
7704
|
+
}
|
|
7705
|
+
}
|
|
7706
|
+
const rawQueTitle = item.que_title ?? item.queTitle;
|
|
7707
|
+
if (typeof rawQueTitle === "string" && rawQueTitle.trim()) {
|
|
7708
|
+
const candidates = index.byTitle.get(rawQueTitle.trim().toLowerCase()) ?? [];
|
|
7709
|
+
if (candidates.length === 1) {
|
|
7710
|
+
return candidates[0];
|
|
7711
|
+
}
|
|
7712
|
+
}
|
|
7713
|
+
}
|
|
7714
|
+
return {
|
|
7715
|
+
queId: item.que_id ?? item.queId,
|
|
7716
|
+
queTitle: item.que_title ?? item.queTitle,
|
|
7717
|
+
queType: item.que_type ?? item.queType
|
|
7718
|
+
};
|
|
7719
|
+
}
|
|
7720
|
+
function normalizeAnswerValue(value, params) {
|
|
7721
|
+
validateWriteValueShape(params?.field ?? null, value, params);
|
|
7661
7722
|
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
7662
7723
|
return {
|
|
7663
7724
|
value,
|
|
@@ -7666,6 +7727,70 @@ function normalizeAnswerValue(value) {
|
|
|
7666
7727
|
}
|
|
7667
7728
|
return value;
|
|
7668
7729
|
}
|
|
7730
|
+
function validateWriteValueShape(field, value, params) {
|
|
7731
|
+
const writeFormat = field ? inferFieldWriteFormat(field) : null;
|
|
7732
|
+
if (!writeFormat) {
|
|
7733
|
+
return;
|
|
7734
|
+
}
|
|
7735
|
+
const validatedField = field;
|
|
7736
|
+
if (!validatedField) {
|
|
7737
|
+
return;
|
|
7738
|
+
}
|
|
7739
|
+
if (writeFormat.kind === "member_list" && !isValidMemberWriteValue(value)) {
|
|
7740
|
+
throw fieldValueFormatError({
|
|
7741
|
+
field: validatedField,
|
|
7742
|
+
writeFormat,
|
|
7743
|
+
value,
|
|
7744
|
+
tool: params?.tool,
|
|
7745
|
+
location: params?.location
|
|
7746
|
+
});
|
|
7747
|
+
}
|
|
7748
|
+
if (writeFormat.kind === "department_list" && !isValidDepartmentWriteValue(value)) {
|
|
7749
|
+
throw fieldValueFormatError({
|
|
7750
|
+
field: validatedField,
|
|
7751
|
+
writeFormat,
|
|
7752
|
+
value,
|
|
7753
|
+
tool: params?.tool,
|
|
7754
|
+
location: params?.location
|
|
7755
|
+
});
|
|
7756
|
+
}
|
|
7757
|
+
}
|
|
7758
|
+
function isValidMemberWriteValue(value) {
|
|
7759
|
+
const obj = asObject(value);
|
|
7760
|
+
return Boolean(obj && asNullableString(obj.userId)?.trim());
|
|
7761
|
+
}
|
|
7762
|
+
function isValidDepartmentWriteValue(value) {
|
|
7763
|
+
const obj = asObject(value);
|
|
7764
|
+
if (!obj) {
|
|
7765
|
+
return false;
|
|
7766
|
+
}
|
|
7767
|
+
const deptId = obj.deptId;
|
|
7768
|
+
return ((typeof deptId === "string" && deptId.trim().length > 0) ||
|
|
7769
|
+
(typeof deptId === "number" && Number.isFinite(deptId)));
|
|
7770
|
+
}
|
|
7771
|
+
function fieldValueFormatError(params) {
|
|
7772
|
+
const fieldLabel = asNullableString(params.field.queTitle)?.trim() ||
|
|
7773
|
+
String(params.field.queId ?? "unknown_field");
|
|
7774
|
+
const formatName = params.writeFormat.kind === "member_list" ? "成员字段" : "部门字段";
|
|
7775
|
+
return new InputValidationError({
|
|
7776
|
+
message: `${formatName} "${fieldLabel}" 的写入值格式不正确`,
|
|
7777
|
+
errorCode: "FIELD_VALUE_FORMAT_ERROR",
|
|
7778
|
+
fixHint: params.writeFormat.kind === "member_list"
|
|
7779
|
+
? '传对象数组,例如 [{"userId":"u_123","userName":"张三"}];不要传纯字符串或 user_id。'
|
|
7780
|
+
: '传对象数组,例如 [{"deptId":111,"deptName":"销售部"}];不要传纯字符串或 dept_id。',
|
|
7781
|
+
details: {
|
|
7782
|
+
tool: params.tool ?? "qf_record_create",
|
|
7783
|
+
location: params.location ?? null,
|
|
7784
|
+
field: {
|
|
7785
|
+
que_id: params.field.queId ?? null,
|
|
7786
|
+
que_title: asNullableString(params.field.queTitle),
|
|
7787
|
+
que_type: params.field.queType ?? null
|
|
7788
|
+
},
|
|
7789
|
+
expected_format: params.writeFormat,
|
|
7790
|
+
received_value: params.value
|
|
7791
|
+
}
|
|
7792
|
+
});
|
|
7793
|
+
}
|
|
7669
7794
|
function needsFormResolution(fields) {
|
|
7670
7795
|
const keys = Object.keys(fields ?? {});
|
|
7671
7796
|
if (!keys.length) {
|
|
@@ -7697,11 +7822,95 @@ function extractFieldSummaries(form) {
|
|
|
7697
7822
|
que_id: field.queId ?? null,
|
|
7698
7823
|
que_title: asNullableString(field.queTitle),
|
|
7699
7824
|
que_type: field.queType,
|
|
7825
|
+
write_format: inferFieldWriteFormat(field),
|
|
7700
7826
|
has_sub_fields: sub.length > 0,
|
|
7701
7827
|
sub_field_count: sub.length
|
|
7702
7828
|
};
|
|
7703
7829
|
});
|
|
7704
7830
|
}
|
|
7831
|
+
function inferFieldWriteFormat(field) {
|
|
7832
|
+
const kind = inferFieldWriteFormatKind(field.queType);
|
|
7833
|
+
if (kind === "member_list") {
|
|
7834
|
+
return {
|
|
7835
|
+
kind,
|
|
7836
|
+
description: "Pass one or more member objects in values[]. Each item must contain userId.",
|
|
7837
|
+
item_shape: {
|
|
7838
|
+
userId: "string",
|
|
7839
|
+
userName: "string? (recommended)",
|
|
7840
|
+
name: "string? (accepted alias)"
|
|
7841
|
+
},
|
|
7842
|
+
example: [
|
|
7843
|
+
{
|
|
7844
|
+
userId: "u_123",
|
|
7845
|
+
userName: "张三"
|
|
7846
|
+
}
|
|
7847
|
+
],
|
|
7848
|
+
resolution_hint: "Use qf_users_list or qf_department_users_list to resolve valid userId values before writing."
|
|
7849
|
+
};
|
|
7850
|
+
}
|
|
7851
|
+
if (kind === "department_list") {
|
|
7852
|
+
return {
|
|
7853
|
+
kind,
|
|
7854
|
+
description: "Pass one or more department objects in values[]. Each item must contain deptId.",
|
|
7855
|
+
item_shape: {
|
|
7856
|
+
deptId: "string|number",
|
|
7857
|
+
deptName: "string? (recommended)",
|
|
7858
|
+
name: "string? (accepted alias)"
|
|
7859
|
+
},
|
|
7860
|
+
example: [
|
|
7861
|
+
{
|
|
7862
|
+
deptId: 111,
|
|
7863
|
+
deptName: "销售部"
|
|
7864
|
+
}
|
|
7865
|
+
],
|
|
7866
|
+
resolution_hint: "Use qf_departments_list to resolve valid deptId values before writing."
|
|
7867
|
+
};
|
|
7868
|
+
}
|
|
7869
|
+
return null;
|
|
7870
|
+
}
|
|
7871
|
+
function inferFieldWriteFormatKind(queType) {
|
|
7872
|
+
const tokens = collectQueTypeTokens(queType);
|
|
7873
|
+
if (tokens.some((token) => MEMBER_QUE_TYPE_KEYWORDS.some((keyword) => token.includes(keyword)))) {
|
|
7874
|
+
return "member_list";
|
|
7875
|
+
}
|
|
7876
|
+
if (tokens.some((token) => DEPARTMENT_QUE_TYPE_KEYWORDS.some((keyword) => token.includes(keyword)))) {
|
|
7877
|
+
return "department_list";
|
|
7878
|
+
}
|
|
7879
|
+
return null;
|
|
7880
|
+
}
|
|
7881
|
+
function collectQueTypeTokens(queType) {
|
|
7882
|
+
const tokens = new Set();
|
|
7883
|
+
const queue = [queType];
|
|
7884
|
+
while (queue.length > 0) {
|
|
7885
|
+
const current = queue.shift();
|
|
7886
|
+
if (current === null || current === undefined) {
|
|
7887
|
+
continue;
|
|
7888
|
+
}
|
|
7889
|
+
if (typeof current === "string") {
|
|
7890
|
+
const normalized = current.trim().toLowerCase();
|
|
7891
|
+
if (normalized) {
|
|
7892
|
+
tokens.add(normalized);
|
|
7893
|
+
}
|
|
7894
|
+
continue;
|
|
7895
|
+
}
|
|
7896
|
+
if (typeof current === "number" || typeof current === "boolean") {
|
|
7897
|
+
tokens.add(String(current));
|
|
7898
|
+
continue;
|
|
7899
|
+
}
|
|
7900
|
+
if (Array.isArray(current)) {
|
|
7901
|
+
queue.push(...current);
|
|
7902
|
+
continue;
|
|
7903
|
+
}
|
|
7904
|
+
const obj = asObject(current);
|
|
7905
|
+
if (!obj) {
|
|
7906
|
+
continue;
|
|
7907
|
+
}
|
|
7908
|
+
for (const value of Object.values(obj)) {
|
|
7909
|
+
queue.push(value);
|
|
7910
|
+
}
|
|
7911
|
+
}
|
|
7912
|
+
return Array.from(tokens);
|
|
7913
|
+
}
|
|
7705
7914
|
function buildFieldIndex(form) {
|
|
7706
7915
|
const byId = new Map();
|
|
7707
7916
|
const byTitle = new Map();
|
|
@@ -8442,6 +8651,48 @@ function buildErrorExampleCalls(params) {
|
|
|
8442
8651
|
}
|
|
8443
8652
|
];
|
|
8444
8653
|
}
|
|
8654
|
+
if (errorCode === "FIELD_VALUE_FORMAT_ERROR") {
|
|
8655
|
+
const expectedFormat = asObject(params.details?.expected_format);
|
|
8656
|
+
const kind = asNullableString(expectedFormat?.kind);
|
|
8657
|
+
if (kind === "member_list") {
|
|
8658
|
+
return [
|
|
8659
|
+
{
|
|
8660
|
+
tool: "qf_form_get",
|
|
8661
|
+
arguments: {
|
|
8662
|
+
app_key: appKey
|
|
8663
|
+
},
|
|
8664
|
+
note: "先查看字段 write_format,确认成员字段写入 shape"
|
|
8665
|
+
},
|
|
8666
|
+
{
|
|
8667
|
+
tool: "qf_department_users_list",
|
|
8668
|
+
arguments: {
|
|
8669
|
+
dept_id: 111,
|
|
8670
|
+
fetch_child: false
|
|
8671
|
+
},
|
|
8672
|
+
note: "查询有效成员 userId,再按 [{userId,userName}] 写入"
|
|
8673
|
+
}
|
|
8674
|
+
];
|
|
8675
|
+
}
|
|
8676
|
+
if (kind === "department_list") {
|
|
8677
|
+
return [
|
|
8678
|
+
{
|
|
8679
|
+
tool: "qf_form_get",
|
|
8680
|
+
arguments: {
|
|
8681
|
+
app_key: appKey
|
|
8682
|
+
},
|
|
8683
|
+
note: "先查看字段 write_format,确认部门字段写入 shape"
|
|
8684
|
+
},
|
|
8685
|
+
{
|
|
8686
|
+
tool: "qf_departments_list",
|
|
8687
|
+
arguments: {
|
|
8688
|
+
keyword: "销售",
|
|
8689
|
+
limit: 20
|
|
8690
|
+
},
|
|
8691
|
+
note: "查询有效部门 deptId,再按 [{deptId,deptName}] 写入"
|
|
8692
|
+
}
|
|
8693
|
+
];
|
|
8694
|
+
}
|
|
8695
|
+
}
|
|
8445
8696
|
if (errorCode === "INTERNAL_ERROR" || errorCode === "UNKNOWN_ERROR") {
|
|
8446
8697
|
return [
|
|
8447
8698
|
{
|