fix(desktop): avoid internal welcome agent churn

Co-authored-by: npub102wg7q285p64ch2fjvstmf2ntn2sz3c4u5hmwatalc76mhsuauysftjtfj <7a9c8f0147a0755c5d499320bda5535cd5014715e52fb7757dfe3dadde1cef09@buzz.block.builderlab.xyz>
Signed-off-by: npub102wg7q285p64ch2fjvstmf2ntn2sz3c4u5hmwatalc76mhsuauysftjtfj <7a9c8f0147a0755c5d499320bda5535cd5014715e52fb7757dfe3dadde1cef09@buzz.block.builderlab.xyz>
This commit is contained in:
npub102wg7q285p64ch2fjvstmf2ntn2sz3c4u5hmwatalc76mhsuauysftjtfj
2026-07-23 21:09:20 -07:00
parent 7f7c3c7a64
commit d89b694856
4 changed files with 116 additions and 15 deletions

View File

@@ -9,6 +9,7 @@ import {
pickWelcomeGuideAgentForRelay,
pickWelcomeTeamStarterAgentForRelay,
welcomeStarterRuntimeUpdate,
welcomeTeammateHasExpectedAccess,
WELCOME_GUIDE_AGENT_NAME,
WELCOME_GUIDE_PERSONA_ID,
WELCOME_TEAM_ID,
@@ -379,3 +380,33 @@ test("starter matching prefers running, then deployed instances", () => {
deployed,
);
});
test("internal local Welcome teammates accept enforced owner-only access", () => {
const teammate = makeAgent({
respondTo: "owner-only",
respondToAllowlist: [],
});
assert.equal(welcomeTeammateHasExpectedAccess(teammate, PUB_B, true), true);
assert.equal(welcomeTeammateHasExpectedAccess(teammate, PUB_B, false), false);
});
test("provider Welcome teammates still require the lead allowlist", () => {
const teammate = makeAgent({
backend: { type: "provider", id: "remote", config: {} },
respondTo: "owner-only",
respondToAllowlist: [],
});
assert.equal(welcomeTeammateHasExpectedAccess(teammate, PUB_B, true), false);
assert.equal(
welcomeTeammateHasExpectedAccess(
{
...teammate,
respondTo: "allowlist",
respondToAllowlist: [PUB_B],
},
PUB_B,
true,
),
true,
);
});

View File

