mirror of
https://github.com/block/buzz.git
synced 2026-11-02 15:31:27 +00:00
fix(agents): add route-level tests, restore run-location context for instance edits
AgentEditDialog now wraps instance-present paths in AgentRunLocationProvider (matching AgentDialog's instance-edit arm) so RespondToField's remote-backend access warning has context when the agent runs on a remote machine. Three new routing tests in agentDialogRouting.test.mjs pin: - instance-with-definition → AgentRunLocationProvider wrapping AgentInstanceEditDialog - instance-only → same (unlinked-agent UpdateManagedAgentInput path) - definition-only → AgentEditDefinitionOnlyDialog sub-component Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This commit is contained in:
@@ -47,8 +47,10 @@ import type {
|
||||
} from "@/shared/api/types";
|
||||
import type { EditAgentFocusTarget } from "@/features/agents/openEditAgentEvent";
|
||||
import type { AgentDefinitionSubmitOptions } from "./AgentDefinitionDialog";
|
||||
import { runLocationForBackend } from "@/features/agents/lib/agentAccessWarning";
|
||||
import { AgentDefinitionDialog } from "./AgentDefinitionDialog";
|
||||
import { AgentInstanceEditDialog } from "./AgentInstanceEditDialog";
|
||||
import { AgentRunLocationProvider } from "./AgentRunLocationContext";
|
||||
import { editPersonaDialogState } from "./personaDialogState";
|
||||
|
||||
// ── Types ─────────────────────────────────────────────────────────────────────
|
||||
@@ -103,18 +105,26 @@ export function AgentEditDialog({
|
||||
// parallelism, env vars, harness pin, auto-restart, start-on-launch, instance
|
||||
// name) and owns a well-tested save path. Route all instance-present contexts
|
||||
// here so no I/L field is accidentally omitted.
|
||||
//
|
||||
// Wrap in AgentRunLocationProvider so RespondToField's access warning can
|
||||
// name the machine the agent actually runs on (mirrors AgentDialog's instance-
|
||||
// edit arm — backend → run location for the remote-backend warning path).
|
||||
if (ctx.kind === "instance-with-definition" || ctx.kind === "instance-only") {
|
||||
return (
|
||||
<AgentInstanceEditDialog
|
||||
agent={ctx.instance}
|
||||
initialFocus={initialFocus}
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
onUpdated={onUpdated}
|
||||
// R4 back-door deleted: avatar lives on the merged surface (definition
|
||||
// section is visible in the profile panel when a definition exists).
|
||||
onEditLinkedPersona={undefined}
|
||||
/>
|
||||
<AgentRunLocationProvider
|
||||
runLocation={runLocationForBackend(ctx.instance.backend)}
|
||||
>
|
||||
<AgentInstanceEditDialog
|
||||
agent={ctx.instance}
|
||||
initialFocus={initialFocus}
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
onUpdated={onUpdated}
|
||||
// R4 back-door deleted: avatar lives on the merged surface (definition
|
||||
// section is visible in the profile panel when a definition exists).
|
||||
onEditLinkedPersona={undefined}
|
||||
/>
|
||||
</AgentRunLocationProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { AgentDialog } from "./AgentDialog.tsx";
|
||||
import { AgentDefinitionDialog } from "./AgentDefinitionDialog.tsx";
|
||||
import { AgentInstanceEditDialog } from "./AgentInstanceEditDialog.tsx";
|
||||
import { AgentRunLocationProvider } from "./AgentRunLocationContext.tsx";
|
||||
import { AgentEditDialog } from "./AgentEditDialog.tsx";
|
||||
|
||||
// ── Phase 1B.3c routing pinning ─────────────────────────────────────────────
|
||||
//
|
||||
@@ -110,3 +111,124 @@ test("create mode routes to the internal create router, not a form directly", ()
|
||||
);
|
||||
assert.equal(element.type.name, "AgentCreateDialogRouter");
|
||||
});
|
||||
|
||||
// ── Phase 1 merged surface routing (AgentEditDialog) ────────────────────────
|
||||
//
|
||||
// AgentEditDialog collapses R1–R7 onto one entry point. Like AgentDialog, the
|
||||
// instance-present arms are hook-free so they can be called directly and the
|
||||
// returned element tree inspected. The definition-only arm delegates to a
|
||||
// sub-component that owns hooks, so we only confirm the type name there.
|
||||
//
|
||||
// These tests prove:
|
||||
// • instance-with-definition → AgentInstanceEditDialog (all I/L fields live here)
|
||||
// • instance-only → AgentInstanceEditDialog (unlinked agents save correctly)
|
||||
// • definition-only → AgentEditDefinitionOnlyDialog sub-component
|
||||
//
|
||||
// They also confirm the AgentRunLocationProvider wrapper is present on instance
|
||||
// paths (so RespondToField's remote-backend warning works).
|
||||
|
||||
const minimalDefinition = {
|
||||
id: "def-abc",
|
||||
displayName: "Bot",
|
||||
avatarUrl: "",
|
||||
systemPrompt: "Do stuff.",
|
||||
runtime: "goose",
|
||||
model: null,
|
||||
provider: null,
|
||||
isBuiltIn: false,
|
||||
isActive: true,
|
||||
namePool: [],
|
||||
envVars: {},
|
||||
respondTo: null,
|
||||
respondToAllowlist: [],
|
||||
parallelism: null,
|
||||
createdAt: "2025-01-01T00:00:00Z",
|
||||
updatedAt: "2025-01-01T00:00:00Z",
|
||||
};
|
||||
|
||||
const minimalInstance = {
|
||||
pubkey: "pk-0000",
|
||||
name: "Bot",
|
||||
avatarUrl: "",
|
||||
systemPrompt: "Do stuff.",
|
||||
model: null,
|
||||
provider: null,
|
||||
envVars: {},
|
||||
respondTo: null,
|
||||
respondToAllowlist: [],
|
||||
parallelism: null,
|
||||
autoRestartOnConfigChange: false,
|
||||
startOnAppLaunch: false,
|
||||
};
|
||||
|
||||
test("AgentEditDialog: instance-with-definition routes instance to AgentInstanceEditDialog", () => {
|
||||
const ctx = {
|
||||
kind: "instance-with-definition",
|
||||
definition: minimalDefinition,
|
||||
instance: minimalInstance,
|
||||
};
|
||||
|
||||
const element = AgentEditDialog({
|
||||
ctx,
|
||||
open: true,
|
||||
onOpenChange: noop,
|
||||
});
|
||||
|
||||
// Wrapped in AgentRunLocationProvider for respondTo warning.
|
||||
assert.equal(element.type, AgentRunLocationProvider);
|
||||
const form = element.props.children;
|
||||
assert.equal(
|
||||
form.type,
|
||||
AgentInstanceEditDialog,
|
||||
"linked agent edit must reach AgentInstanceEditDialog (all I/L fields)",
|
||||
);
|
||||
assert.equal(form.props.agent, minimalInstance);
|
||||
assert.equal(form.props.open, true);
|
||||
assert.equal(
|
||||
form.props.onEditLinkedPersona,
|
||||
undefined,
|
||||
"R4 back-door must be deleted",
|
||||
);
|
||||
});
|
||||
|
||||
test("AgentEditDialog: instance-only routes to AgentInstanceEditDialog (unlinked save)", () => {
|
||||
const ctx = { kind: "instance-only", instance: minimalInstance };
|
||||
|
||||
const element = AgentEditDialog({
|
||||
ctx,
|
||||
open: true,
|
||||
onOpenChange: noop,
|
||||
});
|
||||
|
||||
// Wrapped in AgentRunLocationProvider for respondTo warning.
|
||||
assert.equal(element.type, AgentRunLocationProvider);
|
||||
const form = element.props.children;
|
||||
assert.equal(
|
||||
form.type,
|
||||
AgentInstanceEditDialog,
|
||||
"unlinked agent edit must reach AgentInstanceEditDialog (UpdateManagedAgentInput path)",
|
||||
);
|
||||
assert.equal(form.props.agent, minimalInstance);
|
||||
});
|
||||
|
||||
test("AgentEditDialog: definition-only routes to definition sub-component", () => {
|
||||
const ctx = { kind: "definition-only", definition: minimalDefinition };
|
||||
|
||||
const element = AgentEditDialog({
|
||||
ctx,
|
||||
open: true,
|
||||
onOpenChange: noop,
|
||||
});
|
||||
|
||||
// Hook-bearing sub-component: confirm by name rather than reference.
|
||||
assert.equal(
|
||||
typeof element.type,
|
||||
"function",
|
||||
"definition-only must route to a React component",
|
||||
);
|
||||
assert.equal(
|
||||
element.type.name,
|
||||
"AgentEditDefinitionOnlyDialog",
|
||||
"definition-only must use the coordinator-wired sub-component",
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user