Skip to content
This repository was archived by the owner on Feb 27, 2026. It is now read-only.

Commit 24b9397

Browse files
saulmcclaude
andcommitted
feat: send generated greeting when user joins via invite
After accepting a join request (onInvite) or joining a conversation (/convos/join), trigger the normal reply pipeline with a system context message so the agent generates a contextual introduction based on its instructions. No new endpoints needed — greetings fire automatically from the existing invite-accept and join handlers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 102657e commit 24b9397

2 files changed

Lines changed: 65 additions & 3 deletions

File tree

extensions/convos/index.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as path from "node:path";
55
import { emptyPluginConfigSchema, renderQrPngBase64 } from "openclaw/plugin-sdk";
66
import type { ConvosSDKClient } from "./src/sdk-client.js";
77
import { resolveConvosAccount, type CoreConfig } from "./src/accounts.js";
8-
import { convosPlugin } from "./src/channel.js";
8+
import { convosPlugin, triggerGreeting } from "./src/channel.js";
99
import { getClientForAccount } from "./src/outbound.js";
1010
import { getConvosRuntime, setConvosRuntime, setConvosSetupActive } from "./src/runtime.js";
1111
import { resolveConvosDbPath } from "./src/sdk-client.js";
@@ -446,6 +446,21 @@ const plugin = {
446446
}
447447

448448
const result = await client.joinConversation(inviteUrl, name);
449+
450+
// Trigger a generated greeting in the background after joining.
451+
if (result.status === "joined") {
452+
triggerGreeting({
453+
account,
454+
conversationId: result.conversationId,
455+
senderId: "system",
456+
context:
457+
"[System] You've just been added to a user's pre-existing chat. Introduce yourself concisely.",
458+
runtime,
459+
}).catch((err) => {
460+
console.error("[convos-join] Greeting generation failed:", err);
461+
});
462+
}
463+
449464
jsonResponse(res, 200, {
450465
conversationId: result.conversationId,
451466
status: result.status,
@@ -467,7 +482,8 @@ const plugin = {
467482
}
468483
try {
469484
const body = await readJsonBody(req);
470-
const conversationId = typeof body.conversationId === "string" ? body.conversationId : undefined;
485+
const conversationId =
486+
typeof body.conversationId === "string" ? body.conversationId : undefined;
471487
const message = typeof body.message === "string" ? body.message : undefined;
472488
if (!conversationId || !message) {
473489
jsonResponse(res, 400, { error: "conversationId and message (strings) are required" });
@@ -505,7 +521,8 @@ const plugin = {
505521
}
506522
try {
507523
const body = await readJsonBody(req);
508-
const conversationId = typeof body.conversationId === "string" ? body.conversationId : undefined;
524+
const conversationId =
525+
typeof body.conversationId === "string" ? body.conversationId : undefined;
509526
const name = typeof body.name === "string" ? body.name : undefined;
510527

511528
if (!conversationId) {

extensions/convos/src/channel.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,10 @@ export const convosPlugin: ChannelPlugin<ResolvedConvosAccount> = {
277277
`[${account.accountId}] XMTP stateDir: ${stateDir}, cwd: ${process.cwd()}, dbPath: ${dbPath}`,
278278
);
279279

280+
// Track conversations that have already received a greeting so we
281+
// only greet the first user who joins, not every subsequent invite.
282+
const greetedConversations = new Set<string>();
283+
280284
// Create SDK client with message handling
281285
const client = await ConvosSDKClient.create({
282286
privateKey: account.privateKey,
@@ -294,6 +298,22 @@ export const convosPlugin: ChannelPlugin<ResolvedConvosAccount> = {
294298
// TODO: Add policy-based handling
295299
log?.info(`[${account.accountId}] Auto-accepting invite request`);
296300
await inviteCtx.accept();
301+
302+
// Greet only the first user who joins each conversation.
303+
if (!greetedConversations.has(inviteCtx.conversationId)) {
304+
greetedConversations.add(inviteCtx.conversationId);
305+
triggerGreeting({
306+
account,
307+
conversationId: inviteCtx.conversationId,
308+
senderId: inviteCtx.joinerInboxId,
309+
context:
310+
"[System] You've just created this group and a user has just joined. Introduce yourself concisely.",
311+
runtime,
312+
log,
313+
}).catch((err) => {
314+
log?.error(`[${account.accountId}] Greeting generation failed: ${String(err)}`);
315+
});
316+
}
297317
},
298318
});
299319

@@ -508,6 +528,31 @@ async function deliverConvosReply(params: {
508528
}
509529
}
510530

531+
/**
532+
* Trigger a generated greeting by feeding a synthetic system message through
533+
* the normal reply pipeline. The agent will produce a contextual introduction
534+
* based on its instructions / system prompt.
535+
*/
536+
export async function triggerGreeting(params: {
537+
account: ResolvedConvosAccount;
538+
conversationId: string;
539+
senderId: string;
540+
context: string;
541+
runtime: PluginRuntime;
542+
log?: RuntimeLogger;
543+
}): Promise<void> {
544+
const { account, conversationId, senderId, context, runtime, log } = params;
545+
const syntheticMsg: InboundMessage = {
546+
conversationId,
547+
messageId: `greeting-${Date.now()}`,
548+
senderId,
549+
senderName: "",
550+
content: context,
551+
timestamp: new Date(),
552+
};
553+
await handleInboundMessage(account, syntheticMsg, runtime, log);
554+
}
555+
511556
/**
512557
* Stop SDK client for an account
513558
*/

0 commit comments

Comments
 (0)