desktop: hydrate managed agents before Welcome provisioning

Make the owner-scoped persona sync backfill awaitable and run it before Welcome onboarding inspects local managed agents. This lets a second installation reconstruct relay-canonical identities before deciding whether to mint replacements.

Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
This commit is contained in:
Wes
2026-08-05 12:39:33 -06:00
committed by Carl
parent 57bb681b69
commit 94b96752c9
3 changed files with 59 additions and 9 deletions

View File

@@ -22,6 +22,23 @@ const PERSONA_SYNC_KINDS = [
KIND_DELETION,
];
export async function hydratePersonaSync(
pubkey: string,
relayUrl: string,
onCancelled: () => boolean = () => false,
): Promise<void> {
const events = await relayClient.fetchEvents({
kinds: PERSONA_SYNC_KINDS,
authors: [pubkey],
limit: 500,
});
for (const event of events) {
if (onCancelled()) return;
if (event.pubkey !== pubkey) continue;
await reconcileInboundPersonaEvent(JSON.stringify(event), relayUrl);
}
}
// Start the persona/team/agent/deletion sync for `pubkey` on `relayUrl`:
// one-shot backfill of existing heads + tombstones, then a live subscription.
// Returns a disposer that closes the live subscription. Extracted from the hook
@@ -48,15 +65,9 @@ export function startPersonaSync(
// One-shot backfill of existing heads + tombstones (closes the fresh-start
// gap that live-only subscription + reconnect-replay cannot recover).
void relayClient
.fetchEvents({ kinds: PERSONA_SYNC_KINDS, authors: [pubkey], limit: 500 })
.then((events) => {
if (onCancelled()) return;
for (const event of events) reconcile(event);
})
.catch((error) => {
console.warn("[usePersonaSync] backfill failed:", error);
});
void hydratePersonaSync(pubkey, relayUrl, onCancelled).catch((error) => {
console.warn("[usePersonaSync] backfill failed:", error);
});
let unsub: (() => Promise<void>) | null = null;
void relayClient

View File

@@ -4,6 +4,7 @@ import test from "node:test";
import {
activateWelcomeTeamPersonasSequentially,
buildWelcomeStarterCreateInput,
hydrateWelcomeTeamAgentsBeforeProvision,
LEGACY_WELCOME_GUIDE_SYSTEM_PROMPT,
pickWelcomeGuideAgent,
pickWelcomeGuideAgentForRelay,
@@ -15,6 +16,30 @@ import {
WELCOME_TEAM_STARTERS,
} from "./welcomeGuide.ts";
test("Welcome provisioning awaits relay hydration before inspecting agents", async () => {
const calls = [];
await hydrateWelcomeTeamAgentsBeforeProvision(
"wss://relay.example///",
async () => {
calls.push("identity");
return { pubkey: PUB_A };
},
async (pubkey, relayUrl) => {
calls.push(`hydrate:${pubkey}:${relayUrl}`);
},
);
assert.deepEqual(calls, ["identity", `hydrate:${PUB_A}:wss://relay.example`]);
});
test("Welcome provisioning skips hydration without a relay scope", async () => {
let called = false;
await hydrateWelcomeTeamAgentsBeforeProvision(null, async () => {
called = true;
return { pubkey: PUB_A };
});
assert.equal(called, false);
});
const PUB_A = "a".repeat(64);
const PUB_B = "b".repeat(64);
const PUB_C = "c".repeat(64);

View File

@@ -1,3 +1,4 @@
import { hydratePersonaSync } from "@/features/agents/lib/usePersonaSync";
import {
buildInstanceInputForDefinition,
resolveStartRuntimeForDefinition,
@@ -11,6 +12,7 @@ import {
updateManagedAgent,
} from "@/shared/api/tauri";
import { getGlobalAgentConfig } from "@/shared/api/tauriGlobalAgentConfig";
import { getIdentity } from "@/shared/api/tauriIdentity";
import { listPersonas, setPersonaActive } from "@/shared/api/tauriPersonas";
import type {
AcpRuntime,
@@ -53,6 +55,17 @@ function normalizeRelayUrl(relayUrl: string | null | undefined) {
return relayUrl?.trim().replace(/\/+$/, "") ?? null;
}
export async function hydrateWelcomeTeamAgentsBeforeProvision(
relayUrl: string | null | undefined,
resolveIdentity: typeof getIdentity = getIdentity,
hydrate: typeof hydratePersonaSync = hydratePersonaSync,
): Promise<void> {
const normalizedRelay = normalizeRelayUrl(relayUrl);
if (!normalizedRelay) return;
const identity = await resolveIdentity();
await hydrate(identity.pubkey, normalizedRelay);
}
function isAgentScopedToRelay(agent: ManagedAgent, relayUrl?: string | null) {
const targetRelayUrl = normalizeRelayUrl(relayUrl);
if (!targetRelayUrl) {
@@ -269,6 +282,7 @@ async function provisionWelcomeTeam(
channelId: string,
relayUrl?: string | null,
): Promise<WelcomeTeamAgents> {
await hydrateWelcomeTeamAgentsBeforeProvision(relayUrl);
const existingAgents = await listManagedAgents();
await ensureWelcomeTeamPersonasActive();
const [personas, runtimeCatalog, globalConfig] = await Promise.all([