feat: add local approval submission drafts
This commit is contained in:
@@ -19,12 +19,14 @@ test("legacy updater post-install syncs the complete bundled Skill", () => {
|
||||
mkdirSync(target, { recursive: true });
|
||||
writeFileSync(join(bundled, "SKILL.md"), "new skill");
|
||||
writeFileSync(join(bundled, "references", "create-approval.md"), "create rules");
|
||||
writeFileSync(join(bundled, "references", "local-drafts.md"), "draft 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.equal(readFileSync(join(target, "references", "local-drafts.md"), "utf8"), "draft rules");
|
||||
assert.deepEqual(readdirSync(join(root, "client", "skill")), ["dingtalk-approval"]);
|
||||
});
|
||||
|
||||
@@ -245,6 +247,7 @@ test("MCP approval_login returns both portable text and a standard resource link
|
||||
const instructions = client.getInstructions() ?? "";
|
||||
assert.match(instructions, /登录链接过期.*approval_login.*绝不.*重新安装/);
|
||||
assert.match(instructions, /审批引用.*调用容量.*内部分批.*不得向用户解释/);
|
||||
assert.match(instructions, /提单草稿仅可保存在 Agent 所在机器.*不得上传网关.*实时模板 Schema/s);
|
||||
assert.doesNotMatch(instructions, /最多.*20|selectionId|approvalId|targetId/);
|
||||
const history = tools.tools.find((tool) => tool.name === "approval_list_history")!;
|
||||
assert.equal((history.inputSchema as any).properties.startDate.default, "");
|
||||
|
||||
@@ -389,6 +389,7 @@ test("generic installer preserves identity across safe updates, rollback, downgr
|
||||
assert.equal(existsSync(paths.bridgeCommand), true);
|
||||
assert.equal(existsSync(paths.skillFile), true);
|
||||
assert.equal(existsSync(join(paths.skillDir, "references", "create-approval.md")), true);
|
||||
assert.equal(existsSync(join(paths.skillDir, "references", "local-drafts.md")), true);
|
||||
const state = JSON.parse(readFileSync(paths.stateFile, "utf8"));
|
||||
assert.equal(state.version, "1.1.0-rc.5");
|
||||
assert.deepEqual(state.manifestMirrors, [`${mirrorBase}manifest.json`]);
|
||||
@@ -654,6 +655,7 @@ const skill = join(prefix, "node_modules", "dingtalk-approval-client", "skill",
|
||||
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");
|
||||
writeFileSync(join(skill, "references", "local-drafts.md"), "# Local drafts\\n");
|
||||
`);
|
||||
if (process.platform === "win32") {
|
||||
writeFileSync(join(bin, "npm.cmd"), "@echo off\r\nexit /b 99\r\n");
|
||||
|
||||
@@ -36,6 +36,7 @@ test("customer distribution prefers remote MCP OAuth and keeps a generic compati
|
||||
assert.match(compatibilityInstaller, /90 秒[\s\S]*stderr[\s\S]*stdout/);
|
||||
assert.match(compatibilityInstaller, /登录链接过期.*绝不能重新安装.*approval_login.*loginCommand/s);
|
||||
assert.match(compatibilityInstaller, /sourceStatus[\s\S]*dingtalk\.status[\s\S]*xft\.status[\s\S]*都必须是 `ok`/);
|
||||
assert.match(compatibilityInstaller, /references\/local-drafts\.md/);
|
||||
assert.match(compatibilityInstaller, /更新[\s\S]*update --json[\s\S]*卸载[\s\S]*uninstall --yes --json/);
|
||||
assert.doesNotMatch(compatibilityInstaller, /TimeoutSec 300|--max-time 300|--timeout=300|至少 10 分钟超时/);
|
||||
assert.doesNotMatch(compatibilityInstaller, /--host|WorkBuddy|Codex|Claude/);
|
||||
@@ -80,7 +81,9 @@ test("customer distribution prefers remote MCP OAuth and keeps a generic compati
|
||||
|
||||
const skill = readFileSync("skills/dingtalk-approval/SKILL.md", "utf8");
|
||||
const createApproval = readFileSync("skills/dingtalk-approval/references/create-approval.md", "utf8");
|
||||
const localDrafts = readFileSync("skills/dingtalk-approval/references/local-drafts.md", "utf8");
|
||||
assert.match(skill, /read `references\/create-approval\.md` completely/);
|
||||
assert.match(skill, /read `references\/local-drafts\.md` completely/);
|
||||
assert.doesNotMatch(skill, /XFT recall is experimental/);
|
||||
assert.match(createApproval, /XFT recall is experimental.*XFTSYS9999/s);
|
||||
assert.match(skill, /native remote-MCP OAuth flow when the user can complete its registered callback/);
|
||||
@@ -90,6 +93,18 @@ test("customer distribution prefers remote MCP OAuth and keeps a generic compati
|
||||
assert.match(skill, /authChallenge\.directUrl.*current reply/s);
|
||||
assert.match(skill, /onboarding\.firstRun=true.*Do not execute an example automatically/s);
|
||||
assert.match(skill, /link expires.*approval_login.*Never reinstall/s);
|
||||
assert.match(localDrafts, /%LOCALAPPDATA%\\DingTalkApproval\\drafts/);
|
||||
assert.match(localDrafts, /Library\/Application Support\/DingTalkApproval\/drafts/);
|
||||
assert.match(localDrafts, /\.local\/share.*dingtalk-approval\/drafts/s);
|
||||
assert.match(localDrafts, /mode `0700`.*mode `0600`/s);
|
||||
assert.match(localDrafts, /atomically rename/);
|
||||
assert.match(localDrafts, /more than 90 days old/);
|
||||
assert.match(localDrafts, /SHA-256/);
|
||||
assert.match(localDrafts, /Never store[\s\S]*templateId[\s\S]*confirmationId[\s\S]*complete upstream schema/);
|
||||
assert.match(localDrafts, /outcome_uncertain[\s\S]*freeze[\s\S]*Never prepare or submit it again automatically/);
|
||||
assert.match(localDrafts, /never on the approval gateway/);
|
||||
assert.match(localDrafts, /local attachment path.*never a valid upstream attachment field value.*native client/s);
|
||||
assert.match(createApproval, /Local file paths.*not valid attachment field values.*native client/s);
|
||||
|
||||
const builder = readFileSync("scripts/build-release.mjs", "utf8");
|
||||
assert.match(builder, /schemaVersion: 3,\s+version,/s);
|
||||
|
||||
@@ -46,6 +46,12 @@ 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() ?? "", /近30天.*allowDuplicateInvoices=true/s);
|
||||
assert.match(client.getInstructions() ?? "", /草稿只能由 Agent 保存在.*当前 OS 用户私有数据目录/s);
|
||||
assert.match(client.getInstructions() ?? "", /schemaVersion=1.*dingtalk-approval-local-draft.*submit_uncertain/s);
|
||||
assert.match(client.getInstructions() ?? "", /不得上传网关.*不得.*完整上游 Schema/s);
|
||||
assert.match(client.getInstructions() ?? "", /恢复时.*实时读取 Schema/s);
|
||||
assert.match(client.getInstructions() ?? "", /自动清理超过 90 天/s);
|
||||
assert.match(client.getInstructions() ?? "", /outcome_uncertain.*冻结.*禁止自动重试/s);
|
||||
|
||||
const tools = await client.listTools();
|
||||
assert.deepEqual(tools.tools.map((tool) => tool.name), [
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
import { APPLICATION_VERSION } from "../src/version.js";
|
||||
|
||||
test("package, gateway, and bridge share one application version", () => {
|
||||
const packageMetadata = JSON.parse(readFileSync("package.json", "utf8")) as { version: string };
|
||||
assert.equal(APPLICATION_VERSION, packageMetadata.version);
|
||||
for (const path of ["src/server.ts", "src/bridge.ts"]) {
|
||||
const source = readFileSync(path, "utf8");
|
||||
assert.match(source, /version: APPLICATION_VERSION/);
|
||||
assert.doesNotMatch(source, /version: "\d+\.\d+\.\d+(?:-rc\.\d+)?"/);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user