fix: keep approval batching internal
CI / Client installer (macos-latest) (push) Canceled after 0s
CI / Client installer (ubuntu-latest) (push) Canceled after 0s
CI / Client installer (windows-latest) (push) Canceled after 0s
CI / test (22) (push) Canceled after 0s
CI / test (24) (push) Canceled after 0s
CI / release-smoke (push) Canceled after 0s

This commit is contained in:
2026-08-18 01:55:34 +00:00
parent 504145aec6
commit 1d1c7e1d8a
7 changed files with 22 additions and 25 deletions
+1 -1
View File
@@ -193,7 +193,7 @@ test("MCP approval_login returns both portable text and a standard resource link
assert.equal((history.inputSchema as any).properties.endDate.default, "");
const batch = tools.tools.find((tool) => tool.name === "approval_process_batch")!;
assert.equal((batch.inputSchema as any).properties.items.maxItems, 20);
assert.match(batch.description ?? "", /最多 20 条.*零写入/s);
assert.match(batch.description ?? "", /Schema容量.*不向用户解释.*任一无效则零写入/s);
} finally {
await client.close();
await server.close();
+3 -2
View File
@@ -43,7 +43,8 @@ test("MCP initialization teaches the workflow while tools/list stays compact", a
assert.match(client.getInstructions() ?? "", /非必填字段.*是否补充/s);
assert.match(client.getInstructions() ?? "", /出差申请.*外出.*业务招待申请.*差旅费用报销.*业务招待费用报销/s);
assert.match(client.getInstructions() ?? "", /revokeId.*approval_revoke_submission/);
assert.match(client.getInstructions() ?? "", /最多(?:处理 )?20 条.*零写入.*禁止自动.*重试/s);
assert.match(client.getInstructions() ?? "", /单次条数限制.*内部调用细节.*绝不向用户.*一键全选.*如何分组/s);
assert.match(client.getInstructions() ?? "", /用户只是在查看.*业务信息.*不主动讨论.*自动分批.*不得造成重复确认/s);
assert.match(client.getInstructions() ?? "", /近30天.*allowDuplicateInvoices=true/s);
const tools = await client.listTools();
@@ -73,7 +74,7 @@ test("MCP initialization teaches the workflow while tools/list stays compact", a
assert.match(historySchema.properties.startDate.description, /最近30天/);
assert.match(descriptions.approval_process_batch ?? "", /用户明确.*不二次确认/);
assert.match(descriptions.approval_process_batch ?? "", /succeeded.*completed.*followUpRequired/);
assert.match(descriptions.approval_process_batch ?? "", /最多 20 条.*任一无效.*零写入/s);
assert.match(descriptions.approval_process_batch ?? "", /Schema容量.*不得转述给用户.*任一无效.*零写入/s);
assert.match(descriptions.approval_prepare_submission ?? "", /明确确认|确认/);
assert.match(descriptions.approval_get_submission_schema ?? "", /必填字段齐全.*可选字段/);
assert.match(descriptions.approval_get_submission_options ?? "", /承担部门.*城市编码/);
+11 -9
View File
@@ -209,7 +209,7 @@ test("selectionId processes exactly the confirmed live result set and rejects dr
assert.equal(writes, 2);
});
test("todo lists explicitly describe the fixed 20-item batch policy", async () => {
test("todo lists keep transport batch policy internal", async () => {
const user = { userId: "user-1" };
const manyDing = {
async listMyTodos() {
@@ -219,21 +219,23 @@ test("todo lists explicitly describe the fixed 20-item batch policy", async () =
},
} as unknown as ApprovalService;
const overflow = await new UnifiedApprovalService({ ...config(), batchLimit: 99 }, manyDing).listTodos(user, "dingtalk", 30);
assert.deepEqual(overflow.batchPolicy, {
maxItemsPerCall: 20,
selectionAvailable: false,
selectionUnavailableReason: "result_exceeds_batch_limit",
overflowGuidance: "一次最多处理 20 条。超过时可从同一次列表结果中选取 approvalId 分组提交;每组仍须经用户明确授权。",
});
assert.equal(overflow.returned, 21);
assert.equal("selectionId" in overflow, false);
assert.equal("selectionUnavailableReason" in overflow, false);
assert.equal("selectionHint" in overflow, false);
assert.equal("batchPolicy" in overflow, false);
const emptyDing = { async listMyTodos() { return { items: [] }; } } as unknown as ApprovalService;
const empty = await new UnifiedApprovalService(config(), emptyDing).listTodos(user, "dingtalk", 10);
assert.equal(empty.batchPolicy.selectionUnavailableReason, "empty_result");
assert.equal("selectionId" in empty, false);
assert.equal("selectionUnavailableReason" in empty, false);
assert.equal("batchPolicy" in empty, false);
const xftUnavailable = { async listMyTodos() { throw new Error("XFT unavailable"); } };
const partial = await new UnifiedApprovalService(config(), manyDing, xftUnavailable as never).listTodos(user, "all", 1);
assert.equal(partial.batchPolicy.selectionUnavailableReason, "requested_source_unhealthy");
assert.equal("selectionId" in partial, false);
assert.equal("selectionUnavailableReason" in partial, false);
assert.equal("batchPolicy" in partial, false);
});
test("combined todo lists are deterministic regardless of source response timing", async () => {