Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ export function ChannelComposerActivityAccessory({
testId="channel-composer-activity-row"
visible={visible}
>
<div className="flex w-full items-center gap-2 overflow-visible pl-2">
<div className="flex w-full min-w-0 items-center justify-start gap-2 overflow-visible pl-2">
{cardMintJobs.length > 0 ? <CardMintComposerChip /> : null}
{workingBotPubkeys.length > 0 ? (
<div className="flex min-w-0 flex-1 overflow-visible">
<div className="flex min-w-0 shrink overflow-visible">
<BotActivityComposerAction
agents={agents}
channelId={channel?.id ?? null}
Expand All @@ -58,7 +58,7 @@ export function ChannelComposerActivityAccessory({
{typingPubkeys.length > 0 ? (
<TypingIndicatorRow
channel={channel}
className="min-w-0 flex-1 py-0 pl-[calc(0.75rem+1px)] pr-0 sm:pl-[calc(1rem+1px)]"
className="min-w-0 shrink py-0 px-0"
currentPubkey={currentPubkey}
profiles={profiles}
typingPubkeys={typingPubkeys}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
channelScopedBotTypingPubkeyKey,
mergeMemberAgentFlagsIntoProfiles,
} from "./useChannelActivityTyping.ts";
import { getChannelAgentSessionAgents } from "./useChannelAgentSessions.ts";

const AGENT =
"abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234";
Expand Down Expand Up @@ -43,6 +44,68 @@ describe("channelScopedBotTypingPubkeyKey", () => {
});
});

describe("DM agent typing classification", () => {
it("admits a known relay agent who participates in the active DM", () => {
const dm = {
id: "dm-1",
name: "Sigma DM",
channelType: "dm",
participantPubkeys: [AGENT, AGENT_2],
};
const agents = [
{
pubkey: AGENT,
name: "Sigma",
status: "deployed",
agentSource: "relay",
canInterruptTurn: false,
channelIds: [],
channels: [],
},
];

assert.deepEqual(
getChannelAgentSessionAgents({
activeChannel: dm,
activeChannelId: dm.id,
agents,
channelMembers: [],
}),
agents,
);
});

it("does not admit a known agent who is not a DM participant", () => {
const dm = {
id: "dm-1",
name: "Sigma DM",
channelType: "dm",
participantPubkeys: [AGENT_2],
};
const agents = [
{
pubkey: AGENT,
name: "Sigma",
status: "deployed",
agentSource: "relay",
canInterruptTurn: false,
channelIds: [],
channels: [],
},
];

assert.deepEqual(
getChannelAgentSessionAgents({
activeChannel: dm,
activeChannelId: dm.id,
agents,
channelMembers: [],
}),
[],
);
});
});

describe("thread-only bot typing regression", () => {
beforeEach(() => {
resetActiveAgentTurnsStore();
Expand Down
15 changes: 15 additions & 0 deletions desktop/src/features/channels/ui/useChannelAgentSessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,14 @@ export function getChannelAgentSessionAgents({
.map((member) => normalizePubkey(member.pubkey)),
)
: null;
const dmParticipantPubkeys =
activeChannel.channelType === "dm"
? new Set(
activeChannel.participantPubkeys.map((pubkey) =>
normalizePubkey(pubkey),
),
)
: null;

return agents.filter((agent) => {
const normalizedPubkey = normalizePubkey(agent.pubkey);
Expand All @@ -142,6 +150,13 @@ export function getChannelAgentSessionAgents({
channelIds.includes(activeChannelId) ||
channels.includes(activeChannel.name);

// DMs do not reliably expose channel membership or directory channel
// scope. A participant that is already a known managed/relay agent must
// still be classified as an agent so its typing feeds the working signal.
if (dmParticipantPubkeys?.has(normalizedPubkey)) {
return true;
}

if (agent.agentSource === "member-bot") {
return botMemberPubkeys?.has(normalizedPubkey) ?? matchesDeclaredChannel;
}
Expand Down