diff --git a/CHANGELOG.md b/CHANGELOG.md index df6ad73de1f..abe57c8108b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Docs: https://docs.openclaw.ai - Agents/usage tracking: stop forcing `supportsUsageInStreaming: false` on non-native openai-completions endpoints so providers like DashScope, DeepSeek, and other OpenAI-compatible backends report token usage and cost instead of showing all zeros. (#46142) - Node/startup: remove leftover debug `console.log("node host PATH: ...")` that printed the resolved PATH on every `openclaw node run` invocation. (#46411) - Control UI/dashboard: preserve structured gateway shutdown reasons across restart disconnects so config-triggered restarts no longer fall back to `disconnected (1006): no reason`. (#46532) Thanks @vincentkoc. +- Feishu/topic threads: fetch full thread context, including prior bot replies, when starting a topic-thread session so follow-up turns in Feishu topics keep the right conversation state. Thanks @Coobiw. - Browser/profiles: drop the auto-created `chrome-relay` browser profile; users who need the Chrome extension relay must now create their own profile via `openclaw browser create-profile`. (#45777) Thanks @odysseus0. ## 2026.3.13 diff --git a/extensions/feishu/src/bot.test.ts b/extensions/feishu/src/bot.test.ts index 858d83cbc72..5de21aa825b 100644 --- a/extensions/feishu/src/bot.test.ts +++ b/extensions/feishu/src/bot.test.ts @@ -15,9 +15,12 @@ const { mockCreateFeishuReplyDispatcher, mockSendMessageFeishu, mockGetMessageFeishu, + mockListFeishuThreadMessages, mockDownloadMessageResourceFeishu, mockCreateFeishuClient, mockResolveAgentRoute, + mockReadSessionUpdatedAt, + mockResolveStorePath, } = vi.hoisted(() => ({ mockCreateFeishuReplyDispatcher: vi.fn(() => ({ dispatcher: vi.fn(), @@ -26,6 +29,7 @@ const { })), mockSendMessageFeishu: vi.fn().mockResolvedValue({ messageId: "pairing-msg", chatId: "oc-dm" }), mockGetMessageFeishu: vi.fn().mockResolvedValue(null), + mockListFeishuThreadMessages: vi.fn().mockResolvedValue([]), mockDownloadMessageResourceFeishu: vi.fn().mockResolvedValue({ buffer: Buffer.from("video"), contentType: "video/mp4", @@ -40,6 +44,8 @@ const { mainSessionKey: "agent:main:main", matchedBy: "default", })), + mockReadSessionUpdatedAt: vi.fn(), + mockResolveStorePath: vi.fn(() => "/tmp/feishu-sessions.json"), })); vi.mock("./reply-dispatcher.js", () => ({ @@ -49,6 +55,7 @@ vi.mock("./reply-dispatcher.js", () => ({ vi.mock("./send.js", () => ({ sendMessageFeishu: mockSendMessageFeishu, getMessageFeishu: mockGetMessageFeishu, + listFeishuThreadMessages: mockListFeishuThreadMessages, })); vi.mock("./media.js", () => ({ @@ -140,6 +147,8 @@ describe("handleFeishuMessage command authorization", () => { beforeEach(() => { vi.clearAllMocks(); mockShouldComputeCommandAuthorized.mockReset().mockReturnValue(true); + mockReadSessionUpdatedAt.mockReturnValue(undefined); + mockResolveStorePath.mockReturnValue("/tmp/feishu-sessions.json"); mockResolveAgentRoute.mockReturnValue({ agentId: "main", channel: "feishu", @@ -166,6 +175,12 @@ describe("handleFeishuMessage command authorization", () => { resolveAgentRoute: mockResolveAgentRoute as unknown as PluginRuntime["channel"]["routing"]["resolveAgentRoute"], }, + session: { + readSessionUpdatedAt: + mockReadSessionUpdatedAt as unknown as PluginRuntime["channel"]["session"]["readSessionUpdatedAt"], + resolveStorePath: + mockResolveStorePath as unknown as PluginRuntime["channel"]["session"]["resolveStorePath"], + }, reply: { resolveEnvelopeFormatOptions: vi.fn( () => ({}), @@ -1709,6 +1724,123 @@ describe("handleFeishuMessage command authorization", () => { ); }); + it("bootstraps topic thread context only for a new thread session", async () => { + mockShouldComputeCommandAuthorized.mockReturnValue(false); + mockGetMessageFeishu.mockResolvedValue({ + messageId: "om_topic_root", + chatId: "oc-group", + content: "root starter", + contentType: "text", + threadId: "omt_topic_1", + }); + mockListFeishuThreadMessages.mockResolvedValue([ + { + messageId: "om_bot_reply", + senderId: "app_1", + senderType: "app", + content: "assistant reply", + contentType: "text", + createTime: 1710000000000, + }, + { + messageId: "om_follow_up", + senderId: "ou-topic-user", + senderType: "user", + content: "follow-up question", + contentType: "text", + createTime: 1710000001000, + }, + ]); + + const cfg: ClawdbotConfig = { + channels: { + feishu: { + groups: { + "oc-group": { + requireMention: false, + groupSessionScope: "group_topic", + }, + }, + }, + }, + } as ClawdbotConfig; + + const event: FeishuMessageEvent = { + sender: { sender_id: { open_id: "ou-topic-user" } }, + message: { + message_id: "om_topic_followup_existing_session", + root_id: "om_topic_root", + chat_id: "oc-group", + chat_type: "group", + message_type: "text", + content: JSON.stringify({ text: "current turn" }), + }, + }; + + await dispatchMessage({ cfg, event }); + + expect(mockReadSessionUpdatedAt).toHaveBeenCalledWith({ + storePath: "/tmp/feishu-sessions.json", + sessionKey: "agent:main:feishu:dm:ou-attacker", + }); + expect(mockListFeishuThreadMessages).toHaveBeenCalledWith( + expect.objectContaining({ + rootMessageId: "om_topic_root", + }), + ); + expect(mockFinalizeInboundContext).toHaveBeenCalledWith( + expect.objectContaining({ + ThreadStarterBody: "root starter", + ThreadHistoryBody: "assistant reply\n\nfollow-up question", + ThreadLabel: "Feishu thread in oc-group", + MessageThreadId: "om_topic_root", + }), + ); + }); + + it("skips topic thread bootstrap when the thread session already exists", async () => { + mockShouldComputeCommandAuthorized.mockReturnValue(false); + mockReadSessionUpdatedAt.mockReturnValue(1710000000000); + + const cfg: ClawdbotConfig = { + channels: { + feishu: { + groups: { + "oc-group": { + requireMention: false, + groupSessionScope: "group_topic", + }, + }, + }, + }, + } as ClawdbotConfig; + + const event: FeishuMessageEvent = { + sender: { sender_id: { open_id: "ou-topic-user" } }, + message: { + message_id: "om_topic_followup", + root_id: "om_topic_root", + chat_id: "oc-group", + chat_type: "group", + message_type: "text", + content: JSON.stringify({ text: "current turn" }), + }, + }; + + await dispatchMessage({ cfg, event }); + + expect(mockGetMessageFeishu).not.toHaveBeenCalled(); + expect(mockListFeishuThreadMessages).not.toHaveBeenCalled(); + expect(mockFinalizeInboundContext).toHaveBeenCalledWith( + expect.objectContaining({ + ThreadStarterBody: undefined, + ThreadHistoryBody: undefined, + ThreadLabel: "Feishu thread in oc-group", + MessageThreadId: "om_topic_root", + }), + ); + }); + it("does not dispatch twice for the same image message_id (concurrent dedupe)", async () => { mockShouldComputeCommandAuthorized.mockReturnValue(false); diff --git a/extensions/feishu/src/bot.ts b/extensions/feishu/src/bot.ts index 815f935ed94..980a9769d7a 100644 --- a/extensions/feishu/src/bot.ts +++ b/extensions/feishu/src/bot.ts @@ -29,7 +29,7 @@ import { import { parsePostContent } from "./post.js"; import { createFeishuReplyDispatcher } from "./reply-dispatcher.js"; import { getFeishuRuntime } from "./runtime.js"; -import { getMessageFeishu, sendMessageFeishu } from "./send.js"; +import { getMessageFeishu, listFeishuThreadMessages, sendMessageFeishu } from "./send.js"; import type { FeishuMessageContext, FeishuMediaInfo, ResolvedFeishuAccount } from "./types.js"; import type { DynamicAgentCreationConfig } from "./types.js"; @@ -1239,16 +1239,17 @@ export async function handleFeishuMessage(params: { const mediaPayload = buildAgentMediaPayload(mediaList); // Fetch quoted/replied message content if parentId exists + let quotedMessageInfo: Awaited> = null; let quotedContent: string | undefined; if (ctx.parentId) { try { - const quotedMsg = await getMessageFeishu({ + quotedMessageInfo = await getMessageFeishu({ cfg, messageId: ctx.parentId, accountId: account.accountId, }); - if (quotedMsg) { - quotedContent = quotedMsg.content; + if (quotedMessageInfo) { + quotedContent = quotedMessageInfo.content; log( `feishu[${account.accountId}]: fetched quoted message: ${quotedContent?.slice(0, 100)}`, ); @@ -1258,6 +1259,11 @@ export async function handleFeishuMessage(params: { } } + const isTopicSessionForThread = + isGroup && + (groupSession?.groupSessionScope === "group_topic" || + groupSession?.groupSessionScope === "group_topic_sender"); + const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg); const messageBody = buildFeishuAgentBody({ ctx, @@ -1309,13 +1315,140 @@ export async function handleFeishuMessage(params: { })) : undefined; + const threadContextBySessionKey = new Map< + string, + { + threadStarterBody?: string; + threadHistoryBody?: string; + threadLabel?: string; + } + >(); + let rootMessageInfo: Awaited> | undefined; + let rootMessageFetched = false; + const getRootMessageInfo = async () => { + if (!ctx.rootId) { + return null; + } + if (!rootMessageFetched) { + rootMessageFetched = true; + if (ctx.rootId === ctx.parentId && quotedMessageInfo) { + rootMessageInfo = quotedMessageInfo; + } else { + try { + rootMessageInfo = await getMessageFeishu({ + cfg, + messageId: ctx.rootId, + accountId: account.accountId, + }); + } catch (err) { + log(`feishu[${account.accountId}]: failed to fetch root message: ${String(err)}`); + rootMessageInfo = null; + } + } + } + return rootMessageInfo ?? null; + }; + const resolveThreadContextForAgent = async (agentId: string, agentSessionKey: string) => { + const cached = threadContextBySessionKey.get(agentSessionKey); + if (cached) { + return cached; + } + + const threadContext: { + threadStarterBody?: string; + threadHistoryBody?: string; + threadLabel?: string; + } = { + threadLabel: + (ctx.rootId || ctx.threadId) && isTopicSessionForThread + ? `Feishu thread in ${ctx.chatId}` + : undefined, + }; + + if (!(ctx.rootId || ctx.threadId) || !isTopicSessionForThread) { + threadContextBySessionKey.set(agentSessionKey, threadContext); + return threadContext; + } + + const storePath = core.channel.session.resolveStorePath(cfg.session?.store, { agentId }); + const previousThreadSessionTimestamp = core.channel.session.readSessionUpdatedAt({ + storePath, + sessionKey: agentSessionKey, + }); + if (previousThreadSessionTimestamp) { + log( + `feishu[${account.accountId}]: skipping thread bootstrap for existing session ${agentSessionKey}`, + ); + threadContextBySessionKey.set(agentSessionKey, threadContext); + return threadContext; + } + + const rootMsg = await getRootMessageInfo(); + let feishuThreadId = ctx.threadId ?? rootMsg?.threadId; + if (feishuThreadId) { + log(`feishu[${account.accountId}]: resolved thread ID: ${feishuThreadId}`); + } + if (!feishuThreadId) { + log( + `feishu[${account.accountId}]: no threadId found for root message ${ctx.rootId ?? "none"}, skipping thread history`, + ); + threadContextBySessionKey.set(agentSessionKey, threadContext); + return threadContext; + } + + try { + const threadMessages = await listFeishuThreadMessages({ + cfg, + threadId: feishuThreadId, + currentMessageId: ctx.messageId, + rootMessageId: ctx.rootId, + limit: 20, + accountId: account.accountId, + }); + const senderScoped = groupSession?.groupSessionScope === "group_topic_sender"; + const relevantMessages = senderScoped + ? threadMessages.filter( + (msg) => msg.senderType === "app" || msg.senderId === ctx.senderOpenId, + ) + : threadMessages; + + const threadStarterBody = rootMsg?.content ?? relevantMessages[0]?.content; + const historyMessages = + rootMsg?.content || ctx.rootId ? relevantMessages : relevantMessages.slice(1); + const historyParts = historyMessages.map((msg) => { + const role = msg.senderType === "app" ? "assistant" : "user"; + return core.channel.reply.formatAgentEnvelope({ + channel: "Feishu", + from: `${msg.senderId ?? "Unknown"} (${role})`, + timestamp: msg.createTime, + body: msg.content, + envelope: envelopeOptions, + }); + }); + + threadContext.threadStarterBody = threadStarterBody; + threadContext.threadHistoryBody = + historyParts.length > 0 ? historyParts.join("\n\n") : undefined; + log( + `feishu[${account.accountId}]: populated thread bootstrap with starter=${threadStarterBody ? "yes" : "no"} history=${historyMessages.length}`, + ); + } catch (err) { + log(`feishu[${account.accountId}]: failed to fetch thread history: ${String(err)}`); + } + + threadContextBySessionKey.set(agentSessionKey, threadContext); + return threadContext; + }; + // --- Shared context builder for dispatch --- - const buildCtxPayloadForAgent = ( + const buildCtxPayloadForAgent = async ( + agentId: string, agentSessionKey: string, agentAccountId: string, wasMentioned: boolean, - ) => - core.channel.reply.finalizeInboundContext({ + ) => { + const threadContext = await resolveThreadContextForAgent(agentId, agentSessionKey); + return core.channel.reply.finalizeInboundContext({ Body: combinedBody, BodyForAgent: messageBody, InboundHistory: inboundHistory, @@ -1335,6 +1468,12 @@ export async function handleFeishuMessage(params: { Surface: "feishu" as const, MessageSid: ctx.messageId, ReplyToBody: quotedContent ?? undefined, + ThreadStarterBody: threadContext.threadStarterBody, + ThreadHistoryBody: threadContext.threadHistoryBody, + ThreadLabel: threadContext.threadLabel, + // Only use rootId (om_* message anchor) — threadId (omt_*) is a container + // ID and would produce invalid reply targets downstream. + MessageThreadId: ctx.rootId && isTopicSessionForThread ? ctx.rootId : undefined, Timestamp: Date.now(), WasMentioned: wasMentioned, CommandAuthorized: commandAuthorized, @@ -1343,6 +1482,7 @@ export async function handleFeishuMessage(params: { GroupSystemPrompt: isGroup ? groupConfig?.systemPrompt?.trim() || undefined : undefined, ...mediaPayload, }); + }; // Parse message create_time (Feishu uses millisecond epoch string). const messageCreateTimeMs = event.message.create_time @@ -1402,7 +1542,8 @@ export async function handleFeishuMessage(params: { } const agentSessionKey = buildBroadcastSessionKey(route.sessionKey, route.agentId, agentId); - const agentCtx = buildCtxPayloadForAgent( + const agentCtx = await buildCtxPayloadForAgent( + agentId, agentSessionKey, route.accountId, ctx.mentionedBot && agentId === activeAgentId, @@ -1502,7 +1643,8 @@ export async function handleFeishuMessage(params: { ); } else { // --- Single-agent dispatch (existing behavior) --- - const ctxPayload = buildCtxPayloadForAgent( + const ctxPayload = await buildCtxPayloadForAgent( + route.agentId, route.sessionKey, route.accountId, ctx.mentionedBot, diff --git a/extensions/feishu/src/send.test.ts b/extensions/feishu/src/send.test.ts index 18e14b20d79..8971f91cb3e 100644 --- a/extensions/feishu/src/send.test.ts +++ b/extensions/feishu/src/send.test.ts @@ -1,12 +1,14 @@ import type { ClawdbotConfig } from "openclaw/plugin-sdk/feishu"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { getMessageFeishu } from "./send.js"; +import { getMessageFeishu, listFeishuThreadMessages } from "./send.js"; -const { mockClientGet, mockCreateFeishuClient, mockResolveFeishuAccount } = vi.hoisted(() => ({ - mockClientGet: vi.fn(), - mockCreateFeishuClient: vi.fn(), - mockResolveFeishuAccount: vi.fn(), -})); +const { mockClientGet, mockClientList, mockCreateFeishuClient, mockResolveFeishuAccount } = + vi.hoisted(() => ({ + mockClientGet: vi.fn(), + mockClientList: vi.fn(), + mockCreateFeishuClient: vi.fn(), + mockResolveFeishuAccount: vi.fn(), + })); vi.mock("./client.js", () => ({ createFeishuClient: mockCreateFeishuClient, @@ -27,6 +29,7 @@ describe("getMessageFeishu", () => { im: { message: { get: mockClientGet, + list: mockClientList, }, }, }); @@ -165,4 +168,68 @@ describe("getMessageFeishu", () => { }), ); }); + + it("reuses the same content parsing for thread history messages", async () => { + mockClientList.mockResolvedValueOnce({ + code: 0, + data: { + items: [ + { + message_id: "om_root", + msg_type: "text", + body: { + content: JSON.stringify({ text: "root starter" }), + }, + }, + { + message_id: "om_card", + msg_type: "interactive", + body: { + content: JSON.stringify({ + body: { + elements: [{ tag: "markdown", content: "hello from card 2.0" }], + }, + }), + }, + sender: { + id: "app_1", + sender_type: "app", + }, + create_time: "1710000000000", + }, + { + message_id: "om_file", + msg_type: "file", + body: { + content: JSON.stringify({ file_key: "file_v3_123" }), + }, + sender: { + id: "ou_1", + sender_type: "user", + }, + create_time: "1710000001000", + }, + ], + }, + }); + + const result = await listFeishuThreadMessages({ + cfg: {} as ClawdbotConfig, + threadId: "omt_1", + rootMessageId: "om_root", + }); + + expect(result).toEqual([ + expect.objectContaining({ + messageId: "om_file", + contentType: "file", + content: "[file message]", + }), + expect.objectContaining({ + messageId: "om_card", + contentType: "interactive", + content: "hello from card 2.0", + }), + ]); + }); }); diff --git a/extensions/feishu/src/send.ts b/extensions/feishu/src/send.ts index 5692edd32ff..d4cad09fe07 100644 --- a/extensions/feishu/src/send.ts +++ b/extensions/feishu/src/send.ts @@ -65,6 +65,7 @@ type FeishuMessageGetItem = { message_id?: string; chat_id?: string; chat_type?: FeishuChatType; + thread_id?: string; msg_type?: string; body?: { content?: string }; sender?: FeishuMessageSender; @@ -151,13 +152,19 @@ function parseInteractiveCardContent(parsed: unknown): string { return "[Interactive Card]"; } - const candidate = parsed as { elements?: unknown }; - if (!Array.isArray(candidate.elements)) { + // Support both schema 1.0 (top-level `elements`) and 2.0 (`body.elements`). + const candidate = parsed as { elements?: unknown; body?: { elements?: unknown } }; + const elements = Array.isArray(candidate.elements) + ? candidate.elements + : Array.isArray(candidate.body?.elements) + ? candidate.body!.elements + : null; + if (!elements) { return "[Interactive Card]"; } const texts: string[] = []; - for (const element of candidate.elements) { + for (const element of elements) { if (!element || typeof element !== "object") { continue; } @@ -177,7 +184,7 @@ function parseInteractiveCardContent(parsed: unknown): string { return texts.join("\n").trim() || "[Interactive Card]"; } -function parseQuotedMessageContent(rawContent: string, msgType: string): string { +function parseFeishuMessageContent(rawContent: string, msgType: string): string { if (!rawContent) { return ""; } @@ -218,6 +225,30 @@ function parseQuotedMessageContent(rawContent: string, msgType: string): string return `[${msgType || "unknown"} message]`; } +function parseFeishuMessageItem( + item: FeishuMessageGetItem, + fallbackMessageId?: string, +): FeishuMessageInfo { + const msgType = item.msg_type ?? "text"; + const rawContent = item.body?.content ?? ""; + + return { + messageId: item.message_id ?? fallbackMessageId ?? "", + chatId: item.chat_id ?? "", + chatType: + item.chat_type === "group" || item.chat_type === "private" || item.chat_type === "p2p" + ? item.chat_type + : undefined, + senderId: item.sender?.id, + senderOpenId: item.sender?.id_type === "open_id" ? item.sender?.id : undefined, + senderType: item.sender?.sender_type, + content: parseFeishuMessageContent(rawContent, msgType), + contentType: msgType, + createTime: item.create_time ? parseInt(String(item.create_time), 10) : undefined, + threadId: item.thread_id || undefined, + }; +} + /** * Get a message by its ID. * Useful for fetching quoted/replied message content. @@ -255,29 +286,98 @@ export async function getMessageFeishu(params: { return null; } - const msgType = item.msg_type ?? "text"; - const rawContent = item.body?.content ?? ""; - const content = parseQuotedMessageContent(rawContent, msgType); - - return { - messageId: item.message_id ?? messageId, - chatId: item.chat_id ?? "", - chatType: - item.chat_type === "group" || item.chat_type === "private" || item.chat_type === "p2p" - ? item.chat_type - : undefined, - senderId: item.sender?.id, - senderOpenId: item.sender?.id_type === "open_id" ? item.sender?.id : undefined, - senderType: item.sender?.sender_type, - content, - contentType: msgType, - createTime: item.create_time ? parseInt(String(item.create_time), 10) : undefined, - }; + return parseFeishuMessageItem(item, messageId); } catch { return null; } } +export type FeishuThreadMessageInfo = { + messageId: string; + senderId?: string; + senderType?: string; + content: string; + contentType: string; + createTime?: number; +}; + +/** + * List messages in a Feishu thread (topic). + * Uses container_id_type=thread to directly query thread messages, + * which includes both the root message and all replies (including bot replies). + */ +export async function listFeishuThreadMessages(params: { + cfg: ClawdbotConfig; + threadId: string; + currentMessageId?: string; + /** Exclude the root message (already provided separately as ThreadStarterBody). */ + rootMessageId?: string; + limit?: number; + accountId?: string; +}): Promise { + const { cfg, threadId, currentMessageId, rootMessageId, limit = 20, accountId } = params; + const account = resolveFeishuAccount({ cfg, accountId }); + if (!account.configured) { + throw new Error(`Feishu account "${account.accountId}" not configured`); + } + + const client = createFeishuClient(account); + + const response = (await client.im.message.list({ + params: { + container_id_type: "thread", + container_id: threadId, + // Fetch newest messages first so long threads keep the most recent turns. + // Results are reversed below to restore chronological order. + sort_type: "ByCreateTimeDesc", + page_size: Math.min(limit + 1, 50), + }, + })) as { + code?: number; + msg?: string; + data?: { + items?: Array< + { + message_id?: string; + root_id?: string; + parent_id?: string; + } & FeishuMessageGetItem + >; + }; + }; + + if (response.code !== 0) { + throw new Error( + `Feishu thread list failed: code=${response.code} msg=${response.msg ?? "unknown"}`, + ); + } + + const items = response.data?.items ?? []; + const results: FeishuThreadMessageInfo[] = []; + + for (const item of items) { + if (currentMessageId && item.message_id === currentMessageId) continue; + if (rootMessageId && item.message_id === rootMessageId) continue; + + const parsed = parseFeishuMessageItem(item); + + results.push({ + messageId: parsed.messageId, + senderId: parsed.senderId, + senderType: parsed.senderType, + content: parsed.content, + contentType: parsed.contentType, + createTime: parsed.createTime, + }); + + if (results.length >= limit) break; + } + + // Restore chronological order (oldest first) since we fetched newest-first. + results.reverse(); + return results; +} + export type SendFeishuMessageParams = { cfg: ClawdbotConfig; to: string; diff --git a/extensions/feishu/src/types.ts b/extensions/feishu/src/types.ts index c28398fca65..05293a7ff1d 100644 --- a/extensions/feishu/src/types.ts +++ b/extensions/feishu/src/types.ts @@ -72,6 +72,8 @@ export type FeishuMessageInfo = { content: string; contentType: string; createTime?: number; + /** Feishu thread ID (omt_xxx) — present when the message belongs to a topic thread. */ + threadId?: string; }; export type FeishuProbeResult = BaseProbeResult & { diff --git a/scripts/bundle-a2ui.sh b/scripts/bundle-a2ui.sh index 4d53c40ca4c..682cacd11da 100755 --- a/scripts/bundle-a2ui.sh +++ b/scripts/bundle-a2ui.sh @@ -88,6 +88,8 @@ fi pnpm -s exec tsc -p "$A2UI_RENDERER_DIR/tsconfig.json" if command -v rolldown >/dev/null 2>&1 && rolldown --version >/dev/null 2>&1; then rolldown -c "$A2UI_APP_DIR/rolldown.config.mjs" +elif [[ -f "$ROOT_DIR/node_modules/.pnpm/node_modules/rolldown/bin/cli.mjs" ]]; then + node "$ROOT_DIR/node_modules/.pnpm/node_modules/rolldown/bin/cli.mjs" -c "$A2UI_APP_DIR/rolldown.config.mjs" elif [[ -f "$ROOT_DIR/node_modules/.pnpm/rolldown@1.0.0-rc.9/node_modules/rolldown/bin/cli.mjs" ]]; then node "$ROOT_DIR/node_modules/.pnpm/rolldown@1.0.0-rc.9/node_modules/rolldown/bin/cli.mjs" \ -c "$A2UI_APP_DIR/rolldown.config.mjs"