Merge pull request #1369 from tyler6204/fix/bluebubbles-gc-guid-resolution
BlueBubbles: short ID mapping, action resolution, and threading/typing fixes
This commit is contained in:
@@ -299,6 +299,8 @@ export async function runAgentTurnWithFallback(params: {
|
||||
const { text, skip } = normalizeStreamingText(payload);
|
||||
const hasPayloadMedia = (payload.mediaUrls?.length ?? 0) > 0;
|
||||
if (skip && !hasPayloadMedia) return;
|
||||
const currentMessageId =
|
||||
params.sessionCtx.MessageSidFull ?? params.sessionCtx.MessageSid;
|
||||
const taggedPayload = applyReplyTagsToPayload(
|
||||
{
|
||||
text,
|
||||
@@ -308,12 +310,12 @@ export async function runAgentTurnWithFallback(params: {
|
||||
replyToTag: payload.replyToTag,
|
||||
replyToCurrent: payload.replyToCurrent,
|
||||
},
|
||||
params.sessionCtx.MessageSid,
|
||||
currentMessageId,
|
||||
);
|
||||
// Let through payloads with audioAsVoice flag even if empty (need to track it)
|
||||
if (!isRenderablePayload(taggedPayload) && !payload.audioAsVoice) return;
|
||||
const parsed = parseReplyDirectives(taggedPayload.text ?? "", {
|
||||
currentMessageId: params.sessionCtx.MessageSid,
|
||||
currentMessageId,
|
||||
silentToken: SILENT_REPLY_TOKEN,
|
||||
});
|
||||
const cleaned = parsed.text || undefined;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { NormalizedUsage } from "../../agents/usage.js";
|
||||
import { getChannelDock } from "../../channels/dock.js";
|
||||
import type { ChannelThreadingToolContext } from "../../channels/plugins/types.js";
|
||||
import type { ChannelId, ChannelThreadingToolContext } from "../../channels/plugins/types.js";
|
||||
import { normalizeChannelId } from "../../channels/registry.js";
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import { isReasoningTagProvider } from "../../utils/provider-utils.js";
|
||||
@@ -21,17 +21,25 @@ export function buildThreadingToolContext(params: {
|
||||
}): ChannelThreadingToolContext {
|
||||
const { sessionCtx, config, hasRepliedRef } = params;
|
||||
if (!config) return {};
|
||||
const provider = normalizeChannelId(sessionCtx.Provider);
|
||||
if (!provider) return {};
|
||||
const dock = getChannelDock(provider);
|
||||
if (!dock?.threading?.buildToolContext) return {};
|
||||
const rawProvider = sessionCtx.Provider?.trim().toLowerCase();
|
||||
if (!rawProvider) return {};
|
||||
const provider = normalizeChannelId(rawProvider);
|
||||
// WhatsApp context isolation keys off conversation id, not the bot's own number.
|
||||
const threadingTo =
|
||||
provider === "whatsapp"
|
||||
rawProvider === "whatsapp"
|
||||
? (sessionCtx.From ?? sessionCtx.To)
|
||||
: provider === "imessage" && sessionCtx.ChatType === "direct"
|
||||
: rawProvider === "imessage" && sessionCtx.ChatType === "direct"
|
||||
? (sessionCtx.From ?? sessionCtx.To)
|
||||
: sessionCtx.To;
|
||||
// Fallback for unrecognized/plugin channels (e.g., BlueBubbles before plugin registry init)
|
||||
const dock = provider ? getChannelDock(provider) : undefined;
|
||||
if (!dock?.threading?.buildToolContext) {
|
||||
return {
|
||||
currentChannelId: threadingTo?.trim() || undefined,
|
||||
currentChannelProvider: provider ?? (rawProvider as ChannelId),
|
||||
hasRepliedRef,
|
||||
};
|
||||
}
|
||||
const context =
|
||||
dock.threading.buildToolContext({
|
||||
cfg: config,
|
||||
@@ -47,7 +55,7 @@ export function buildThreadingToolContext(params: {
|
||||
}) ?? {};
|
||||
return {
|
||||
...context,
|
||||
currentChannelProvider: provider,
|
||||
currentChannelProvider: provider!, // guaranteed non-null since dock exists
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -377,7 +377,7 @@ export async function runReplyAgent(params: {
|
||||
directlySentBlockKeys,
|
||||
replyToMode,
|
||||
replyToChannel,
|
||||
currentMessageId: sessionCtx.MessageSid,
|
||||
currentMessageId: sessionCtx.MessageSidFull ?? sessionCtx.MessageSid,
|
||||
messageProvider: followupRun.run.messageProvider,
|
||||
messagingToolSentTexts: runResult.messagingToolSentTexts,
|
||||
messagingToolSentTargets: runResult.messagingToolSentTargets,
|
||||
|
||||
@@ -350,7 +350,7 @@ export async function runPreparedReply(
|
||||
const authProfileIdSource = sessionEntry?.authProfileOverrideSource;
|
||||
const followupRun = {
|
||||
prompt: queuedBody,
|
||||
messageId: sessionCtx.MessageSid,
|
||||
messageId: sessionCtx.MessageSidFull ?? sessionCtx.MessageSid,
|
||||
summaryLine: baseBodyTrimmedRaw,
|
||||
enqueuedAt: Date.now(),
|
||||
// Originating channel for reply routing.
|
||||
|
||||
@@ -140,7 +140,7 @@ describe("createTypingSignaler", () => {
|
||||
expect(typing.startTypingOnText).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not start typing on tool start before text", async () => {
|
||||
it("starts typing on tool start before text", async () => {
|
||||
const typing = createMockTypingController();
|
||||
const signaler = createTypingSignaler({
|
||||
typing,
|
||||
@@ -150,8 +150,9 @@ describe("createTypingSignaler", () => {
|
||||
|
||||
await signaler.signalToolStart();
|
||||
|
||||
expect(typing.startTypingLoop).not.toHaveBeenCalled();
|
||||
expect(typing.refreshTypingTtl).not.toHaveBeenCalled();
|
||||
expect(typing.startTypingLoop).toHaveBeenCalled();
|
||||
expect(typing.refreshTypingTtl).toHaveBeenCalled();
|
||||
expect(typing.startTypingOnText).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("refreshes ttl on tool start when active after text", async () => {
|
||||
|
||||
@@ -95,13 +95,13 @@ export function createTypingSignaler(params: {
|
||||
|
||||
const signalToolStart = async () => {
|
||||
if (disabled) return;
|
||||
if (!hasRenderableText) return;
|
||||
// Start typing as soon as tools begin executing, even before the first text delta.
|
||||
if (!typing.isActive()) {
|
||||
await typing.startTypingLoop();
|
||||
typing.refreshTypingTtl();
|
||||
return;
|
||||
}
|
||||
// Keep typing indicator alive during tool execution without changing mode semantics.
|
||||
// Keep typing indicator alive during tool execution.
|
||||
typing.refreshTypingTtl();
|
||||
};
|
||||
|
||||
|
||||
@@ -38,10 +38,14 @@ export type MsgContext = {
|
||||
AccountId?: string;
|
||||
ParentSessionKey?: string;
|
||||
MessageSid?: string;
|
||||
/** Provider-specific full message id when MessageSid is a shortened alias. */
|
||||
MessageSidFull?: string;
|
||||
MessageSids?: string[];
|
||||
MessageSidFirst?: string;
|
||||
MessageSidLast?: string;
|
||||
ReplyToId?: string;
|
||||
/** Provider-specific full reply-to id when ReplyToId is a shortened alias. */
|
||||
ReplyToIdFull?: string;
|
||||
ReplyToBody?: string;
|
||||
ReplyToSender?: string;
|
||||
ForwardedFrom?: string;
|
||||
|
||||
Reference in New Issue
Block a user