Add office agent management wizard (#56)
* Add agents * Agent * Added agents management * Polish agent wizard and release blockers. Finalize the office agent management flow by aligning the gateway fallback behavior, cleaning up UI semantics, and updating tests so the branch is ready to ship. Made-with: Cursor --------- Co-authored-by: iamlukethedev <iamlukethedev@users.noreply.github.com> Co-authored-by: iamlukethedev <lucas.guilherme@smartwayslfl.com>
This commit is contained in:
@@ -25,7 +25,10 @@ import {
|
||||
type StudioSettingsLoadOptions,
|
||||
} from "@/lib/studio/coordinator";
|
||||
import { resolveDeskAssignments } from "@/lib/studio/settings";
|
||||
import { renameGatewayAgent } from "@/lib/gateway/agentConfig";
|
||||
import {
|
||||
createGatewayAgent,
|
||||
renameGatewayAgent,
|
||||
} from "@/lib/gateway/agentConfig";
|
||||
import {
|
||||
runStudioBootstrapLoadOperation,
|
||||
executeStudioBootstrapLoadCommands,
|
||||
@@ -53,11 +56,26 @@ import {
|
||||
AgentEditorModal,
|
||||
type AgentEditorSection,
|
||||
} from "@/features/agents/components/AgentEditorModal";
|
||||
import { AgentCreateWizardModal } from "@/features/agents/components/AgentCreateWizardModal";
|
||||
import type { AgentIdentityValues } from "@/features/agents/components/AgentIdentityFields";
|
||||
import { useChatInteractionController } from "@/features/agents/operations/useChatInteractionController";
|
||||
import {
|
||||
applyCreateAgentBootstrapPermissions,
|
||||
CREATE_AGENT_DEFAULT_PERMISSIONS,
|
||||
} from "@/features/agents/operations/createAgentBootstrapOperation";
|
||||
import { deleteAgentRecordViaStudio } from "@/features/agents/operations/deleteAgentOperation";
|
||||
import { planAgentSettingsMutation } from "@/features/agents/operations/agentSettingsMutationWorkflow";
|
||||
import {
|
||||
executeHistorySyncCommands,
|
||||
runHistorySyncOperation,
|
||||
} from "@/features/agents/operations/historySyncOperation";
|
||||
import {
|
||||
buildQueuedMutationBlock,
|
||||
runAgentConfigMutationLifecycle,
|
||||
runCreateAgentMutationLifecycle,
|
||||
type CreateAgentBlockState,
|
||||
} from "@/features/agents/operations/mutationLifecycleWorkflow";
|
||||
import { useConfigMutationQueue } from "@/features/agents/operations/useConfigMutationQueue";
|
||||
import {
|
||||
RUNTIME_SYNC_DEFAULT_HISTORY_LIMIT,
|
||||
RUNTIME_SYNC_MAX_HISTORY_LIMIT,
|
||||
@@ -72,6 +90,12 @@ import {
|
||||
} from "@/lib/gateway/models";
|
||||
import type { GatewayModelPolicySnapshot } from "@/lib/gateway/models";
|
||||
import type { AgentAvatarProfile } from "@/lib/avatars/profile";
|
||||
import {
|
||||
createEmptyPersonalityDraft,
|
||||
serializePersonalityFiles,
|
||||
type PersonalityBuilderDraft,
|
||||
} from "@/lib/agents/personalityBuilder";
|
||||
import { writeGatewayAgentFiles } from "@/lib/gateway/agentFiles";
|
||||
import { randomUUID } from "@/lib/uuid";
|
||||
import {
|
||||
HQSidebar,
|
||||
@@ -167,6 +191,15 @@ type PreparedTextMessageEntry = {
|
||||
scenario: MockTextMessageScenario;
|
||||
};
|
||||
|
||||
type OfficeDeleteMutationBlockState = {
|
||||
kind: "delete-agent";
|
||||
agentId: string;
|
||||
agentName: string;
|
||||
phase: "queued" | "mutating" | "awaiting-restart";
|
||||
startedAt: number;
|
||||
sawDisconnect: boolean;
|
||||
};
|
||||
|
||||
type PhoneCallSpeakPayload = {
|
||||
agentId: string;
|
||||
requestKey: string;
|
||||
@@ -214,6 +247,31 @@ const formatOpenClawValue = (value: string | null | undefined) => {
|
||||
const buildPhoneCallOutputLine = (text: string) => `[phone booth] ${text}`;
|
||||
const buildTextMessageOutputLine = (text: string) => `[messaging booth] ${text}`;
|
||||
|
||||
const buildIdentityFileDraft = (identity: AgentIdentityValues) => {
|
||||
const draft = createEmptyPersonalityDraft();
|
||||
draft.identity = {
|
||||
...draft.identity,
|
||||
...identity,
|
||||
};
|
||||
return serializePersonalityFiles(draft);
|
||||
};
|
||||
|
||||
const resolveOfficeMutationGuardMessage = (guardReason?: string) => {
|
||||
if (guardReason === "not-connected") {
|
||||
return "Connect to the gateway before changing the office fleet.";
|
||||
}
|
||||
if (guardReason === "create-block-active") {
|
||||
return "Finish the active agent creation before starting another fleet change.";
|
||||
}
|
||||
if (guardReason === "rename-block-active") {
|
||||
return "Finish the active rename before changing the office fleet.";
|
||||
}
|
||||
if (guardReason === "delete-block-active") {
|
||||
return "Finish the active deletion before changing the office fleet.";
|
||||
}
|
||||
return "The office fleet is busy right now.";
|
||||
};
|
||||
|
||||
const PHONE_BOOTH_ASSISTANT_FALLBACK_RE =
|
||||
/\b(?:i\s+)?can(?:not|['’]t)\s+(?:place|make)\s+(?:phone\s+)?calls?\b/i;
|
||||
|
||||
@@ -745,6 +803,16 @@ export function OfficeScreen({
|
||||
const [agentEditorAgentId, setAgentEditorAgentId] = useState<string | null>(null);
|
||||
const [agentEditorInitialSection, setAgentEditorInitialSection] =
|
||||
useState<AgentEditorSection>("avatar");
|
||||
const [createAgentWizardNonce, setCreateAgentWizardNonce] = useState(0);
|
||||
const [createAgentWizardOpen, setCreateAgentWizardOpen] = useState(false);
|
||||
const [createAgentBusy, setCreateAgentBusy] = useState(false);
|
||||
const [createAgentModalError, setCreateAgentModalError] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const [createAgentBlock, setCreateAgentBlock] =
|
||||
useState<CreateAgentBlockState | null>(null);
|
||||
const [deleteAgentBlock, setDeleteAgentBlock] =
|
||||
useState<OfficeDeleteMutationBlockState | null>(null);
|
||||
const [preparedPhoneCallsByAgentId, setPreparedPhoneCallsByAgentId] = useState<
|
||||
Record<string, PreparedPhoneCallEntry>
|
||||
>({});
|
||||
@@ -900,6 +968,22 @@ export function OfficeScreen({
|
||||
stateRef.current = state;
|
||||
}, [state]);
|
||||
|
||||
const hasRunningAgents = useMemo(
|
||||
() =>
|
||||
state.agents.some(
|
||||
(agent) => agent.status === "running" || Boolean(agent.runId),
|
||||
),
|
||||
[state.agents],
|
||||
);
|
||||
const hasDeleteMutationBlock = deleteAgentBlock?.kind === "delete-agent";
|
||||
const { enqueueConfigMutation } = useConfigMutationQueue({
|
||||
status,
|
||||
hasRunningAgents,
|
||||
hasRestartBlockInProgress: Boolean(
|
||||
deleteAgentBlock && deleteAgentBlock.phase !== "queued",
|
||||
),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
officeTriggerStateRef.current = officeTriggerState;
|
||||
}, [officeTriggerState]);
|
||||
@@ -1111,6 +1195,335 @@ export function OfficeScreen({
|
||||
status,
|
||||
]);
|
||||
|
||||
const handleCloseCreateAgentWizard = useCallback(
|
||||
(createdAgentId: string | null) => {
|
||||
setCreateAgentWizardOpen(false);
|
||||
setCreateAgentModalError(null);
|
||||
if (createdAgentId) {
|
||||
openAgentEditor(createdAgentId, "IDENTITY.md");
|
||||
}
|
||||
},
|
||||
[openAgentEditor],
|
||||
);
|
||||
const handleOpenCreateAgentWizard = useCallback(() => {
|
||||
setCreateAgentModalError(null);
|
||||
setCreateAgentWizardNonce((current) => current + 1);
|
||||
setCreateAgentWizardOpen(true);
|
||||
}, []);
|
||||
const clearDeletedAgentUiState = useCallback((agentId: string) => {
|
||||
setSelectedChatAgentId((current) => (current === agentId ? null : current));
|
||||
setAgentEditorAgentId((current) => (current === agentId ? null : current));
|
||||
setMonitorAgentId((current) => (current === agentId ? null : current));
|
||||
setGithubReviewAgentId((current) => (current === agentId ? null : current));
|
||||
setQaTestingAgentId((current) => (current === agentId ? null : current));
|
||||
setPreparedPhoneCallsByAgentId((current) => {
|
||||
if (!(agentId in current)) return current;
|
||||
const next = { ...current };
|
||||
delete next[agentId];
|
||||
return next;
|
||||
});
|
||||
setPreparedTextMessagesByAgentId((current) => {
|
||||
if (!(agentId in current)) return current;
|
||||
const next = { ...current };
|
||||
delete next[agentId];
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
const createAgentStatusLine = useMemo(() => {
|
||||
if (!createAgentBlock) return null;
|
||||
if (createAgentBlock.phase === "queued") {
|
||||
return "Waiting for active runs to finish before creating the new agent.";
|
||||
}
|
||||
return `Creating ${createAgentBlock.agentName}.`;
|
||||
}, [createAgentBlock]);
|
||||
const deleteAgentStatusLine = useMemo(() => {
|
||||
if (!deleteAgentBlock) return null;
|
||||
if (deleteAgentBlock.phase === "queued") {
|
||||
return `Waiting for active runs to finish before deleting ${deleteAgentBlock.agentName}.`;
|
||||
}
|
||||
return `Deleting ${deleteAgentBlock.agentName}.`;
|
||||
}, [deleteAgentBlock]);
|
||||
const handleCreateAgentFromIdentity = useCallback(
|
||||
async (identity: AgentIdentityValues) => {
|
||||
let createdAgentId: string | null = null;
|
||||
const success = await runCreateAgentMutationLifecycle(
|
||||
{
|
||||
payload: {
|
||||
name: identity.name,
|
||||
},
|
||||
status,
|
||||
hasCreateBlock: Boolean(createAgentBlock),
|
||||
hasRenameBlock: false,
|
||||
hasDeleteBlock: Boolean(hasDeleteMutationBlock),
|
||||
createAgentBusy,
|
||||
},
|
||||
{
|
||||
enqueueConfigMutation,
|
||||
createAgent: async (name) => {
|
||||
const created = await createGatewayAgent({ client, name });
|
||||
const files = buildIdentityFileDraft(identity);
|
||||
await writeGatewayAgentFiles({
|
||||
client,
|
||||
agentId: created.id,
|
||||
files: {
|
||||
"IDENTITY.md": files["IDENTITY.md"],
|
||||
},
|
||||
});
|
||||
return { id: created.id };
|
||||
},
|
||||
setQueuedBlock: ({ agentName, startedAt }) => {
|
||||
const queuedCreateBlock = buildQueuedMutationBlock({
|
||||
kind: "create-agent",
|
||||
agentId: "",
|
||||
agentName,
|
||||
startedAt,
|
||||
});
|
||||
setCreateAgentBlock({
|
||||
agentName: queuedCreateBlock.agentName,
|
||||
phase: "queued",
|
||||
startedAt: queuedCreateBlock.startedAt,
|
||||
});
|
||||
},
|
||||
setCreatingBlock: (agentName) => {
|
||||
setCreateAgentBlock((current) => {
|
||||
if (!current || current.agentName !== agentName) return current;
|
||||
return { ...current, phase: "creating" };
|
||||
});
|
||||
},
|
||||
onCompletion: async (completion) => {
|
||||
createdAgentId = completion.agentId;
|
||||
await loadAgents({ forceSettings: true });
|
||||
const createdAgent =
|
||||
stateRef.current.agents.find(
|
||||
(entry) => entry.agentId === completion.agentId,
|
||||
) ?? null;
|
||||
if (createdAgent?.sessionKey) {
|
||||
try {
|
||||
await applyCreateAgentBootstrapPermissions({
|
||||
client,
|
||||
agentId: createdAgent.agentId,
|
||||
sessionKey: createdAgent.sessionKey,
|
||||
draft: { ...CREATE_AGENT_DEFAULT_PERMISSIONS },
|
||||
loadAgents: () => loadAgents({ forceSettings: true }),
|
||||
});
|
||||
} catch (error) {
|
||||
const message =
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Failed to apply default permissions.";
|
||||
setError(
|
||||
`Agent created, but default permissions could not be applied: ${message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
dispatch({
|
||||
type: "selectAgent",
|
||||
agentId: completion.agentId,
|
||||
});
|
||||
setSelectedChatAgentId(completion.agentId);
|
||||
setCreateAgentBlock(null);
|
||||
setCreateAgentModalError(null);
|
||||
},
|
||||
setCreateAgentModalError,
|
||||
setCreateAgentBusy,
|
||||
clearCreateBlock: () => {
|
||||
setCreateAgentBlock(null);
|
||||
},
|
||||
onError: setError,
|
||||
},
|
||||
);
|
||||
return success ? createdAgentId : null;
|
||||
},
|
||||
[
|
||||
client,
|
||||
createAgentBlock,
|
||||
createAgentBusy,
|
||||
dispatch,
|
||||
enqueueConfigMutation,
|
||||
hasDeleteMutationBlock,
|
||||
loadAgents,
|
||||
setError,
|
||||
status,
|
||||
],
|
||||
);
|
||||
const handleFinishCreateAgentAvatar = useCallback(
|
||||
async (params: {
|
||||
agentId: string;
|
||||
draft: PersonalityBuilderDraft;
|
||||
profile: AgentAvatarProfile;
|
||||
}) => {
|
||||
setCreateAgentBusy(true);
|
||||
setCreateAgentModalError(null);
|
||||
try {
|
||||
const files = serializePersonalityFiles(params.draft);
|
||||
await writeGatewayAgentFiles({
|
||||
client,
|
||||
agentId: params.agentId,
|
||||
files,
|
||||
});
|
||||
const currentAgent =
|
||||
stateRef.current.agents.find((entry) => entry.agentId === params.agentId) ?? null;
|
||||
const nextName = params.draft.identity.name.trim();
|
||||
const currentName = currentAgent?.name.trim() ?? "";
|
||||
if (nextName && nextName !== currentName) {
|
||||
const renamed = await renameGatewayAgent({
|
||||
client,
|
||||
agentId: params.agentId,
|
||||
name: nextName,
|
||||
});
|
||||
if (!renamed) {
|
||||
throw new Error("Saved the wizard files, but could not rename the live agent.");
|
||||
}
|
||||
}
|
||||
handleAvatarProfileSave(params.agentId, params.profile);
|
||||
await loadAgents({ forceSettings: true });
|
||||
setCreateAgentWizardOpen(false);
|
||||
setCreateAgentModalError(null);
|
||||
openAgentEditor(params.agentId, "IDENTITY.md");
|
||||
} catch (error) {
|
||||
const message =
|
||||
error instanceof Error ? error.message : "Failed to finish creating the agent.";
|
||||
setCreateAgentModalError(message);
|
||||
} finally {
|
||||
setCreateAgentBusy(false);
|
||||
}
|
||||
},
|
||||
[client, handleAvatarProfileSave, loadAgents, openAgentEditor],
|
||||
);
|
||||
const handleDeleteAgent = useCallback(
|
||||
async (agentId: string) => {
|
||||
const decision = planAgentSettingsMutation(
|
||||
{ kind: "delete-agent", agentId },
|
||||
{
|
||||
status,
|
||||
hasCreateBlock: Boolean(createAgentBlock),
|
||||
hasRenameBlock: false,
|
||||
hasDeleteBlock: Boolean(hasDeleteMutationBlock),
|
||||
cronCreateBusy: false,
|
||||
cronRunBusyJobId: null,
|
||||
cronDeleteBusyJobId: null,
|
||||
},
|
||||
);
|
||||
if (decision.kind === "deny") {
|
||||
setError(
|
||||
decision.message ?? resolveOfficeMutationGuardMessage(decision.guardReason),
|
||||
);
|
||||
return;
|
||||
}
|
||||
const agent = state.agents.find(
|
||||
(entry) => entry.agentId === decision.normalizedAgentId,
|
||||
);
|
||||
if (!agent) return;
|
||||
const confirmed = window.confirm(
|
||||
`Delete ${agent.name}? This removes the agent record from OpenClaw and clears its scheduled automations. Claw3D will not touch workspace files.`,
|
||||
);
|
||||
if (!confirmed) return;
|
||||
|
||||
await runAgentConfigMutationLifecycle({
|
||||
kind: "delete-agent",
|
||||
label: `Delete ${agent.name}`,
|
||||
isLocalGateway: false,
|
||||
deps: {
|
||||
enqueueConfigMutation,
|
||||
setQueuedBlock: () => {
|
||||
const queuedBlock = buildQueuedMutationBlock({
|
||||
kind: "delete-agent",
|
||||
agentId: decision.normalizedAgentId,
|
||||
agentName: agent.name,
|
||||
startedAt: Date.now(),
|
||||
});
|
||||
setDeleteAgentBlock({
|
||||
kind: "delete-agent",
|
||||
agentId: queuedBlock.agentId,
|
||||
agentName: queuedBlock.agentName,
|
||||
phase: queuedBlock.phase,
|
||||
startedAt: queuedBlock.startedAt,
|
||||
sawDisconnect: queuedBlock.sawDisconnect,
|
||||
});
|
||||
},
|
||||
setMutatingBlock: () => {
|
||||
setDeleteAgentBlock((current) => {
|
||||
if (!current || current.agentId !== decision.normalizedAgentId) {
|
||||
return current;
|
||||
}
|
||||
return {
|
||||
...current,
|
||||
phase: "mutating",
|
||||
};
|
||||
});
|
||||
},
|
||||
patchBlockAwaitingRestart: (patch) => {
|
||||
setDeleteAgentBlock((current) => {
|
||||
if (!current || current.agentId !== decision.normalizedAgentId) {
|
||||
return current;
|
||||
}
|
||||
return {
|
||||
...current,
|
||||
...patch,
|
||||
};
|
||||
});
|
||||
},
|
||||
clearBlock: () => {
|
||||
setDeleteAgentBlock((current) => {
|
||||
if (!current || current.agentId !== decision.normalizedAgentId) {
|
||||
return current;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
},
|
||||
executeMutation: async () => {
|
||||
await deleteAgentRecordViaStudio({
|
||||
client,
|
||||
agentId: decision.normalizedAgentId,
|
||||
logError: (message, error) => console.error(message, error),
|
||||
});
|
||||
clearDeletedAgentUiState(decision.normalizedAgentId);
|
||||
dispatch({
|
||||
type: "removeAgent",
|
||||
agentId: decision.normalizedAgentId,
|
||||
});
|
||||
},
|
||||
shouldAwaitRemoteRestart: async () => false,
|
||||
reloadAgents: () => loadAgents({ forceSettings: true }),
|
||||
setMobilePaneChat: () => {},
|
||||
onError: setError,
|
||||
},
|
||||
});
|
||||
},
|
||||
[
|
||||
clearDeletedAgentUiState,
|
||||
client,
|
||||
createAgentBlock,
|
||||
dispatch,
|
||||
enqueueConfigMutation,
|
||||
hasDeleteMutationBlock,
|
||||
loadAgents,
|
||||
setError,
|
||||
state.agents,
|
||||
status,
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!createAgentBlock || createAgentBlock.phase === "queued") return;
|
||||
const maxWaitMs = 90_000;
|
||||
const elapsed = Date.now() - createAgentBlock.startedAt;
|
||||
const remaining = Math.max(0, maxWaitMs - elapsed);
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
setCreateAgentBlock((current) => {
|
||||
if (!current || current.phase === "queued") return current;
|
||||
return null;
|
||||
});
|
||||
setCreateAgentBusy(false);
|
||||
setCreateAgentWizardOpen(false);
|
||||
setError("Agent creation timed out.");
|
||||
void loadAgents({ forceSettings: true });
|
||||
}, remaining);
|
||||
return () => {
|
||||
window.clearTimeout(timeoutId);
|
||||
};
|
||||
}, [createAgentBlock, loadAgents, setError]);
|
||||
|
||||
const requestAgentHistoryRefresh = useCallback(
|
||||
async (params: {
|
||||
agentId: string;
|
||||
@@ -1341,6 +1754,11 @@ export function OfficeScreen({
|
||||
if (status === "disconnected") {
|
||||
connectionEpochRef.current += 1;
|
||||
setAgentsLoaded(false);
|
||||
setCreateAgentWizardOpen(false);
|
||||
setCreateAgentBusy(false);
|
||||
setCreateAgentModalError(null);
|
||||
setCreateAgentBlock(null);
|
||||
setDeleteAgentBlock(null);
|
||||
loadAgentsInFlightRef.current = null;
|
||||
gatewayConfigSnapshot.current = null;
|
||||
lastLoadAgentsStartedAtRef.current = 0;
|
||||
@@ -1665,6 +2083,19 @@ export function OfficeScreen({
|
||||
: null;
|
||||
const mainAgent =
|
||||
state.agents.find((agent) => agent.agentId === MAIN_AGENT_ID) ?? null;
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedChatAgentId) return;
|
||||
if (state.agents.some((agent) => agent.agentId === selectedChatAgentId)) return;
|
||||
setSelectedChatAgentId(null);
|
||||
}, [selectedChatAgentId, state.agents]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!agentEditorAgentId) return;
|
||||
if (state.agents.some((agent) => agent.agentId === agentEditorAgentId)) return;
|
||||
setAgentEditorAgentId(null);
|
||||
}, [agentEditorAgentId, state.agents]);
|
||||
|
||||
const runLog = useRunLog({ client, status, agents: state.agents });
|
||||
const standupAgentSnapshots = useMemo<StandupAgentSnapshot[]>(
|
||||
() =>
|
||||
@@ -2870,9 +3301,13 @@ export function OfficeScreen({
|
||||
dispatch({ type: "selectAgent", agentId });
|
||||
}
|
||||
}}
|
||||
onAddAgent={handleOpenCreateAgentWizard}
|
||||
onAgentEdit={(agentId) => {
|
||||
openAgentEditor(agentId, "avatar");
|
||||
}}
|
||||
onAgentDelete={(agentId) => {
|
||||
void handleDeleteAgent(agentId);
|
||||
}}
|
||||
onDeskAssignmentChange={handleDeskAssignmentChange}
|
||||
onDeskAssignmentsReset={handleDeskAssignmentsReset}
|
||||
onGithubReviewDismiss={() => {
|
||||
@@ -2899,20 +3334,42 @@ export function OfficeScreen({
|
||||
</p>
|
||||
<p className="mt-1 text-sm text-amber-50">{emptyFleetMessage}</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="ui-btn-secondary shrink-0 px-3 py-2 text-xs font-semibold tracking-[0.05em] text-foreground"
|
||||
onClick={() => {
|
||||
void loadAgents({ forceSettings: true });
|
||||
}}
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="ui-btn-secondary px-3 py-2 text-xs font-semibold tracking-[0.05em] text-foreground"
|
||||
onClick={() => {
|
||||
handleOpenCreateAgentWizard();
|
||||
}}
|
||||
>
|
||||
Add Agent
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ui-btn-secondary px-3 py-2 text-xs font-semibold tracking-[0.05em] text-foreground"
|
||||
onClick={() => {
|
||||
void loadAgents({ forceSettings: true });
|
||||
}}
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{deleteAgentStatusLine ? (
|
||||
<div className="pointer-events-none fixed left-1/2 top-5 z-40 -translate-x-1/2 px-4">
|
||||
<div className="pointer-events-auto rounded-lg border border-red-400/30 bg-black/85 px-4 py-3 shadow-2xl backdrop-blur">
|
||||
<div className="font-mono text-[10px] uppercase tracking-[0.16em] text-red-200/75">
|
||||
Fleet mutation
|
||||
</div>
|
||||
<div className="mt-1 text-sm text-red-50">{deleteAgentStatusLine}</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{!debugEnabled ? (
|
||||
<HQSidebar
|
||||
open={sidebarOpen}
|
||||
@@ -2921,6 +3378,7 @@ export function OfficeScreen({
|
||||
onToggle={() => setSidebarOpen((prev) => !prev)}
|
||||
onTabChange={setActiveSidebarTab}
|
||||
onOpenMarketplace={() => setMarketplaceOpen(true)}
|
||||
onAddAgent={handleOpenCreateAgentWizard}
|
||||
inboxPanel={
|
||||
<InboxPanel
|
||||
agents={state.agents}
|
||||
@@ -3444,6 +3902,7 @@ export function OfficeScreen({
|
||||
) : null}
|
||||
{agentEditorAgent ? (
|
||||
<AgentEditorModal
|
||||
key={`${agentEditorAgent.agentId}:${agentEditorInitialSection}`}
|
||||
open
|
||||
client={client}
|
||||
agents={state.agents}
|
||||
@@ -3463,8 +3922,25 @@ export function OfficeScreen({
|
||||
return false;
|
||||
}
|
||||
}}
|
||||
onDelete={async (agentId) => {
|
||||
await handleDeleteAgent(agentId);
|
||||
}}
|
||||
onNavigateAgent={(agentId, section) => {
|
||||
openAgentEditor(agentId, section);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<AgentCreateWizardModal
|
||||
key={createAgentWizardNonce}
|
||||
open={createAgentWizardOpen}
|
||||
suggestedName={`Agent ${state.agents.length + 1}`}
|
||||
busy={createAgentBusy}
|
||||
submitError={createAgentModalError}
|
||||
statusLine={createAgentStatusLine}
|
||||
onClose={handleCloseCreateAgentWizard}
|
||||
onCreateAgent={handleCreateAgentFromIdentity}
|
||||
onFinishWizard={handleFinishCreateAgentAvatar}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user