diff --git a/src/cli.ts b/src/cli.ts index 8ae7e07d..f460ee48 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -4320,6 +4320,11 @@ async function cmdQuoted(rest: string[]): Promise { process.exit(1); } + // Read isolation: register this bot from its own send-cred file so the Lark + // client (getMessageDetail below) is available WITHOUT reading the denied + // bots.json — same as cmdHistory / cmdSend. Missing this was why a sandboxed + // isolated bot's `botmux quoted` failed "Bot not registered". + await registerSelfFromCredFile(); const { larkAppId: appId } = await resolveSessionAppId(sessionIdArg); const { getMessageDetail } = await import('./im/lark/client.js'); @@ -4343,6 +4348,19 @@ async function cmdQuoted(rest: string[]): Promise { const merged = await resolveMergedCardContent(appId, messageId).catch(() => null); if (merged) rendered.content = merged.text; } + // The referenced message's file/media resources arrive as key+name only. A + // read-isolated agent can't call the Lark resource API itself (bots.json + // creds are deny-read), so download the bytes HERE — via the bot client + // registered above — into this bot's OWN attachment bucket + // (attachments///, read-allowed by its carve-out; sandbox + // denies file *reads*, not writes). Surface the local paths so the agent can + // actually open the file instead of only seeing its key. + if (rendered.resources?.length) { + const { downloadResources } = await import('./core/session-manager.js'); + const { attachments, needLogin } = await downloadResources(appId, messageId, rendered.resources); + (rendered as { attachments?: unknown }).attachments = attachments; + if (needLogin) (rendered as { needLogin?: boolean }).needLogin = true; + } console.log(JSON.stringify(rendered, null, 2)); } catch (err: any) { console.error(`获取被引用消息失败: ${err.message}`);