feat: send workspace images to QQ chats

Worker results may report image attachments stored inside the workspace;
the runtime validates them (containment, png/jpg magic, size) and
delivers them with the pending event. Assistants gain a send_image
action so users can ask for an image later. QQ uploads via /files with
base64 file_data and sends msg_type 7 rich media, sharing the same
msg_id/msg_seq counter as text replies; non-image adapters flatten
images to text.
This commit is contained in:
zenord
2026-08-19 14:39:18 +08:00
parent 7aa610294c
commit 6b506b8c55
17 changed files with 659 additions and 62 deletions
+18
View File
@@ -19,11 +19,18 @@ export type ProposalStatus =
| "pending"
| "finished";
export interface ProposalPendingAttachment {
path: string;
mimeType?: string;
}
export interface ProposalPending {
summary: string;
question?: string;
workspaceDirty?: boolean;
receivedAt: number;
attachments?: ProposalPendingAttachment[];
droppedAttachments?: string[];
}
export type ProposalFinishKind = "done" | "cancelled";
@@ -325,6 +332,17 @@ function validateProposal(proposal: Proposal, id: string): void {
|| typeof pending.receivedAt !== "number") {
invalid("pending");
}
if (pending.attachments !== undefined
&& (!Array.isArray(pending.attachments) || pending.attachments.length > 3
|| pending.attachments.some((attachment) => !isRecord(attachment)
|| typeof attachment.path !== "string" || attachment.path.length === 0
|| (attachment.mimeType !== undefined && typeof attachment.mimeType !== "string")))) {
invalid("pending.attachments");
}
if (pending.droppedAttachments !== undefined
&& (!Array.isArray(pending.droppedAttachments) || pending.droppedAttachments.some((entry) => typeof entry !== "string"))) {
invalid("pending.droppedAttachments");
}
}
if (proposal.finishKind !== undefined && !FINISH_KINDS.includes(proposal.finishKind)) invalid("finishKind");
if (proposal.finishNote !== undefined && typeof proposal.finishNote !== "string") invalid("finishNote");