feat: add standalone login renewal
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 08:04:37 +00:00
parent ac7be79288
commit b3fa2315ba
11 changed files with 118 additions and 19 deletions
+26 -5
View File
@@ -179,6 +179,7 @@ export async function runClientInstaller(
assertSafeClientRoot(paths.root, context.home);
let result: Record<string, unknown>;
if (args.command === "install" || args.command === "update") result = await installClient(paths, args, context);
else if (args.command === "login") result = loginClient(paths, context);
else if (args.command === "uninstall") result = uninstallClient(paths, args, context);
else if (args.command === "print-path") result = printPath(paths, args);
else if (args.command === "print-config") result = printConfig(paths, args);
@@ -232,6 +233,7 @@ function parseClientArgs(argv: string[]): ParsedArgs {
}
const allowed = new Set(command === "install" || command === "update"
? ["--manifest-url", "--public-key", "--app-root", "--json", "--allow-downgrade"]
: command === "login" ? ["--app-root", "--json"]
: command === "uninstall" ? ["--app-root", "--json", "--yes"]
: command === "print-path" ? ["--app-root", "--json"]
: command === "print-config" ? ["--app-root", "--json", "--format"]
@@ -319,7 +321,7 @@ async function installClient(paths: ClientPaths, args: ParsedArgs, context: Comm
const legacyCleanupFailed: string[] = [];
const legacyRemoved = cleanupLegacyInstallations(context.env, context.platform, context.home, legacyCleanupFailed);
cleanupOldVersions(paths, versionDir);
const postInstall = attemptPostInstallLogin(clientEntry, manifest.gatewayUrl, paths, context);
const postInstall = attemptLogin(clientEntry, manifest.gatewayUrl, paths, context);
return installResult(paths, state, legacyRemoved, legacyCleanupFailed, postInstall);
} finally {
rmSync(temporary, { recursive: true, force: true });
@@ -483,6 +485,7 @@ function installResult(
"立即处理 postInstall:若有 authChallenge.directUrl,必须在当前回复中原样展示,不得声称已另行推送,也不得等待用户首次查询审批。",
"用户授权后调用 approval_login;登录成功后按 verifyTool 做一次双来源只读验证,并展示一次性使用示例。",
"宿主若不能热加载 MCP,请新建会话或完整重启;恢复上述登录流程后再宣告安装完成,不得执行任何审批写操作。",
"登录链接过期时绝不重新安装:MCP 已加载就再次调用 approval_login;尚未加载就执行 loginCommand。",
];
return {
ok: true,
@@ -491,17 +494,35 @@ function installResult(
root: paths.root,
bridgePath: paths.bridgeCommand,
cliPath: paths.cliCommand,
loginCommand: loginCommand(paths),
skillPath: paths.skillDir,
mcpConfig: mcpConfig(paths),
legacyEntriesRemoved: legacyRemoved,
legacyCleanupFailed,
configurationModified: false,
postInstall,
postInstall: { ...postInstall, loginCommand: loginCommand(paths) },
nextSteps,
};
}
function attemptPostInstallLogin(
function loginClient(paths: ClientPaths, context: Pick<CommandContext, "env">): Record<string, unknown> {
const state = assertInstalled(paths);
const runtime = resolveInside(paths.root, state.runtimeDir);
const clientEntry = join(runtime, "node_modules", "dingtalk-approval-client", "dist", "src", "bridge.js");
if (!existsSync(clientEntry)) throw new Error("客户端运行时缺少 Bridge 入口,请运行 update 修复安装");
return {
ok: true,
operation: "login",
...attemptLogin(clientEntry, state.gatewayUrl, paths, context),
loginCommand: loginCommand(paths),
};
}
function loginCommand(paths: ClientPaths): { command: string; args: ["login", "--json"] } {
return { command: paths.cliCommand, args: ["login", "--json"] };
}
function attemptLogin(
clientEntry: string,
gatewayUrl: string,
paths: ClientPaths,
@@ -514,7 +535,7 @@ function attemptPostInstallLogin(
verifyTool: { name: "approval_list_todos" as const, arguments: { source: "all" as const, query: "" as const, limit: 1 as const } },
completionCriteria: "登录确认并完成钉钉 OA 与薪福通双来源只读验证",
};
const result = spawnSync(process.execPath, [clientEntry, "--url", gatewayUrl, "--post-install-login"], {
const result = spawnSync(process.execPath, [clientEntry, "--url", gatewayUrl, "--login"], {
encoding: "utf8",
timeout: 30_000,
maxBuffer: 256 * 1024,
@@ -815,7 +836,7 @@ function writeResult(value: Record<string, unknown>, args: ParsedArgs, context:
}
function usage(): string {
return "用法:dingtalk-approval install|update|uninstall|print-path|print-config|doctor [--manifest-url URL] [--format mcp-json] [--json]";
return "用法:dingtalk-approval install|update|login|uninstall|print-path|print-config|doctor [--manifest-url URL] [--format mcp-json] [--json]";
}
if (isClientDirectEntrypoint(import.meta.url, process.argv[1])) {