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
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:
+6
-5
@@ -267,6 +267,7 @@ export class RemoteBridge {
|
||||
`备用原始链接:${directUrl}`,
|
||||
...(challenge.user_code ? [`授权码:${challenge.user_code}`] : []),
|
||||
`链接有效期至:${expiresAt}`,
|
||||
"链接过期后让 Agent 重新获取即可,无需重新安装客户端。",
|
||||
"Agent 必须在当前回复正文中原样显示以上链接,不得只说“已生成”或“已推送”。",
|
||||
"登录完成后重新调用原工具。",
|
||||
].join("\n");
|
||||
@@ -388,10 +389,10 @@ function parseEndpoint(argv: string[]): string {
|
||||
|
||||
export function createBridgeMcpServer(bridge: RemoteBridge): McpServer {
|
||||
const server = new McpServer({ name: "dingtalk-approval-stdio-bridge", version: "1.1.0-rc.8" }, {
|
||||
instructions: "首次使用或登录过期时,工具不会通过其他消息通道自动发送链接。必须把content.text中的登录URL原样写入当前面向用户的回复,不得只声称已生成或已推送。审批引用、调用容量和内部分批不得向用户解释;查看时只回答业务信息,用户明确授权后在授权范围内自动处理。写前校验失败时零写入,任何不确定结果都不得自动重试。",
|
||||
instructions: "首次使用或登录链接过期时调用approval_login;每次调用会检查登录并在需要时生成有效的新链接,绝不因链接过期而重新安装。工具不会通过其他消息通道自动发送链接,必须把content.text中的登录URL原样写入当前面向用户的回复,不得只声称已生成或已推送。审批引用、调用容量和内部分批不得向用户解释;查看时只回答业务信息,用户明确授权后在授权范围内自动处理。写前校验失败时零写入,任何不确定结果都不得自动重试。",
|
||||
});
|
||||
server.registerTool("approval_login", {
|
||||
title: "登录审批服务", description: "创建或检查唯一的钉钉登录链接;同一身份同时用于钉钉OA和薪福通。", inputSchema: {},
|
||||
title: "登录审批服务", description: "检查登录;未登录或旧链接已过期时生成新的钉钉授权链接。同一身份同时用于钉钉OA和薪福通,无需重新安装。", inputSchema: {},
|
||||
annotations: { readOnlyHint: true, destructiveHint: false },
|
||||
}, async () => {
|
||||
const result = await bridge.loginChallenge();
|
||||
@@ -488,7 +489,7 @@ export async function startBridge(endpoint: string, statePath = process.env.DING
|
||||
await server.connect(new StdioServerTransport());
|
||||
}
|
||||
|
||||
async function runPostInstallLogin(endpoint: string, statePath: string): Promise<void> {
|
||||
async function runLogin(endpoint: string, statePath: string): Promise<void> {
|
||||
syncBundledSkillForLegacyUpdate(statePath);
|
||||
const bridge = new RemoteBridge(endpoint, resolve(statePath));
|
||||
bridge.ensureOnboardingPending();
|
||||
@@ -526,8 +527,8 @@ if (isDirectEntrypoint(import.meta.url, process.argv[1])) {
|
||||
const endpoint = parseEndpoint(args);
|
||||
const statePath = process.env.DINGTALK_APPROVAL_BRIDGE_STATE
|
||||
?? join(homedir(), ".config", "dingtalk-approval-bridge", "session.json");
|
||||
const task = args.includes("--post-install-login")
|
||||
? runPostInstallLogin(endpoint, statePath)
|
||||
const task = args.includes("--login") || args.includes("--post-install-login")
|
||||
? runLogin(endpoint, statePath)
|
||||
: startBridge(endpoint, statePath);
|
||||
task.catch((error) => { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1; });
|
||||
}
|
||||
|
||||
+26
-5
@@ -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])) {
|
||||
|
||||
Reference in New Issue
Block a user