refactor: reduce approval context overhead
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 03:25:06 +00:00
parent 88f221e811
commit ac7be79288
12 changed files with 136 additions and 43 deletions
+23 -2
View File
@@ -1,11 +1,32 @@
import assert from "node:assert/strict";
import { mkdtempSync, readFileSync, statSync, writeFileSync } from "node:fs";
import { mkdirSync, mkdtempSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { APPROVAL_ONBOARDING_EXAMPLES, RemoteBridge, bridgeAuthToolResult, createBridgeMcpServer } from "../src/bridge.js";
import {
APPROVAL_ONBOARDING_EXAMPLES, RemoteBridge, bridgeAuthToolResult, createBridgeMcpServer,
syncBundledSkillForLegacyUpdate,
} from "../src/bridge.js";
test("legacy updater post-install syncs the complete bundled Skill", () => {
const root = mkdtempSync(join(tmpdir(), "ding-bridge-skill-sync-"));
const bundled = join(root, "package", "skill", "dingtalk-approval");
const statePath = join(root, "client", "state", "session.json");
const target = join(root, "client", "skill", "dingtalk-approval");
mkdirSync(join(bundled, "references"), { recursive: true });
mkdirSync(target, { recursive: true });
writeFileSync(join(bundled, "SKILL.md"), "new skill");
writeFileSync(join(bundled, "references", "create-approval.md"), "create rules");
writeFileSync(join(target, "SKILL.md"), "old skill");
syncBundledSkillForLegacyUpdate(statePath, bundled);
assert.equal(readFileSync(join(target, "SKILL.md"), "utf8"), "new skill");
assert.equal(readFileSync(join(target, "references", "create-approval.md"), "utf8"), "create rules");
assert.deepEqual(readdirSync(join(root, "client", "skill")), ["dingtalk-approval"]);
});
test("stdio bridge creates a messenger-friendly device login challenge and protects local state", async () => {
const calls: string[] = [];
+5
View File
@@ -130,6 +130,7 @@ test("generic installer preserves identity across safe updates, rollback, downgr
const paths = clientPaths({ DINGTALK_APPROVAL_CLIENT_HOME: appRoot }, process.platform, join(root, "home"));
assert.equal(existsSync(paths.bridgeCommand), true);
assert.equal(existsSync(paths.skillFile), true);
assert.equal(existsSync(join(paths.skillDir, "references", "create-approval.md")), true);
const state = JSON.parse(readFileSync(paths.stateFile, "utf8"));
assert.equal(state.version, "1.1.0-rc.5");
assert.equal(state.host, undefined);
@@ -340,6 +341,10 @@ const target = join(prefix, "node_modules", "dingtalk-approval-client", "dist",
mkdirSync(target, { recursive: true });
writeFileSync(join(target, "bridge.js"), ${JSON.stringify(fakeBridge)});
writeFileSync(join(target, "client-installer.js"), "#!/usr/bin/env node\\n");
const skill = join(prefix, "node_modules", "dingtalk-approval-client", "skill", "dingtalk-approval");
mkdirSync(join(skill, "references"), { recursive: true });
writeFileSync(join(skill, "SKILL.md"), "---\\nname: dingtalk-approval\\ndescription: Bundled test\\n---\\n");
writeFileSync(join(skill, "references", "create-approval.md"), "# Create approval\\n");
`);
if (process.platform === "win32") {
writeFileSync(join(bin, "npm.cmd"), "@echo off\r\nexit /b 99\r\n");
+5 -1
View File
@@ -20,7 +20,10 @@ test("customer distribution is a generic single-login gateway bridge", () => {
assert.doesNotMatch(installer, /DWS_CLI_PATH|dingtalk-workspace-cli|APP_MODE=local/);
const skill = readFileSync("skills/dingtalk-approval/SKILL.md", "utf8");
assert.match(skill, /XFT recall is experimental.*XFTSYS9999/s);
const createApproval = readFileSync("skills/dingtalk-approval/references/create-approval.md", "utf8");
assert.match(skill, /read `references\/create-approval\.md` completely/);
assert.doesNotMatch(skill, /XFT recall is experimental/);
assert.match(createApproval, /XFT recall is experimental.*XFTSYS9999/s);
assert.match(skill, /does not send or push.*current user-facing reply/s);
assert.match(skill, /handle the installer's `postInstall` result immediately.*waiting for the user's first approval request/s);
assert.match(skill, /onboarding\.firstRun=true.*Do not execute an example automatically/s);
@@ -29,6 +32,7 @@ test("customer distribution is a generic single-login gateway bridge", () => {
assert.match(builder, /schemaVersion: 3,\s+version,/s);
assert.match(builder, /signData\(null, manifestBytes/);
assert.match(builder, /dingtalk-approval-client/);
assert.match(builder, /cpSync\(resolve\("skills\/dingtalk-approval"\).*recursive: true/s);
assert.doesNotMatch(builder, /codexPlugin|codex-plugin|\.codex-plugin|openai\.yaml/);
assert.doesNotMatch(builder, /dingtalk-workspace-cli|DWS_URL|DWS_SHA256/);
});
+12 -6
View File
@@ -140,8 +140,11 @@ test("unified approval API returns opaque user-bound IDs and routes DingTalk act
const listed = await service.listTodos(user, "all", 50);
const approvalId = String(listed.items[0]!.approvalId);
assert.deepEqual(listed.items[0]!.amount, { value: 100, currency: "CNY" });
assert.equal(listed.items[0]!.submittedAtLocal, "2026-08-11T09:00:00.000+08:00");
assert.equal(listed.items[0]!.submittedTimeZone, "Asia/Shanghai");
assert.equal(listed.items[0]!.submittedAt, "2026-08-11T09:00:00.000+08:00");
assert.equal("submittedAtLocal" in listed.items[0]!, false);
assert.equal("submittedTimeZone" in listed.items[0]!, false);
assert.equal("sourceType" in listed.items[0]!, false);
assert.equal("actionsHint" in listed.items[0]!, false);
assert.match(approvalId, /^v4\./);
assert.ok(approvalId.length <= 128, `common DingTalk approvalId should be compact, got ${approvalId.length} characters`);
assert.doesNotMatch(approvalId, /instance-secret|task-secret/);
@@ -247,12 +250,15 @@ test("combined todo lists are deterministic regardless of source response timing
} as unknown as ApprovalService;
const xft = {
async listMyTodos() {
return { items: [{ source: "xft", billId: "2026081300000001", title: "XFT" }] };
return { items: [{ source: "xft", billId: "2026081300000001", title: "XFT", applyTime: "2026-08-13T01:30:00.000Z" }] };
},
};
const service = new UnifiedApprovalService(config(), ding, xft as never);
const listed = await service.listTodos({ userId: "user-1" }, "all", 2);
assert.deepEqual(listed.items.map((item) => item.source), ["dingtalk", "xft"]);
assert.equal(listed.items[1]!.submittedAt, "2026-08-13T09:30:00.000+08:00");
assert.equal("submittedAtLocal" in listed.items[1]!, false);
assert.equal("submittedTimeZone" in listed.items[1]!, false);
});
test("v4 references remain backward compatible with unexpired v2 and v3 approvals", async () => {
@@ -309,8 +315,8 @@ test("v4 approval references stay compact for representative DingTalk, Yida, and
};
const service = new UnifiedApprovalService(config(), ding, xft as never);
const listed = await service.listTodos({ userId: "user-1" }, "all", 10);
const official = String(listed.items.find((item) => item.sourceType === "official_oa")?.approvalId);
const yida = String(listed.items.find((item) => item.sourceType === "yida")?.approvalId);
const official = String(listed.items.find((item) => item.businessNo === "5VXimwfMSBSWhjzttVCyWA10041785394274")?.approvalId);
const yida = String(listed.items.find((item) => item.businessNo === "FINST-7b7ffc7800d942b7b8f6e70e16efab34")?.approvalId);
const xftId = String(listed.items.find((item) => item.source === "xft")?.approvalId);
assert.match(official, /^v4\./);
assert.ok(official.length <= 128, `DingTalk reference is ${official.length} characters`);
@@ -589,7 +595,7 @@ test("unified OA return signs live targets, revalidates them, and confirms task
const user = { userId: "user-1", name: "测试用户乙" };
const listed = await service.listTodos(user, "dingtalk", 10);
assert.deepEqual(listed.items[0]!.availableActions, ["approve", "reject"]);
assert.match(String(listed.items[0]!.actionsHint), /approval_get_actions/);
assert.equal("actionsHint" in listed.items[0]!, false);
const approvalId = String(listed.items[0]!.approvalId);
const actions = await service.getActions(user, approvalId);
const returnAction = actions.actions[2]! as {