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.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- 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);
|
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 () => {
|
it("routes media-only tool results when summaries are suppressed", async () => {
|
||||||
setNoAbort();
|
setNoAbort();
|
||||||
mocks.routeReply.mockClear();
|
mocks.routeReply.mockClear();
|
||||||
|
|||||||
@ -215,8 +215,15 @@ export async function dispatchReplyFromConfig(params: {
|
|||||||
const surfaceChannel = normalizeMessageChannel(ctx.Surface);
|
const surfaceChannel = normalizeMessageChannel(ctx.Surface);
|
||||||
// Prefer provider channel because surface may carry origin metadata in relayed flows.
|
// Prefer provider channel because surface may carry origin metadata in relayed flows.
|
||||||
const currentSurface = providerChannel ?? surfaceChannel;
|
const currentSurface = providerChannel ?? surfaceChannel;
|
||||||
|
const isInternalWebchatTurn =
|
||||||
|
currentSurface === INTERNAL_MESSAGE_CHANNEL &&
|
||||||
|
(surfaceChannel === INTERNAL_MESSAGE_CHANNEL || !surfaceChannel) &&
|
||||||
|
ctx.ExplicitDeliverRoute !== true;
|
||||||
const shouldRouteToOriginating = Boolean(
|
const shouldRouteToOriginating = Boolean(
|
||||||
isRoutableChannel(originatingChannel) && originatingTo && originatingChannel !== currentSurface,
|
!isInternalWebchatTurn &&
|
||||||
|
isRoutableChannel(originatingChannel) &&
|
||||||
|
originatingTo &&
|
||||||
|
originatingChannel !== currentSurface,
|
||||||
);
|
);
|
||||||
const shouldSuppressTyping =
|
const shouldSuppressTyping =
|
||||||
shouldRouteToOriginating || originatingChannel === INTERNAL_MESSAGE_CHANNEL;
|
shouldRouteToOriginating || originatingChannel === INTERNAL_MESSAGE_CHANNEL;
|
||||||
|
|||||||
@ -159,6 +159,11 @@ export type MsgContext = {
|
|||||||
* The chat/channel/user ID where the reply should be sent.
|
* The chat/channel/user ID where the reply should be sent.
|
||||||
*/
|
*/
|
||||||
OriginatingTo?: string;
|
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.
|
* Provider-specific parent conversation id for threaded contexts.
|
||||||
* For Discord threads, this is the parent channel id.
|
* 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({
|
expect.objectContaining({
|
||||||
OriginatingChannel: "telegram",
|
OriginatingChannel: "telegram",
|
||||||
OriginatingTo: "telegram:6812765697",
|
OriginatingTo: "telegram:6812765697",
|
||||||
|
ExplicitDeliverRoute: true,
|
||||||
AccountId: "default",
|
AccountId: "default",
|
||||||
MessageThreadId: 42,
|
MessageThreadId: 42,
|
||||||
}),
|
}),
|
||||||
@ -566,6 +567,7 @@ describe("chat directive tag stripping for non-streaming final payloads", () =>
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
OriginatingChannel: "webchat",
|
OriginatingChannel: "webchat",
|
||||||
OriginatingTo: undefined,
|
OriginatingTo: undefined,
|
||||||
|
ExplicitDeliverRoute: false,
|
||||||
AccountId: undefined,
|
AccountId: undefined,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -906,13 +906,14 @@ export const chatHandlers: GatewayRequestHandlers = {
|
|||||||
(isChannelScopedSession || hasLegacyChannelPeerShape)) ||
|
(isChannelScopedSession || hasLegacyChannelPeerShape)) ||
|
||||||
(isConfiguredMainSessionScope && client?.connect !== undefined && !isFromWebchatClient)),
|
(isConfiguredMainSessionScope && client?.connect !== undefined && !isFromWebchatClient)),
|
||||||
);
|
);
|
||||||
const hasDeliverableRoute =
|
const hasDeliverableRoute = Boolean(
|
||||||
shouldDeliverExternally &&
|
shouldDeliverExternally &&
|
||||||
canInheritDeliverableRoute &&
|
canInheritDeliverableRoute &&
|
||||||
routeChannelCandidate &&
|
routeChannelCandidate &&
|
||||||
routeChannelCandidate !== INTERNAL_MESSAGE_CHANNEL &&
|
routeChannelCandidate !== INTERNAL_MESSAGE_CHANNEL &&
|
||||||
typeof routeToCandidate === "string" &&
|
typeof routeToCandidate === "string" &&
|
||||||
routeToCandidate.trim().length > 0;
|
routeToCandidate.trim().length > 0,
|
||||||
|
);
|
||||||
const originatingChannel = hasDeliverableRoute
|
const originatingChannel = hasDeliverableRoute
|
||||||
? routeChannelCandidate
|
? routeChannelCandidate
|
||||||
: INTERNAL_MESSAGE_CHANNEL;
|
: INTERNAL_MESSAGE_CHANNEL;
|
||||||
@ -935,6 +936,7 @@ export const chatHandlers: GatewayRequestHandlers = {
|
|||||||
Surface: INTERNAL_MESSAGE_CHANNEL,
|
Surface: INTERNAL_MESSAGE_CHANNEL,
|
||||||
OriginatingChannel: originatingChannel,
|
OriginatingChannel: originatingChannel,
|
||||||
OriginatingTo: originatingTo,
|
OriginatingTo: originatingTo,
|
||||||
|
ExplicitDeliverRoute: hasDeliverableRoute,
|
||||||
AccountId: accountId,
|
AccountId: accountId,
|
||||||
MessageThreadId: messageThreadId,
|
MessageThreadId: messageThreadId,
|
||||||
ChatType: "direct",
|
ChatType: "direct",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user