feat: support long text and safe attachment batches
Build web service image / build (push) Successful in 1m15s

This commit is contained in:
2026-09-30 09:16:36 +08:00
parent a977902b25
commit edba7d0d38
26 changed files with 720 additions and 82 deletions
+9 -1
View File
@@ -138,7 +138,9 @@ public sealed class AgentService(IAgentBackend backend, ServiceSecurity security
{
var kind = RequireText(request.Kind, "kind", 80);
var accountId = RequireText(request.AccountId, "accountId", 200);
var text = PrepareText(request.Text);
var text = string.Equals(kind, "send-text", StringComparison.Ordinal)
? PrepareLongText(request.Text)
: PrepareText(request.Text);
var idempotencyKey = RequireText(request.IdempotencyKey, "idempotencyKey", 128);
if (!request.Confirmed) throw new ServiceException("ConfirmationRequired", 409, "Explicit send confirmation is required.");
@@ -271,6 +273,12 @@ public sealed class AgentService(IAgentBackend backend, ServiceSecurity security
return value;
}
private static string PrepareLongText(string? value)
{
try { return WechatTextInput.PrepareLongText(value ?? string.Empty); }
catch (WxAgentException exception) { throw new ServiceException("InvalidRequest", 400, exception.Message); }
}
private static string PrepareText(string? value)
{
try { return WechatTextInput.Prepare(value ?? string.Empty); }
@@ -841,7 +841,7 @@ public sealed class RemoteAgentHostedService(
JsonElement Content,
IReadOnlyList<RemoteReportingScope> ChatScopes);
private static async Task ReplayUnreportedResultsAsync(
private async Task ReplayUnreportedResultsAsync(
RemoteControlClient client,
RemoteTaskLedger ledger,
ReportingConfig reporting,
@@ -851,7 +851,7 @@ public sealed class RemoteAgentHostedService(
await ReportResultAsync(client, ledger, pending.Result, reporting, cancellationToken, pending.ReportingScopes);
}
private static async Task ReportResultAsync(
private async Task ReportResultAsync(
RemoteControlClient client,
RemoteTaskLedger ledger,
RemoteTaskResult result,
@@ -859,8 +859,16 @@ public sealed class RemoteAgentHostedService(
CancellationToken cancellationToken,
IReadOnlyList<RemoteReportingScope>? chatScopes = null)
{
await client.SendTaskResultAsync(result, reporting, chatScopes: chatScopes, cancellationToken: cancellationToken);
ledger.MarkReported(result.TaskId);
try
{
await client.SendTaskResultAsync(result, reporting, chatScopes: chatScopes, cancellationToken: cancellationToken);
ledger.MarkReported(result.TaskId);
}
catch (RemoteClientException exception) when (exception.StatusCode == 404 && exception.Code == "TaskNotFound")
{
ledger.MarkResultTaskNotFound(result.TaskId);
logger.LogWarning("Remote task result no longer exists on the control plane; result retained locally and will not be retried.");
}
}
private async Task<BackendSnapshot> ReadSnapshotAsync(RemoteAgentOptions remote, CancellationToken cancellationToken)
@@ -972,8 +980,16 @@ public sealed class RemoteAgentHostedService(
|| confirmed.ValueKind != JsonValueKind.True)
return false;
targetId = target.GetString() ?? "";
text = content.GetString() ?? "";
return targetId.Length is > 0 and <= 512 && text.Length is > 0 and <= 4000;
if (targetId.Length is 0 or > 512) return false;
try
{
text = WechatTextInput.PrepareLongText(content.GetString() ?? "");
return true;
}
catch (WxAgentException)
{
return false;
}
}
private static bool GetBoolean(JsonElement value, string propertyName) =>
@@ -50,7 +50,7 @@
<label class="toggle"><input id="content" type="checkbox"> 显示正文 <span class="muted">需要 content 权限</span></label>
</section>
<section class="panel"><div class="section-heading"><h2>会话</h2><span class="muted">当前账号</span></div><div id="sessions" class="list"></div></section>
<section class="panel reply-panel"><div class="section-heading"><h2>回复</h2><span id="replyCapability" class="muted">发送能力检查中…</span></div><div class="reply-form"><label for="replyTarget">目标会话</label><select id="replyTarget"><option value="">先选择会话</option></select><label for="replyText">文本</label><textarea id="replyText" rows="4" maxlength="4000" placeholder="输入要发送的文本" disabled></textarea><div class="reply-actions"><button id="sendReply" disabled>发送</button><span id="replyState" class="muted">写能力尚未开放。</span></div></div></section>
<section class="panel reply-panel"><div class="section-heading"><h2>回复</h2><span id="replyCapability" class="muted">发送能力检查中…</span></div><div class="reply-form"><label for="replyTarget">目标会话</label><select id="replyTarget"><option value="">先选择会话</option></select><label for="replyText">文本</label><textarea id="replyText" rows="4" maxlength="20000" placeholder="输入要发送的文本" disabled></textarea><div class="reply-actions"><button id="sendReply" disabled>发送</button><span id="replyState" class="muted">写能力尚未开放。</span></div></div></section>
<section class="panel"><div class="section-heading"><h2>可见消息</h2><span class="muted">只显示当前可读取的数据</span></div><div id="messages" class="list"></div></section>
</section>