mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
fix(desktop): allow restarting saved relay-mesh agents from the UI (#956)
Signed-off-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
@@ -39,23 +39,33 @@ function agent(overrides = {}) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
test("relay-mesh agents cannot be manually started without a fresh target", async () => {
|
test("relay-mesh agents delegate start to the backend preflight", async () => {
|
||||||
let called = false;
|
const meshAgent = agent({
|
||||||
await assert.rejects(
|
|
||||||
startManagedAgentWithRules({
|
|
||||||
agent: agent({
|
|
||||||
envVars: {
|
envVars: {
|
||||||
SPROUT_AGENT_PROVIDER: "openai",
|
SPROUT_AGENT_PROVIDER: "openai",
|
||||||
OPENAI_COMPAT_BASE_URL: "http://127.0.0.1:9337/v1/",
|
OPENAI_COMPAT_BASE_URL: "http://127.0.0.1:9337/v1/",
|
||||||
},
|
},
|
||||||
}),
|
});
|
||||||
|
|
||||||
|
let calledWith = null;
|
||||||
|
await startManagedAgentWithRules({
|
||||||
|
agent: meshAgent,
|
||||||
|
startManagedAgent: async (pubkey) => {
|
||||||
|
calledWith = pubkey;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert.equal(calledWith, meshAgent.pubkey);
|
||||||
|
|
||||||
|
// Backend preflight failures (e.g. no live serve target) propagate as-is.
|
||||||
|
await assert.rejects(
|
||||||
|
startManagedAgentWithRules({
|
||||||
|
agent: meshAgent,
|
||||||
startManagedAgent: async () => {
|
startManagedAgent: async () => {
|
||||||
called = true;
|
throw new Error("no live serve target is available for this model");
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
/Relay-mesh agents need a fresh serve target/,
|
/no live serve target/,
|
||||||
);
|
);
|
||||||
assert.equal(called, false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("ordinary local agents still start normally", async () => {
|
test("ordinary local agents still start normally", async () => {
|
||||||
|
|||||||
@@ -75,18 +75,6 @@ export function resolveManagedAgentChannelId(
|
|||||||
return matches.length === 1 ? matches[0].id : null;
|
return matches.length === 1 ? matches[0].id : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function relayMeshAgentError(agent: ManagedAgent): string | null {
|
|
||||||
if (agent.backend.type !== "local") return null;
|
|
||||||
if (agent.envVars.SPROUT_AGENT_PROVIDER !== "openai") return null;
|
|
||||||
if (
|
|
||||||
agent.envVars.OPENAI_COMPAT_BASE_URL?.replace(/\/+$/, "") !==
|
|
||||||
"http://127.0.0.1:9337/v1"
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return "Relay-mesh agents need a fresh serve target before start. Create a new agent with Run on relay mesh selected.";
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function startManagedAgentWithRules({
|
export async function startManagedAgentWithRules({
|
||||||
agent,
|
agent,
|
||||||
startManagedAgent,
|
startManagedAgent,
|
||||||
@@ -94,8 +82,9 @@ export async function startManagedAgentWithRules({
|
|||||||
agent: ManagedAgent;
|
agent: ManagedAgent;
|
||||||
startManagedAgent: StartManagedAgent;
|
startManagedAgent: StartManagedAgent;
|
||||||
}) {
|
}) {
|
||||||
const relayMeshError = relayMeshAgentError(agent);
|
// Relay-mesh agents are no longer blocked here: the backend start preflight
|
||||||
if (relayMeshError) throw new Error(relayMeshError);
|
// (ensure_relay_mesh_for_record) re-resolves a live serve target and dials
|
||||||
|
// it, failing with an actionable error when no peer serves the model.
|
||||||
await startManagedAgent(agent.pubkey);
|
await startManagedAgent(agent.pubkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,8 +97,6 @@ export async function respawnManagedAgentWithRules({
|
|||||||
startManagedAgent: StartManagedAgent;
|
startManagedAgent: StartManagedAgent;
|
||||||
stopManagedAgent: StopManagedAgent;
|
stopManagedAgent: StopManagedAgent;
|
||||||
}) {
|
}) {
|
||||||
const relayMeshError = relayMeshAgentError(agent);
|
|
||||||
if (relayMeshError) throw new Error(relayMeshError);
|
|
||||||
if (agent.backend.type === "local" && isManagedAgentActive(agent)) {
|
if (agent.backend.type === "local" && isManagedAgentActive(agent)) {
|
||||||
await stopManagedAgent(agent.pubkey);
|
await stopManagedAgent(agent.pubkey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4765,10 +4765,19 @@ async function handleStartManagedAgent(args: {
|
|||||||
}): Promise<RawManagedAgent> {
|
}): Promise<RawManagedAgent> {
|
||||||
const agent = getMockManagedAgent(args.pubkey);
|
const agent = getMockManagedAgent(args.pubkey);
|
||||||
if (isRelayMeshManagedAgent(agent)) {
|
if (isRelayMeshManagedAgent(agent)) {
|
||||||
|
// Model the backend start preflight (ensure_relay_mesh_for_record): a
|
||||||
|
// saved relay-mesh agent re-resolves a live serve target for its model
|
||||||
|
// and only fails when no peer currently serves it.
|
||||||
|
const modelId = agent.env_vars?.OPENAI_COMPAT_MODEL;
|
||||||
|
const hasLiveTarget =
|
||||||
|
mockMeshState.admitted &&
|
||||||
|
mockMeshState.models.some((model) => model.id === modelId);
|
||||||
|
if (!hasLiveTarget) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"relay mesh agents cannot be started from saved state because the selected serve target is not persisted. Create a new agent with Run on relay mesh selected to refresh the target for http://127.0.0.1:9337/v1.",
|
"relay mesh agents cannot be started from saved state because no live serve target is available for this model. Start serving on a mesh peer, or create a new agent with Run on relay mesh selected to refresh the target for http://127.0.0.1:9337/v1.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
if (agent.backend.type === "provider") {
|
if (agent.backend.type === "provider") {
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ test("a non-member cannot enable relay-mesh — membership is the gate", async (
|
|||||||
expect(seq).not.toContain("create_managed_agent");
|
expect(seq).not.toContain("create_managed_agent");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("saved relay-mesh agents require a fresh serve target before manual start", async ({
|
test("saved relay-mesh agents restart via the backend serve-target preflight", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
await gotoApp(page);
|
await gotoApp(page);
|
||||||
@@ -330,18 +330,30 @@ test("saved relay-mesh agents require a fresh serve target before manual start",
|
|||||||
.toContain("stop_managed_agent");
|
.toContain("stop_managed_agent");
|
||||||
await expect(row).toContainText("stopped");
|
await expect(row).toContainText("stopped");
|
||||||
|
|
||||||
const before = (await commands(page)).length;
|
// With a live serve target for the model, manual restart goes through:
|
||||||
|
// the backend preflight re-resolves the target and the agent starts.
|
||||||
|
await openManagedAgentActions(page, pubkey);
|
||||||
|
await page.getByRole("menuitem", { name: "Spawn" }).click();
|
||||||
|
await expect
|
||||||
|
.poll(async () => await commands(page))
|
||||||
|
.toContain("start_managed_agent");
|
||||||
|
await expect(row).toContainText("running");
|
||||||
|
|
||||||
|
await openManagedAgentActions(page, pubkey);
|
||||||
|
await page.getByRole("menuitem", { name: "Stop" }).click();
|
||||||
|
await expect(row).toContainText("stopped");
|
||||||
|
|
||||||
|
// Without a live serve target, the backend preflight rejects the start
|
||||||
|
// with an actionable error, surfaced as a toast; the agent stays stopped.
|
||||||
|
await setMesh(page, { models: [] });
|
||||||
await openManagedAgentActions(page, pubkey);
|
await openManagedAgentActions(page, pubkey);
|
||||||
await page.getByRole("menuitem", { name: "Spawn" }).click();
|
await page.getByRole("menuitem", { name: "Spawn" }).click();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page
|
page
|
||||||
.locator("[data-sonner-toast]")
|
.locator("[data-sonner-toast]")
|
||||||
.filter({ hasText: "Relay-mesh agents need a fresh serve target" }),
|
.filter({ hasText: "no live serve target is available" }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
expect((await commands(page)).slice(before)).not.toContain(
|
|
||||||
"start_managed_agent",
|
|
||||||
);
|
|
||||||
await expect(row).toContainText("stopped");
|
await expect(row).toContainText("stopped");
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
@@ -360,6 +372,6 @@ test("saved relay-mesh agents require a fresh serve target before manual start",
|
|||||||
return err instanceof Error ? err.message : String(err);
|
return err instanceof Error ? err.message : String(err);
|
||||||
}
|
}
|
||||||
}, pubkey),
|
}, pubkey),
|
||||||
).resolves.toContain("selected serve target is not persisted");
|
).resolves.toContain("no live serve target is available");
|
||||||
await expect(row).toContainText("stopped");
|
await expect(row).toContainText("stopped");
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user