Skip to content
Merged
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ Newest first. `Unreleased` is what is on `main` and not yet tagged.
a whole: the Bots under it fall back to an allowlist with nothing on it, so they reach nothing
until the rule is corrected under Admin → Enterprise.

### A group reply the owner allows still reaches the Bot it names

A reply held until its owner allowed it to be shown in a group was written into the transcript and then stopped. The same reply allowed immediately was handed to the Bot it named. Allowing it now hands it on the same way.

**Before upgrading.** Four things change for an existing deployment:

### The app offers help self-hosting OpenBot, until you close it

A slim bar at the top of the signed-in app links to CopilotKit's engineers for help self-hosting
Expand Down
7 changes: 7 additions & 0 deletions server/src/channels/group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1019,8 +1019,15 @@ export function createGroupConversations(deps: {
messageId: row.id,
agentIds: [agentId],
text: held.text,
...chainFrom(row.details),
};
await deps.activity?.(shown, agentId, held.text, `group:${row.id}`);
// The same reply allowed immediately would have been handed to the Bots it names.
// Holding it for the owner's permission must not drop that handoff.
const bots = await orderedRoster(row.ownerUserId, row.channelId);
const bot = bots?.find((candidate) => candidate.id === agentId);
if (bots && bot)
await relay(shown, bot, row.id, held.text, bots, runIdFor(row.id));
return { replyText: held.text };
}
try {
Expand Down
52 changes: 52 additions & 0 deletions server/tests/group-conversations.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,58 @@ describe("group conversations in PostgreSQL", () => {
text: "From your notes: the launch slipped.",
});
});
test("a reply held for the owner's permission still hands off to the Bot it names", async () => {
const owner = await person();
const teammate = await person();
const ada = await bot(owner, "Ada");
const grace = await bot(owner, "Grace");
let decision: "pending" | "approved" = "pending";
const privateShare = createPrivateShareCheck({
approvals: {
open: async () =>
({ id: `${prefix}-held-relay`, status: decision }) as never,
list: async () => [],
rules: async () => [],
},
});
const { conversations, audit } = service({
replies: {
[ada]: () => "Over to you, @Grace.",
[grace]: () => "On it.",
},
privateShare,
});
const channel = await conversations.create(owner.id, {
agentIds: [ada, grace],
});
const [row] = await database
.select({ email: users.email })
.from(users)
.where(eq(users.id, teammate.id));
await conversations.addMember(owner.id, channel.id, { email: row?.email });
await conversations.send(owner.id, channel.id, {
id: `${prefix}-held-relay-send`,
text: "ask grace",
agentId: ada,
});
await drain(conversations);
expect(
audit.filter((event) => event.eventType === "agent.handoff_offered"),
).toHaveLength(0);

decision = "approved";
await conversations.continueWaiting(`${prefix}-held-relay`, async () => {
throw new Error("a held reply is shown, not re-run");
});
await drain(conversations);
expect(
audit.filter((event) => event.eventType === "agent.handoff_offered"),
).toHaveLength(1);
const after = await conversations.list(owner.id, channel.id);
expect(after.messages.some((message) => message.text === "On it.")).toBe(
true,
);
});
test("while a reply waits on the owner's permission, other members do not see it stream in", async () => {
const owner = await person();
const teammate = await person();
Expand Down
Loading