mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:10:43 +00:00
fix(channels): clarify message target syntax
This commit is contained in:
@@ -22,6 +22,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Local models: default custom providers with only `baseUrl` to the Chat Completions adapter and trust loopback model requests automatically, so local OpenAI-compatible proxies receive `/v1/chat/completions` without timing out. Fixes #40024. Thanks @parachuteshe.
|
||||
- Channels/message tool: surface Discord, Slack, and Mattermost `user:`/`channel:` target syntax in the shared message target schema and Discord ambiguity errors, so DM sends by numeric id stop burning retries before finding `user:<id>`. Fixes #72401. Thanks @garyd9, @hclsys, and @praveen9354.
|
||||
- Agents/tools: scope tool-loop detection history to the active run when available, so scheduled heartbeat cycles no longer inherit stale repeated-call counts from previous runs. Fixes #40144. Thanks @mattbrown319.
|
||||
- Control UI: show loading, reload, and retry states when a lazy dashboard panel cannot load after an upgrade, so the Logs tab no longer appears blank on stale browser bundles. Fixes #72450. Thanks @sobergou.
|
||||
- Gateway/plugins: start the Gateway in degraded mode when a single plugin entry has invalid schema config, and let `openclaw doctor --fix` quarantine that plugin config instead of crash-looping every channel. Fixes #62976 and #70371. Thanks @Doraemon-Claw and @pksidekyk.
|
||||
|
||||
@@ -26,18 +26,13 @@ export async function parseAndResolveRecipient(
|
||||
}
|
||||
const resolvedCfg = requireRuntimeConfig(cfg, "Discord recipient resolution");
|
||||
const accountInfo = resolveDiscordAccount({ cfg: resolvedCfg, accountId });
|
||||
const trimmed = raw.trim();
|
||||
const resolvedParseOptions = {
|
||||
...parseOptions,
|
||||
ambiguousMessage: `Ambiguous Discord recipient "${trimmed}". Use "user:${trimmed}" for DMs or "channel:${trimmed}" for channel messages.`,
|
||||
};
|
||||
const resolved = await parseAndResolveDiscordTarget(
|
||||
raw,
|
||||
{
|
||||
cfg: resolvedCfg,
|
||||
accountId: accountInfo.accountId,
|
||||
},
|
||||
resolvedParseOptions,
|
||||
parseOptions,
|
||||
);
|
||||
return { kind: resolved.kind, id: resolved.id };
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export function parseDiscordTarget(
|
||||
}
|
||||
throw new Error(
|
||||
options.ambiguousMessage ??
|
||||
`Ambiguous Discord recipient "${trimmed}". Use "user:${trimmed}" for DMs or "channel:${trimmed}" for channel messages.`,
|
||||
`Ambiguous Discord recipient "${trimmed}". For DMs use "user:${trimmed}" or "<@${trimmed}>"; for channels use "channel:${trimmed}".`,
|
||||
);
|
||||
}
|
||||
return buildMessagingTarget("channel", trimmed, trimmed);
|
||||
|
||||
@@ -62,6 +62,12 @@ describe("parseDiscordTarget", () => {
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("guides ambiguous numeric recipients with all supported explicit formats", () => {
|
||||
expect(() => parseDiscordTarget("123456789")).toThrow(
|
||||
'Ambiguous Discord recipient "123456789". For DMs use "user:123456789" or "<@123456789>"; for channels use "channel:123456789".',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveDiscordChannelId", () => {
|
||||
|
||||
@@ -749,6 +749,19 @@ describe("message tool description", () => {
|
||||
},
|
||||
});
|
||||
|
||||
it("surfaces explicit cross-channel target syntax in the target schema", () => {
|
||||
const tool = createMessageTool({
|
||||
config: {} as never,
|
||||
});
|
||||
const properties = getToolProperties(tool);
|
||||
const target = properties.target as { description?: string } | undefined;
|
||||
|
||||
expect(target?.description).toContain(
|
||||
"Discord/Slack/Mattermost <channelId|user:ID|channel:ID>",
|
||||
);
|
||||
expect(target?.description).toContain("Telegram chat id/@username");
|
||||
});
|
||||
|
||||
it("hides BlueBubbles group actions for DM targets", () => {
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([{ pluginId: "bluebubbles", source: "test", plugin: bluebubblesPlugin }]),
|
||||
|
||||
@@ -48,7 +48,7 @@ function actionNeedsExplicitTarget(action: ChannelMessageActionName): boolean {
|
||||
function buildRoutingSchema() {
|
||||
return {
|
||||
channel: Type.Optional(Type.String()),
|
||||
target: Type.Optional(channelTargetSchema({ description: "Target channel/user id or name." })),
|
||||
target: Type.Optional(channelTargetSchema()),
|
||||
targets: Type.Optional(channelTargetsSchema()),
|
||||
accountId: Type.Optional(Type.String()),
|
||||
dryRun: Type.Optional(Type.Boolean()),
|
||||
|
||||
@@ -1343,44 +1343,6 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
});
|
||||
incrementPresenceVersion();
|
||||
}
|
||||
const snapshot = buildGatewaySnapshot({
|
||||
includeSensitive: scopes.includes(ADMIN_SCOPE),
|
||||
});
|
||||
const cachedHealth = getHealthCache();
|
||||
if (cachedHealth) {
|
||||
snapshot.health = cachedHealth;
|
||||
snapshot.stateVersion.health = getHealthVersion();
|
||||
}
|
||||
const helloOkAuthScopes = deviceToken ? deviceToken.scopes : scopes;
|
||||
const helloOk = {
|
||||
type: "hello-ok",
|
||||
protocol: PROTOCOL_VERSION,
|
||||
server: {
|
||||
version: resolveRuntimeServiceVersion(process.env),
|
||||
connId,
|
||||
},
|
||||
features: { methods: gatewayMethods, events },
|
||||
snapshot,
|
||||
canvasHostUrl: scopedCanvasHostUrl,
|
||||
auth: {
|
||||
role,
|
||||
scopes: helloOkAuthScopes,
|
||||
...(deviceToken
|
||||
? {
|
||||
deviceToken: deviceToken.token,
|
||||
issuedAtMs: deviceToken.rotatedAtMs ?? deviceToken.createdAtMs,
|
||||
...(bootstrapDeviceTokens.length > 1
|
||||
? { deviceTokens: bootstrapDeviceTokens.slice(1) }
|
||||
: {}),
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
policy: {
|
||||
maxPayload: MAX_PAYLOAD_BYTES,
|
||||
maxBufferedBytes: MAX_BUFFERED_BYTES,
|
||||
tickIntervalMs: TICK_INTERVAL_MS,
|
||||
},
|
||||
};
|
||||
if (role === "node") {
|
||||
const context = buildRequestContext();
|
||||
const nodeSession = context.nodeRegistry.register(nextClient, {
|
||||
@@ -1442,6 +1404,45 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
);
|
||||
}
|
||||
|
||||
const snapshot = buildGatewaySnapshot({
|
||||
includeSensitive: scopes.includes(ADMIN_SCOPE),
|
||||
});
|
||||
const cachedHealth = getHealthCache();
|
||||
if (cachedHealth) {
|
||||
snapshot.health = cachedHealth;
|
||||
snapshot.stateVersion.health = getHealthVersion();
|
||||
}
|
||||
const helloOkAuthScopes = deviceToken ? deviceToken.scopes : scopes;
|
||||
const helloOk = {
|
||||
type: "hello-ok",
|
||||
protocol: PROTOCOL_VERSION,
|
||||
server: {
|
||||
version: resolveRuntimeServiceVersion(process.env),
|
||||
connId,
|
||||
},
|
||||
features: { methods: gatewayMethods, events },
|
||||
snapshot,
|
||||
canvasHostUrl: scopedCanvasHostUrl,
|
||||
auth: {
|
||||
role,
|
||||
scopes: helloOkAuthScopes,
|
||||
...(deviceToken
|
||||
? {
|
||||
deviceToken: deviceToken.token,
|
||||
issuedAtMs: deviceToken.rotatedAtMs ?? deviceToken.createdAtMs,
|
||||
...(bootstrapDeviceTokens.length > 1
|
||||
? { deviceTokens: bootstrapDeviceTokens.slice(1) }
|
||||
: {}),
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
policy: {
|
||||
maxPayload: MAX_PAYLOAD_BYTES,
|
||||
maxBufferedBytes: MAX_BUFFERED_BYTES,
|
||||
tickIntervalMs: TICK_INTERVAL_MS,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
await sendFrame({ type: "res", id: frame.id, ok: true, payload: helloOk });
|
||||
} catch (err) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { MESSAGE_ACTION_TARGET_MODE } from "./message-action-spec.js";
|
||||
export const hasNonEmptyString = sharedHasNonEmptyString;
|
||||
|
||||
export const CHANNEL_TARGET_DESCRIPTION =
|
||||
"Recipient/channel: E.164 for WhatsApp/Signal, Telegram chat id/@username, Discord/Slack channel/user, or iMessage handle/chat_id";
|
||||
"Recipient/channel: E.164 for WhatsApp/Signal, Telegram chat id/@username, Discord/Slack/Mattermost <channelId|user:ID|channel:ID>, or iMessage handle/chat_id";
|
||||
|
||||
export const CHANNEL_TARGETS_DESCRIPTION =
|
||||
"Recipient/channel targets (same format as --target); accepts ids or names when the directory is available.";
|
||||
|
||||
Reference in New Issue
Block a user