feat: generalize session sending with multiline text and structured mentions

Add named-session file/image/reply sending, preserve multiline text, and share safe member/all mention handling. Protect existing drafts, bind popup selection to the current window, validate structured mention tokens, and require fresh matching visible messages without automatic resend. Core tests: 121 passed. Windows 4.1.13.63 final validation: 9/9 passed. Record explicit coverage limits and sanitized evidence.
This commit is contained in:
2026-09-06 17:10:53 +08:00
parent ad22a89185
commit c49f6a10b2
23 changed files with 531 additions and 69 deletions
+24 -9
View File
@@ -279,21 +279,32 @@ try
}
case "chat" when args[1] == "reply-latest":
{
var message = await WechatChatClient.ReplyToLatestAsync(GetRequiredOption(args, "--text"), cancellationToken);
var message = await WechatChatClient.ReplyToLatestAsync(GetRequiredOption(args, "--text"), cancellationToken, GetOption(args, "--session") ?? WechatLocators.FileTransferAssistant);
WriteJson(ToMessageOutput(message, includeContent: false));
return 0;
}
case "chat" when args[1] == "mention":
{
var session = GetRequiredOption(args, "--session");
var member = GetRequiredOption(args, "--member");
var text = GetOption(args, "--text");
var confirmation = GetRequiredOption(args, "--confirm");
var result = await WechatChatClient.MentionMemberAsync(session, member, text, confirmation, cancellationToken);
WriteJson(result);
return result.Success ? 0 : 2;
}
case "chat" when args[1] is "send-file" or "send-image":
{
var path = GetRequiredOption(args, "--path");
var session = GetOption(args, "--session") ?? WechatLocators.FileTransferAssistant;
if (args[1] == "send-image" && !IsImagePath(path))
{
throw new WxAgentException(WxAgentErrorCode.InvalidArgument, "send-image accepts png, jpg, jpeg, gif, bmp, or webp files.");
}
var message = args[1] == "send-image"
? await WechatChatClient.SendImageAsync(path, cancellationToken)
: await WechatChatClient.SendFileAsync(path, cancellationToken);
? await WechatChatClient.SendImageAsync(path, cancellationToken, session)
: await WechatChatClient.SendFileAsync(path, cancellationToken, session);
WriteJson(ToMessageOutput(message, includeContent: false));
return 0;
}
@@ -669,11 +680,14 @@ static int ValidateCommandLine(string[] values)
ValidateOptions(values, 2, ["--text", "--session", "--timeout"], []);
return 30;
case "reply-latest":
ValidateOptions(values, 2, ["--text", "--timeout"], []);
ValidateOptions(values, 2, ["--text", "--session", "--timeout"], []);
return 60;
case "send-file":
case "send-image":
ValidateOptions(values, 2, ["--path", "--timeout"], []);
ValidateOptions(values, 2, ["--path", "--session", "--timeout"], []);
return 60;
case "mention":
ValidateOptions(values, 2, ["--session", "--member", "--text", "--confirm", "--timeout"], []);
return 60;
case "read":
ValidateOptions(values, 2, ["--limit", "--timeout"], ["--include-content"]);
@@ -863,10 +877,11 @@ WxAgent.Host commands:
group verify-at-all --group <name> --message <text> [--timeout 30]
chat send-url-card --to <name> --url <http-url> [--message <text>] --confirm CONFIRM [--timeout 60]
chat send-audio --to <name> --path <audio-file> --confirm CONFIRM [--timeout 60]
chat send --text <one-line-text> [--session <name>] [--timeout 30]
chat reply-latest --text <one-line-text> [--timeout 60]
chat send-file --path <file> [--timeout 60]
chat send-image --path <image> [--timeout 60]
chat send --text <text-up-to-4000-chars; multiline-supported> [--session <name>] [--timeout 30]
chat reply-latest --text <text-up-to-4000-chars; multiline-supported> [--session <name>] [--timeout 60]
chat send-file --path <file> [--session <name>] [--timeout 60]
chat send-image --path <image> [--session <name>] [--timeout 60]
chat mention --session <name> --member <display-name> --text <text> --confirm CONFIRM [--timeout 60]
chat read [--limit 20] [--include-content] [--timeout 30]
chat history [--limit 100] [--scrolls 10] [--include-content] [--timeout 60]
chat monitor [--seconds 60] [--session <name>] [--independent] [--state-file <path>] [--include-content] [--timeout <seconds>]