@@ -6,6 +6,7 @@ import {
addChannelMembers,
createManagedAgent,
discoverAcpRuntimes,
getAgentAccessOwnerOnly,
getChannelMembers,
listManagedAgents,
updateManagedAgent,
@@ -260,6 +261,25 @@ export function welcomeStarterRuntimeUpdate(
};
}
export function welcomeTeammateHasExpectedAccess(
teammate: ManagedAgent,
leadPubkey: string,
agentAccessOwnerOnly: boolean,
) {
if (agentAccessOwnerOnly && teammate.backend.type === "local") {
return (
teammate.respondTo === "owner-only" &&
teammate.respondToAllowlist.length === 0
);
}
return (
teammate.respondTo === "allowlist" &&
teammate.respondToAllowlist.some(
(pubkey) => normalizePubkey(pubkey) === normalizePubkey(leadPubkey),
)
);
}
/**
* Ensure the complete built-in Welcome Team is ready for kickoff.
* The team itself is Rust-seeded; this only activates personas, creates any
@@ -271,11 +291,13 @@ async function provisionWelcomeTeam(
): Promise<WelcomeTeamAgents> {
const existingAgents = await listManagedAgents();
await ensureWelcomeTeamPersonasActive();
const [personas, runtimeCatalog, globalConfig] = await Promise.all([
listPersonas(),
discoverAcpRuntimes(),
getGlobalAgentConfig(),
]);
const [personas, runtimeCatalog, globalConfig, agentAccessOwnerOnly] =
await Promise.all([
listPersonas(),
discoverAcpRuntimes(),
getGlobalAgentConfig(),
getAgentAccessOwnerOnly(),
]);
const personasById = new Map(
personas.map((persona) => [persona.id, persona]),
);
@@ -322,11 +344,11 @@ async function provisionWelcomeTeam(
const leadPubkey = lead.pubkey;
for (const index of [1, 2] as const) {
const teammate = welcomeAgents[index];
const alreadyAllowsLead =
teammate.respondTo === "allowlist" &&
teammate.respondToAllowlist.some(
(pubkey) => normalizePubkey(pubkey) === normalizePubkey(leadPubkey),
);
const alreadyAllowsLead = welcomeTeammateHasExpectedAccess(
teammate,
leadPubkey,
agentAccessOwnerOnly,
);
if (!alreadyAllowsLead) {
const updated = await updateManagedAgent({
pubkey: teammate.pubkey,

View File

@@ -199,6 +199,42 @@ test("running teammates restart when their allowlist does not include the lead",
);
});
test("internal running teammates do not restart for enforced owner-only access", () => {
assert.equal(
welcomeTeammateNeedsRestart(
{
...honey,
backend: { type: "local" },
status: "running",
needsRestart: false,
respondTo: "owner-only",
respondToAllowlist: [],
},
fizz.pubkey,
true,
),
false,
);
});
test("internal running teammates still restart for runtime changes", () => {
assert.equal(
welcomeTeammateNeedsRestart(
{
...honey,
backend: { type: "local" },
status: "running",
needsRestart: true,
respondTo: "owner-only",
respondToAllowlist: [],
},
fizz.pubkey,
true,
),
true,
);
});
test("opener keeps partial-readiness warm and mentions only online teammates", () => {
const agentSet = { lead: fizz, teammates: [honey, bumble] };
const introTeammates = selectWelcomeKickoffIntroTeammates(

View File

@@ -3,6 +3,7 @@ import * as React from "react";
import {
managedAgentsQueryKey,
useAcpRuntimesQuery,
useAgentAccessOwnerOnlyQuery,
useManagedAgentsQuery,
} from "@/features/agents/hooks";
import { useGlobalAgentConfig } from "@/features/agents/useGlobalAgentConfig";
@@ -14,6 +15,7 @@ import {
pickWelcomeTeamStarterAgentForRelay,
WELCOME_TEAM_STARTERS,
type WelcomeTeamStarterDefinition,
welcomeTeammateHasExpectedAccess,
} from "@/features/onboarding/welcomeGuide";
import { isWelcomeChannel } from "@/features/onboarding/welcome";
import { getThreadReference } from "@/features/messages/lib/threading";
@@ -357,13 +359,15 @@ async function markerExists(channelId: string, marker: string) {
export function welcomeTeammateNeedsRestart(
agent: ManagedAgent,
leadPubkey: string,
agentAccessOwnerOnly = false,
) {
return (
agent.status === "running" &&
(agent.needsRestart ||
agent.respondTo !== "allowlist" ||
!agent.respondToAllowlist.some(
(pubkey) => normalizePubkey(pubkey) === normalizePubkey(leadPubkey),
!welcomeTeammateHasExpectedAccess(
agent,
leadPubkey,
agentAccessOwnerOnly,
))
);
}
@@ -493,6 +497,8 @@ export function useWelcomeKickoff(
const { activeCommunity } = useCommunities();
const runtimesQuery = useAcpRuntimesQuery();
const managedAgentsQuery = useManagedAgentsQuery();
const agentAccessOwnerOnlyQuery = useAgentAccessOwnerOnlyQuery();
const agentAccessOwnerOnly = agentAccessOwnerOnlyQuery.data;
const { globalConfig, isLoading: configLoading } = useGlobalAgentConfig();
const channelId = activeChannel?.id ?? null;
const isActiveWelcome = isWelcomeChannel(activeChannel);
@@ -553,7 +559,8 @@ export function useWelcomeKickoff(
!channelId ||
!isActiveWelcome ||
configLoading ||
runtimesQuery.isPending
runtimesQuery.isPending ||
agentAccessOwnerOnly === undefined
) {
return;
}
@@ -607,7 +614,11 @@ export function useWelcomeKickoff(
);
if (
isTeammate &&
welcomeTeammateNeedsRestart(agent, resolvedAgentSet.lead.pubkey)
welcomeTeammateNeedsRestart(
agent,
resolvedAgentSet.lead.pubkey,
agentAccessOwnerOnly,
)
) {
return restartWelcomeTeammate(agent);
}
@@ -682,6 +693,7 @@ export function useWelcomeKickoff(
})();
}, [
activeCommunity?.relayUrl,
agentAccessOwnerOnly,
channelId,
configLoading,
isActiveWelcome,