mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 20:05:46 -06:00
fix: align Buzz explicit mention semantics
This commit is contained in:
+1
-1
@@ -23,7 +23,7 @@ Docs: https://docs.openclaw.ai
|
||||
- **Buzz message fidelity:** preserve Markdown output and accept Buzz normal, rich-content, and structured-diff room messages through the existing authorized inbound path. Thanks @shakkernerd.
|
||||
- **Buzz typing indicators:** show room- and thread-scoped typing during agent replies and heartbeat deliveries, refresh through the active authenticated connection without waiting for relay acknowledgement, and drop ephemeral updates safely during disconnects or shutdown. Thanks @shakkernerd.
|
||||
- **Buzz sender directory:** expose current bot, member, room, and room-member directory entries from bounded relay state; use current Buzz profile and room names in inbound context while preserving public keys and UUIDs as stable authorization and routing identities. Thanks @shakkernerd.
|
||||
- **Buzz native mentions:** resolve unique current room-member names and explicit NIP-27 identities into native `p` tags for replies, proactive sends, and bounded standalone delivery; reject ambiguous, unknown, or out-of-room identities instead of sending inert mention text, and preserve Buzz reply-thread session parsing during maintenance and heartbeat runs. Thanks @shakkernerd.
|
||||
- **Buzz native mentions:** resolve unique current room-member names and explicit NIP-27 identities into native `p` tags for replies, proactive sends, and bounded standalone delivery; reject out-of-room identities and unresolved labels without an explicit identity, and preserve Buzz reply-thread session parsing during maintenance and heartbeat runs. Thanks @shakkernerd.
|
||||
- **ClickClack guided setup:** configure ClickClack from `openclaw onboard` or `openclaw channels add clickclack` with URL, token, and workspace prompts, default-account env fallback, nonfatal live connection validation, and gateway-aware next steps that connect automatically when OpenClaw is already running. Thanks @shakkernerd.
|
||||
- **ClickClack command menus:** publish each bot's native OpenClaw commands to ClickClack composer autocomplete at gateway startup, with per-account opt-out and nonfatal compatibility handling for older tokens and servers. Thanks @shakkernerd.
|
||||
- **Skill Workshop approvals:** run agent-initiated apply, reject, and quarantine actions without an additional approval prompt by default while preserving `skills.workshop.approvalPolicy: "pending"` as an opt-in approval gate. Thanks @shakkernerd.
|
||||
|
||||
@@ -204,12 +204,15 @@ openclaw message send \
|
||||
--message "Please review this, nostr:npub1..."
|
||||
```
|
||||
|
||||
The referenced public key must be a current member of the target room. Unknown
|
||||
names, duplicate profile names, and out-of-room public keys fail visibly instead
|
||||
of sending text that looks like a mention without notifying anyone. Ambiguous
|
||||
The referenced public key must be a current member of the target room. Without
|
||||
an explicit identity, unknown names and duplicate profile names fail visibly
|
||||
instead of sending text that looks like a mention without notifying anyone.
|
||||
When the message contains an explicit identity, unresolved or ambiguous labels
|
||||
remain presentation text; include every intended identity explicitly. Ambiguous
|
||||
errors list candidate public keys so the sender can retry with the intended
|
||||
`nostr:npub...` identity. Mention-like text inside inline or fenced Markdown
|
||||
code is ignored, and one message can carry at most 50 native mentions.
|
||||
`nostr:npub...` identity. Out-of-room public keys always fail. Mention-like text
|
||||
inside inline or fenced Markdown code is ignored, and one message can carry at
|
||||
most 50 native mentions.
|
||||
|
||||
Connected Gateways resolve names from their existing in-memory directory
|
||||
snapshot and do not query the relay per message. Profiles beyond the bounded
|
||||
|
||||
@@ -9,7 +9,7 @@ describe("Buzz channel guidance", () => {
|
||||
"- Buzz targets: use a configured room UUID, `buzz:<ROOM_UUID>`, or a unique current room name. Use the UUID when room names are ambiguous.",
|
||||
);
|
||||
expect(hints).toContain(
|
||||
"- Buzz mentions: write a unique current room member as `@Display Name`. For an explicit identity, include `nostr:npub...`; the public key must belong to the target room. Ambiguous, unknown, or out-of-room mentions fail instead of sending untagged mention text.",
|
||||
"- Buzz mentions: write a unique current room member as `@Display Name`. For an explicit identity, include `nostr:npub...`; the public key must belong to the target room. Any unresolved or ambiguous label needs an explicit identity for every intended member.",
|
||||
);
|
||||
expect(buzzPlugin.messaging?.targetResolver?.hint).toBe("<room UUID|configured room name>");
|
||||
});
|
||||
|
||||
@@ -72,7 +72,7 @@ export const buzzPlugin = createChatChannelPlugin<ResolvedBuzzAccount, BuzzProbe
|
||||
agentPrompt: {
|
||||
messageToolHints: () => [
|
||||
"- Buzz targets: use a configured room UUID, `buzz:<ROOM_UUID>`, or a unique current room name. Use the UUID when room names are ambiguous.",
|
||||
"- Buzz mentions: write a unique current room member as `@Display Name`. For an explicit identity, include `nostr:npub...`; the public key must belong to the target room. Ambiguous, unknown, or out-of-room mentions fail instead of sending untagged mention text.",
|
||||
"- Buzz mentions: write a unique current room member as `@Display Name`. For an explicit identity, include `nostr:npub...`; the public key must belong to the target room. Any unresolved or ambiguous label needs an explicit identity for every intended member.",
|
||||
],
|
||||
},
|
||||
reload: { configPrefixes: ["channels.buzz"] },
|
||||
|
||||
@@ -86,7 +86,7 @@ describe("Buzz outbound mentions", () => {
|
||||
).toEqual([ALICE_PUBLIC_KEY]);
|
||||
});
|
||||
|
||||
it("requires explicit identities to resolve the named member they accompany", () => {
|
||||
it("allows unresolved labels as presentation text when an explicit identity is present", () => {
|
||||
const explicitBob = nip19.npubEncode(BOB_PUBLIC_KEY);
|
||||
const roomMembers = members(
|
||||
{ publicKey: ALICE_PUBLIC_KEY, displayName: "Alice" },
|
||||
@@ -94,21 +94,21 @@ describe("Buzz outbound mentions", () => {
|
||||
{ publicKey: BOB_PUBLIC_KEY, displayName: "Bob" },
|
||||
);
|
||||
|
||||
expect(() =>
|
||||
expect(
|
||||
resolveBuzzMessageMentions({
|
||||
text: `Hello @Missing (nostr:${explicitBob})`,
|
||||
members: roomMembers,
|
||||
senderPublicKey: BOT_PUBLIC_KEY,
|
||||
}),
|
||||
).toThrow('Buzz mention "@missing" does not match a current room member');
|
||||
).toEqual([BOB_PUBLIC_KEY]);
|
||||
|
||||
expect(() =>
|
||||
expect(
|
||||
resolveBuzzMessageMentions({
|
||||
text: `Hello @Alice (nostr:${explicitBob})`,
|
||||
members: roomMembers,
|
||||
senderPublicKey: BOT_PUBLIC_KEY,
|
||||
}),
|
||||
).toThrow('Buzz mention "@alice" is ambiguous');
|
||||
).toEqual([BOB_PUBLIC_KEY]);
|
||||
});
|
||||
|
||||
it("bounds ambiguous-member guidance", () => {
|
||||
|
||||
@@ -20,6 +20,10 @@ function isAsciiWhitespace(character: string | undefined): boolean {
|
||||
return character !== undefined && /^[\t-\r ]$/u.test(character);
|
||||
}
|
||||
|
||||
function isOnlyAsciiWhitespace(value: string): boolean {
|
||||
return /^[\t-\r ]*$/u.test(value);
|
||||
}
|
||||
|
||||
function isMentionBoundary(value: string): boolean {
|
||||
const character = value[0];
|
||||
return character === undefined || isAsciiWhitespace(character) || ",;.!?:)]}".includes(character);
|
||||
@@ -32,7 +36,7 @@ function stripCodeRegions(content: string): string {
|
||||
if (content.startsWith("```", index)) {
|
||||
const lineStart = content.lastIndexOf("\n", index - 1) + 1;
|
||||
const beforeFence = content.slice(lineStart, index);
|
||||
if ([...beforeFence].every(isAsciiWhitespace)) {
|
||||
if (isOnlyAsciiWhitespace(beforeFence)) {
|
||||
const openingLineEnd = content.indexOf("\n", index + 3);
|
||||
let searchFrom = openingLineEnd === -1 ? content.length : openingLineEnd + 1;
|
||||
let closeEnd = content.length;
|
||||
@@ -43,7 +47,7 @@ function stripCodeRegions(content: string): string {
|
||||
}
|
||||
const closingLineStart = content.lastIndexOf("\n", closingFence - 1) + 1;
|
||||
const beforeClosingFence = content.slice(closingLineStart, closingFence);
|
||||
if ([...beforeClosingFence].every(isAsciiWhitespace)) {
|
||||
if (isOnlyAsciiWhitespace(beforeClosingFence)) {
|
||||
const closingLineEnd = content.indexOf("\n", closingFence + 3);
|
||||
closeEnd = closingLineEnd === -1 ? content.length : closingLineEnd + 1;
|
||||
break;
|
||||
@@ -237,7 +241,7 @@ export function resolveBuzzMessageMentions(params: {
|
||||
.map((member) => member.displayName)
|
||||
.filter((name): name is string => Boolean(name)),
|
||||
);
|
||||
if (hasAtMentionCandidate(stripped) && names.length === 0) {
|
||||
if (hasAtMentionCandidate(stripped) && names.length === 0 && explicitPublicKeys.length === 0) {
|
||||
throw new Error(
|
||||
"Buzz mention does not match a current room member; use nostr:npub... for an explicit identity",
|
||||
);
|
||||
@@ -245,12 +249,15 @@ export function resolveBuzzMessageMentions(params: {
|
||||
for (const name of names) {
|
||||
const matches = namesToPublicKeys.get(name) ?? [];
|
||||
if (matches.length === 0) {
|
||||
if (explicitPublicKeys.length > 0) {
|
||||
continue;
|
||||
}
|
||||
throw new Error(
|
||||
`Buzz mention "@${name}" does not match a current room member; use nostr:npub... for an explicit identity`,
|
||||
);
|
||||
}
|
||||
if (matches.length > 1) {
|
||||
if (matches.some((publicKey) => explicitPublicKeys.includes(publicKey))) {
|
||||
if (explicitPublicKeys.length > 0) {
|
||||
continue;
|
||||
}
|
||||
const visibleCandidates = matches
|
||||
|
||||
Reference in New Issue
Block a user