Compare commits
10 Commits
main
...
vincentkoc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6d21b3ab1 | ||
|
|
1abdf03fd3 | ||
|
|
ec08a8d6a1 | ||
|
|
4353207e34 | ||
|
|
732d1e5d55 | ||
|
|
b437be4d72 | ||
|
|
07498a78e0 | ||
|
|
def1ec2b12 | ||
|
|
be71fda555 | ||
|
|
2b8588bec0 |
@ -57,6 +57,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/compaction safeguard pre-check: skip embedded compaction before entering the Pi SDK when a session has no real conversation messages, avoiding unnecessary LLM API calls on idle sessions. (#36451) thanks @Sid-Qin.
|
||||
- Config/schema cache key stability: build merged schema cache keys with incremental hashing to avoid large single-string serialization and prevent `RangeError: Invalid string length` on high-cardinality plugin/channel metadata. (#36603) Thanks @powermaster888.
|
||||
- iMessage/cron completion announces: strip leaked inline reply tags (for example `[[reply_to:6100]]`) from user-visible completion text so announcement deliveries do not expose threading metadata. (#24600) Thanks @vincentkoc.
|
||||
- Control UI/iMessage duplicate reply routing: keep internal webchat turns on dispatcher delivery (instead of origin-channel reroute) so Control UI chats do not duplicate replies into iMessage, while preserving webchat-provider relayed routing for external surfaces. Fixes #33483. Thanks @alicexmolt.
|
||||
- Sessions/daily reset transcript archival: archive prior transcript files during stale-session scheduled/daily resets by capturing the previous session entry before rollover, preventing orphaned transcript files on disk. (#35493) Thanks @byungsker.
|
||||
- Feishu/group slash command detection: normalize group mention wrappers before command-authorization probing so mention-prefixed commands (for example `@Bot/model` and `@Bot /reset`) are recognized as gateway commands instead of being forwarded to the agent. (#35994) Thanks @liuxiaopai-ai.
|
||||
- Agents/context pruning: guard assistant thinking/text char estimation against malformed blocks (missing `thinking`/`text` strings or null entries) so pruning no longer crashes with malformed provider content. (openclaw#35146) thanks @Sid-Qin.
|
||||
|
||||
@ -399,6 +399,58 @@ describe("dispatchReplyFromConfig", () => {
|
||||
expect(dispatcher.sendFinalReply).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("does not route external origin replies when current surface is internal webchat without explicit delivery", async () => {
|
||||
setNoAbort();
|
||||
mocks.routeReply.mockClear();
|
||||
const cfg = emptyConfig;
|
||||
const dispatcher = createDispatcher();
|
||||
const ctx = buildTestCtx({
|
||||
Provider: "webchat",
|
||||
Surface: "webchat",
|
||||
OriginatingChannel: "imessage",
|
||||
OriginatingTo: "imessage:+15550001111",
|
||||
});
|
||||
|
||||
const replyResolver = async (
|
||||
_ctx: MsgContext,
|
||||
_opts?: GetReplyOptions,
|
||||
_cfg?: OpenClawConfig,
|
||||
) => ({ text: "hi" }) satisfies ReplyPayload;
|
||||
await dispatchReplyFromConfig({ ctx, cfg, dispatcher, replyResolver });
|
||||
|
||||
expect(mocks.routeReply).not.toHaveBeenCalled();
|
||||
expect(dispatcher.sendFinalReply).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("routes external origin replies for internal webchat turns when explicit delivery is set", async () => {
|
||||
setNoAbort();
|
||||
mocks.routeReply.mockClear();
|
||||
const cfg = emptyConfig;
|
||||
const dispatcher = createDispatcher();
|
||||
const ctx = buildTestCtx({
|
||||
Provider: "webchat",
|
||||
Surface: "webchat",
|
||||
OriginatingChannel: "imessage",
|
||||
OriginatingTo: "imessage:+15550001111",
|
||||
ExplicitDeliverRoute: true,
|
||||
});
|
||||
|
||||
const replyResolver = async (
|
||||
_ctx: MsgContext,
|
||||
_opts?: GetReplyOptions,
|
||||
_cfg?: OpenClawConfig,
|
||||
) => ({ text: "hi" }) satisfies ReplyPayload;
|
||||
await dispatchReplyFromConfig({ ctx, cfg, dispatcher, replyResolver });
|
||||
|
||||
expect(dispatcher.sendFinalReply).not.toHaveBeenCalled();
|
||||
expect(mocks.routeReply).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
channel: "imessage",
|
||||
to: "imessage:+15550001111",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("routes media-only tool results when summaries are suppressed", async () => {
|
||||
setNoAbort();
|
||||
mocks.routeReply.mockClear();
|
||||
|
||||
@ -215,8 +215,15 @@ export async function dispatchReplyFromConfig(params: {
|
||||
const surfaceChannel = normalizeMessageChannel(ctx.Surface);
|
||||
// Prefer provider channel because surface may carry origin metadata in relayed flows.
|
||||
const currentSurface = providerChannel ?? surfaceChannel;
|
||||
const isInternalWebchatTurn =
|
||||
currentSurface === INTERNAL_MESSAGE_CHANNEL &&
|
||||
(surfaceChannel === INTERNAL_MESSAGE_CHANNEL || !surfaceChannel) &&
|
||||
ctx.ExplicitDeliverRoute !== true;
|
||||
const shouldRouteToOriginating = Boolean(
|
||||
isRoutableChannel(originatingChannel) && originatingTo && originatingChannel !== currentSurface,
|
||||
!isInternalWebchatTurn &&
|
||||
isRoutableChannel(originatingChannel) &&
|
||||
originatingTo &&
|
||||
originatingChannel !== currentSurface,
|
||||
);
|
||||
const shouldSuppressTyping =
|
||||
shouldRouteToOriginating || originatingChannel === INTERNAL_MESSAGE_CHANNEL;
|
||||
|
||||
@ -159,6 +159,11 @@ export type MsgContext = {
|
||||
* The chat/channel/user ID where the reply should be sent.
|
||||
*/
|
||||
OriginatingTo?: string;
|
||||
/**
|
||||
* True when the current turn intentionally requested external delivery to
|
||||
* OriginatingChannel/OriginatingTo, rather than inheriting stale session route metadata.
|
||||
*/
|
||||
ExplicitDeliverRoute?: boolean;
|
||||
/**
|
||||
* Provider-specific parent conversation id for threaded contexts.
|
||||
* For Discord threads, this is the parent channel id.
|
||||
|
||||
@ -393,6 +393,7 @@ describe("chat directive tag stripping for non-streaming final payloads", () =>
|
||||
expect.objectContaining({
|
||||
OriginatingChannel: "telegram",
|
||||
OriginatingTo: "telegram:6812765697",
|
||||
ExplicitDeliverRoute: true,
|
||||
AccountId: "default",
|
||||
MessageThreadId: 42,
|
||||
}),
|
||||
@ -566,6 +567,7 @@ describe("chat directive tag stripping for non-streaming final payloads", () =>
|
||||
expect.objectContaining({
|
||||
OriginatingChannel: "webchat",
|
||||
OriginatingTo: undefined,
|
||||
ExplicitDeliverRoute: false,
|
||||
AccountId: undefined,
|
||||
}),
|
||||
);
|
||||
|
||||
@ -906,13 +906,14 @@ export const chatHandlers: GatewayRequestHandlers = {
|
||||
(isChannelScopedSession || hasLegacyChannelPeerShape)) ||
|
||||
(isConfiguredMainSessionScope && client?.connect !== undefined && !isFromWebchatClient)),
|
||||
);
|
||||
const hasDeliverableRoute =
|
||||
const hasDeliverableRoute = Boolean(
|
||||
shouldDeliverExternally &&
|
||||
canInheritDeliverableRoute &&
|
||||
routeChannelCandidate &&
|
||||
routeChannelCandidate !== INTERNAL_MESSAGE_CHANNEL &&
|
||||
typeof routeToCandidate === "string" &&
|
||||
routeToCandidate.trim().length > 0;
|
||||
routeToCandidate.trim().length > 0,
|
||||
);
|
||||
const originatingChannel = hasDeliverableRoute
|
||||
? routeChannelCandidate
|
||||
: INTERNAL_MESSAGE_CHANNEL;
|
||||
@ -935,6 +936,7 @@ export const chatHandlers: GatewayRequestHandlers = {
|
||||
Surface: INTERNAL_MESSAGE_CHANNEL,
|
||||
OriginatingChannel: originatingChannel,
|
||||
OriginatingTo: originatingTo,
|
||||
ExplicitDeliverRoute: hasDeliverableRoute,
|
||||
AccountId: accountId,
|
||||
MessageThreadId: messageThreadId,
|
||||
ChatType: "direct",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user