Skip to content
This repository was archived by the owner on Feb 27, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions extensions/convos/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as path from "node:path";
import { emptyPluginConfigSchema, renderQrPngBase64 } from "openclaw/plugin-sdk";
import type { ConvosSDKClient } from "./src/sdk-client.js";
import { resolveConvosAccount, type CoreConfig } from "./src/accounts.js";
import { convosPlugin } from "./src/channel.js";
import { convosPlugin, triggerGreeting } from "./src/channel.js";
import { getClientForAccount } from "./src/outbound.js";
import { getConvosRuntime, setConvosRuntime, setConvosSetupActive } from "./src/runtime.js";
import { resolveConvosDbPath } from "./src/sdk-client.js";
Expand Down Expand Up @@ -446,6 +446,21 @@ const plugin = {
}

const result = await client.joinConversation(inviteUrl, name);

// Trigger a generated greeting in the background after joining.
if (result.status === "joined") {
triggerGreeting({
account,
conversationId: result.conversationId,
senderId: "system",
context:
"[System] You've just been added to a user's pre-existing chat. Introduce yourself concisely.",
runtime,
}).catch((err) => {
console.error("[convos-join] Greeting generation failed:", err);
});
}

jsonResponse(res, 200, {
conversationId: result.conversationId,
status: result.status,
Expand All @@ -467,7 +482,8 @@ const plugin = {
}
try {
const body = await readJsonBody(req);
const conversationId = typeof body.conversationId === "string" ? body.conversationId : undefined;
const conversationId =
typeof body.conversationId === "string" ? body.conversationId : undefined;
const message = typeof body.message === "string" ? body.message : undefined;
if (!conversationId || !message) {
jsonResponse(res, 400, { error: "conversationId and message (strings) are required" });
Expand Down Expand Up @@ -505,7 +521,8 @@ const plugin = {
}
try {
const body = await readJsonBody(req);
const conversationId = typeof body.conversationId === "string" ? body.conversationId : undefined;
const conversationId =
typeof body.conversationId === "string" ? body.conversationId : undefined;
const name = typeof body.name === "string" ? body.name : undefined;

if (!conversationId) {
Expand Down
45 changes: 45 additions & 0 deletions extensions/convos/src/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,10 @@ export const convosPlugin: ChannelPlugin<ResolvedConvosAccount> = {
`[${account.accountId}] XMTP stateDir: ${stateDir}, cwd: ${process.cwd()}, dbPath: ${dbPath}`,
);

// Track conversations that have already received a greeting so we
// only greet the first user who joins, not every subsequent invite.
const greetedConversations = new Set<string>();

// Create SDK client with message handling
const client = await ConvosSDKClient.create({
privateKey: account.privateKey,
Expand All @@ -294,6 +298,22 @@ export const convosPlugin: ChannelPlugin<ResolvedConvosAccount> = {
// TODO: Add policy-based handling
log?.info(`[${account.accountId}] Auto-accepting invite request`);
await inviteCtx.accept();

// Greet only the first user who joins each conversation.
if (!greetedConversations.has(inviteCtx.conversationId)) {
greetedConversations.add(inviteCtx.conversationId);
triggerGreeting({
account,
conversationId: inviteCtx.conversationId,
senderId: inviteCtx.joinerInboxId,
context:
"[System] You've just created this group and a user has just joined. Introduce yourself concisely.",
runtime,
log,
}).catch((err) => {
log?.error(`[${account.accountId}] Greeting generation failed: ${String(err)}`);
});
}
},
});

Expand Down Expand Up @@ -508,6 +528,31 @@ async function deliverConvosReply(params: {
}
}

/**
* Trigger a generated greeting by feeding a synthetic system message through
* the normal reply pipeline. The agent will produce a contextual introduction
* based on its instructions / system prompt.
*/
export async function triggerGreeting(params: {
account: ResolvedConvosAccount;
conversationId: string;
senderId: string;
context: string;
runtime: PluginRuntime;
log?: RuntimeLogger;
}): Promise<void> {
const { account, conversationId, senderId, context, runtime, log } = params;
const syntheticMsg: InboundMessage = {
conversationId,
messageId: `greeting-${Date.now()}`,
senderId,
senderName: "",
content: context,
timestamp: new Date(),
};
await handleInboundMessage(account, syntheticMsg, runtime, log);
}

/**
* Stop SDK client for an account
*/
Expand Down
Loading