



























@@ -28,13 +28,18 @@ export function decodeStrictBase64(value: string, maxDecodedBytes: number): Buff
2828return decoded;
2929}
303031-type SubagentInlineAttachment = {
31+export type SubagentInlineAttachment = {
3232name: string;
3333content: string;
3434encoding?: "utf8" | "base64";
3535mimeType?: string;
3636};
373738+type AcpInlineImageAttachment = {
39+mediaType: string;
40+data: string;
41+};
42+3843type AttachmentLimits = {
3944enabled: boolean;
4045maxTotalBytes: number;
@@ -94,6 +99,120 @@ function resolveAttachmentLimits(config: OpenClawConfig): AttachmentLimits {
9499};
95100}
96101102+export function resolveAcpSessionsSpawnImageAttachments(params: {
103+config: OpenClawConfig;
104+attachments?: SubagentInlineAttachment[];
105+}):
106+| { status: "ok"; attachments: AcpInlineImageAttachment[] }
107+| { status: "forbidden"; error: string }
108+| { status: "error"; error: string }
109+| null {
110+const requestedAttachments = Array.isArray(params.attachments) ? params.attachments : [];
111+if (requestedAttachments.length === 0) {
112+return null;
113+}
114+115+const limits = resolveAttachmentLimits(params.config);
116+if (!limits.enabled) {
117+return {
118+status: "forbidden",
119+error:
120+"attachments are disabled for sessions_spawn (enable tools.sessions_spawn.attachments.enabled)",
121+};
122+}
123+if (requestedAttachments.length > limits.maxFiles) {
124+return {
125+status: "error",
126+error: `attachments_file_count_exceeded (maxFiles=${limits.maxFiles})`,
127+};
128+}
129+130+const fail = (error: string): never => {
131+throw new Error(error);
132+};
133+134+try {
135+const seen = new Set<string>();
136+const attachments: AcpInlineImageAttachment[] = [];
137+let totalBytes = 0;
138+139+for (const raw of requestedAttachments) {
140+const name = normalizeOptionalString(raw?.name) ?? "";
141+const contentVal = typeof raw?.content === "string" ? raw.content : "";
142+const encodingRaw = normalizeOptionalString(raw?.encoding) ?? "utf8";
143+const encoding = encodingRaw === "base64" ? "base64" : "utf8";
144+const mimeType = normalizeOptionalString(raw?.mimeType) ?? "";
145+146+if (!name) {
147+fail("attachments_invalid_name (empty)");
148+}
149+if (name.includes("/") || name.includes("\\") || name.includes("\u0000")) {
150+fail(`attachments_invalid_name (${name})`);
151+}
152+if (
153+Array.from(name).some((char) => {
154+const code = char.codePointAt(0) ?? 0;
155+return code < 0x20 || code === 0x7f;
156+})
157+) {
158+fail(`attachments_invalid_name (${name})`);
159+}
160+if (name === "." || name === ".." || name === ".manifest.json") {
161+fail(`attachments_invalid_name (${name})`);
162+}
163+if (seen.has(name)) {
164+fail(`attachments_duplicate_name (${name})`);
165+}
166+seen.add(name);
167+if (!mimeType.startsWith("image/")) {
168+fail(`attachments_unsupported_for_acp (name=${name} mimeType=${mimeType || "unknown"})`);
169+}
170+171+let buf: Buffer;
172+if (encoding === "base64") {
173+const strictBuf = decodeStrictBase64(contentVal, limits.maxFileBytes);
174+if (strictBuf === null) {
175+throw new Error("attachments_invalid_base64_or_too_large");
176+}
177+buf = strictBuf;
178+} else {
179+const estimatedBytes = Buffer.byteLength(contentVal, "utf8");
180+if (estimatedBytes > limits.maxFileBytes) {
181+fail(
182+`attachments_file_bytes_exceeded (name=${name} bytes=${estimatedBytes} maxFileBytes=${limits.maxFileBytes})`,
183+);
184+}
185+buf = Buffer.from(contentVal, "utf8");
186+}
187+188+const bytes = buf.byteLength;
189+if (bytes > limits.maxFileBytes) {
190+fail(
191+`attachments_file_bytes_exceeded (name=${name} bytes=${bytes} maxFileBytes=${limits.maxFileBytes})`,
192+);
193+}
194+totalBytes += bytes;
195+if (totalBytes > limits.maxTotalBytes) {
196+fail(
197+`attachments_total_bytes_exceeded (totalBytes=${totalBytes} maxTotalBytes=${limits.maxTotalBytes})`,
198+);
199+}
200+201+attachments.push({
202+mediaType: mimeType,
203+data: buf.toString("base64"),
204+});
205+}
206+207+return { status: "ok", attachments };
208+} catch (err) {
209+return {
210+status: "error",
211+error: err instanceof Error ? err.message : "attachments_materialization_failed",
212+};
213+}
214+}
215+97216export async function materializeSubagentAttachments(params: {
98217config: OpenClawConfig;
99218targetAgentId: string;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。