refactor(renderer): rename EntityWorkspace to TwoPaneLayout for clarity

This commit is contained in:
ZacharyZhang-NY committed 2026-04-16 23:33:55 -04:00
1 parent 933fcf27d0
commit 61aba41e77
8 files changed
+952 -7

No files matched your search

@@ -2,10 +2,10 @@ import type { ReactNode } from "react";
import { Eyebrow } from "./ui";
/**
* Two-slot workspace layout with a section header.
* Use `Panel` for single-slot sections; use this when rendering a primary + secondary pane.
* Two-pane layout with a section header.
* Use `Panel` (from ui/) for single-slot sections. Use this when rendering a primary + secondary pane.
*/
export function EntityWorkspace({
export function TwoPaneLayout({
title,
eyebrow,
primary,
@@ -0,0 +1,111 @@
import { useMemo } from "react";
import type {
AgentRecord,
ConnectorRecord,
WorkspaceRecord,
} from "@shared/types";
import {
getConnectorExecutionReadinessIssue,
isConnectorExecutionReady,
} from "@shared/connector-policy";
import { useAgentForm, type AgentFormMode } from "./useAgentForm";
import { CreateFormBody } from "./agent-form-fields/CreateFormBody";
import { EditFormBody } from "./agent-form-fields/EditFormBody";
interface AgentFormProps {
mode: AgentFormMode;
companyId: string;
connectors: ConnectorRecord[];
workspaces: WorkspaceRecord[];
agents: AgentRecord[];
agent?: AgentRecord;
onSaved: () => Promise<void>;
onDeleted?: () => Promise<void>;
}
export function AgentForm({
mode,
companyId,
connectors,
workspaces,
agents,
agent,
onSaved,
onDeleted,
}: AgentFormProps) {
const form = useAgentForm({
mode,
agent: agent ?? null,
connectors,
workspaces,
companyId,
});
const { executionReadyConnectors, selectedConnectorRecord } = form;
const selectedConnectorIssue =
mode === "edit"
? selectedConnectorRecord
? getConnectorExecutionReadinessIssue(selectedConnectorRecord)
: "Select a connector."
: null;
const persistedConnectorRecord =
mode === "edit" && agent
? connectors.find((connector) => connector.id === agent.connectorId) ?? null
: null;
const persistedConnectorIssue =
mode === "edit"
? persistedConnectorRecord
? getConnectorExecutionReadinessIssue(persistedConnectorRecord)
: "Current connector is unavailable."
: null;
const connectorOptions = useMemo(() => {
const getDisplayLabel = (c: ConnectorRecord) =>
c.id.startsWith("claude_compat") && c.notes ? c.notes : c.label;
const readyOptions = executionReadyConnectors.map((connector) => ({
value: connector.id,
label: getDisplayLabel(connector),
}));
if (mode !== "edit") return readyOptions;
if (!selectedConnectorRecord || isConnectorExecutionReady(selectedConnectorRecord)) {
return readyOptions;
}
return [
{
value: selectedConnectorRecord.id,
label: `${getDisplayLabel(selectedConnectorRecord)} (current, blocked)`,
},
...readyOptions.filter((option) => option.value !== selectedConnectorRecord.id),
];
}, [executionReadyConnectors, selectedConnectorRecord, mode]);
if (mode === "create") {
return (
<CreateFormBody
companyId={companyId}
agents={agents}
workspaces={workspaces}
connectorOptions={connectorOptions}
form={form}
onSaved={onSaved}
/>
);
}
if (!agent) return null;
return (
<EditFormBody
agent={agent}
companyId={companyId}
agents={agents}
workspaces={workspaces}
connectors={connectors}
connectorOptions={connectorOptions}
selectedConnectorIssue={selectedConnectorIssue}
persistedConnectorIssue={persistedConnectorIssue}
form={form}
onSaved={onSaved}
onDeleted={onDeleted}
/>
);
}
@@ -0,0 +1,254 @@
import { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { Robot } from "@phosphor-icons/react";
import { DEPARTMENT_OPTIONS } from "@shared/constants";
import type {
AgentRecord,
ConnectorRecord,
DepartmentId,
WorkspaceRecord,
} from "@shared/types";
import { unwrap } from "../../../../lib/desktop";
import { useT } from "../../../../i18n";
import { ActionButton, Input, Select, TextArea } from "../../../ui";
import type { AgentFormState } from "../useAgentForm";
interface CreateFormBodyProps {
companyId: string;
agents: AgentRecord[];
workspaces: WorkspaceRecord[];
connectorOptions: Array<{ value: string; label: string }>;
form: AgentFormState;
onSaved: () => Promise<void>;
}
export function CreateFormBody({
companyId,
agents,
workspaces,
connectorOptions,
form,
onSaved,
}: CreateFormBodyProps) {
const t = useT();
const {
name,
setName,
role,
setRole,
title,
setTitle,
department,
setDepartment,
connectorId,
setConnectorId,
model,
setModel,
availableModels,
workspaceId,
setWorkspaceId,
budget,
setBudget,
capabilities,
setCapabilities,
reportsTo,
setReportsTo,
thinkingEffort,
setThinkingEffort,
supportsEffort,
executionReadyConnectors,
selectedConnectorRecord,
selectedDepartment,
} = form;
const [showAdvanced, setShowAdvanced] = useState(false);
const [requestError, setRequestError] = useState<string | null>(null);
async function handleCreate() {
try {
setRequestError(null);
if (!selectedConnectorRecord) {
setRequestError(t("org.selectConnectorFirst"));
return;
}
const meta: Record<string, string> = {};
if (thinkingEffort) meta.thinkingEffort = thinkingEffort;
unwrap(
await window.agentCompany.requestHire({
companyId,
requestedByAgentId: null,
name: name.trim(),
role: role.trim(),
title: title.trim(),
department: selectedDepartment,
connectorId: connectorId as ConnectorRecord["id"],
workspaceId: workspaceId || null,
reportsTo: reportsTo || null,
model: model || null,
metadataJson: JSON.stringify(meta),
capabilities: capabilities.trim(),
budgetMonthlyUsd: Number(budget || 0),
}),
);
setName("");
setRole("");
setTitle("");
setDepartment("");
setCapabilities("");
setReportsTo("");
setRequestError(null);
await onSaved();
} catch (error) {
setRequestError(
error instanceof Error ? error.message : t("org.hireRequestFailed"),
);
}
}
return (
<div className="grid min-w-0 gap-4">
<Input
label={t("org.agentName")}
value={name}
onChange={setName}
placeholder={t("org.agentName")}
hint="Display name shown in org chart and messages"
/>
<Input
label={t("agent.role")}
value={role}
onChange={setRole}
placeholder="e.g. Engineer, PM, CEO"
hint="Determines which tasks the agent can handle"
/>
<Input
label={t("agent.title")}
value={title}
onChange={setTitle}
placeholder={t("agent.title")}
hint="Job title shown under the name"
/>
<Select
label={t("org.connector")}
value={connectorId}
onChange={(value) => setConnectorId(value as ConnectorRecord["id"])}
options={connectorOptions}
/>
{executionReadyConnectors.length === 0 ? (
<p className="text-[12px] text-warning">{t("org.noConnectorAvailable")}</p>
) : null}
{availableModels.length > 0 ? (
<Select
label={t("org.model")}
value={model}
onChange={setModel}
options={availableModels.map((m) => ({ value: m.id, label: m.label }))}
/>
) : null}
{supportsEffort ? (
<Select
label={t("org.thinkingEffort")}
value={thinkingEffort}
onChange={setThinkingEffort}
options={[
{ value: "", label: t("org.default") },
{ value: "low", label: "Low" },
{ value: "medium", label: "Medium" },
{ value: "high", label: "High" },
]}
/>
) : null}
<button
type="button"
onClick={() => setShowAdvanced(!showAdvanced)}
className="flex items-center gap-1.5 text-[12px] font-medium text-muted-foreground transition hover:text-foreground"
>
<svg
width="10"
height="10"
viewBox="0 0 10 10"
className={`transition-transform ${showAdvanced ? "rotate-90" : ""}`}
>
<path
d="M3 1l4 4-4 4"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
fill="none"
/>
</svg>
{t("org.advancedSettings")}
</button>
<AnimatePresence>
{showAdvanced ? (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.25, ease: [0.16, 1, 0.3, 1] }}
className="grid gap-4 overflow-hidden"
>
<Select
label={t("comm.department")}
value={department}
onChange={(value) => setDepartment(value as DepartmentId | "")}
options={DEPARTMENT_OPTIONS.map((option) => ({
value: option.value,
label:
option.value === "" ? t("comm.selectDepartment") : option.label,
}))}
/>
<Select
label={t("agent.workspace")}
value={workspaceId}
onChange={setWorkspaceId}
options={[
{ value: "", label: t("agent.noWorkspace") },
...workspaces.map((workspace) => ({
value: workspace.id,
label: workspace.name,
})),
]}
/>
<Select
label={t("agent.reportsTo")}
value={reportsTo}
onChange={setReportsTo}
options={[
{ value: "", label: t("agent.noManager") },
...agents.map((a) => ({ value: a.id, label: a.name })),
]}
/>
<Input
label={t("org.monthlyBudget")}
value={budget}
onChange={setBudget}
placeholder="120"
type="number"
hint="Spending cap per month; agent pauses when exceeded"
/>
<TextArea
label={t("agent.capabilities")}
value={capabilities}
onChange={setCapabilities}
placeholder="What this agent does"
hint="Skills and domain knowledge injected into the agent's system prompt"
/>
</motion.div>
) : null}
</AnimatePresence>
<p className="text-[12px] text-muted-foreground">{t("org.hireRequestNote")}</p>
<ActionButton
label={t("org.submitHireRequest")}
tone="accent"
onClick={() => void handleCreate()}
icon={Robot}
disabled={!selectedConnectorRecord}
/>
{requestError ? (
<p className="text-[12px] text-destructive">{requestError}</p>
) : null}
</div>
);
}
@@ -0,0 +1,351 @@
import { useEffect, useState } from "react";
import { Pencil, Trash } from "@phosphor-icons/react";
import { DEPARTMENT_OPTIONS } from "@shared/constants";
import type {
AgentRecord,
ConnectorRecord,
DepartmentId,
WorkspaceRecord,
} from "@shared/types";
import { unwrap } from "../../../../lib/desktop";
import { buildBudgetPolicyInput } from "../../../../lib/budget-policy-input";
import { useAppStore } from "../../../../state/store";
import { useT } from "../../../../i18n";
import { ActionButton, Input, Select, TextArea } from "../../../ui";
import { SkillAgentToggles } from "../../../skills/SkillAgentToggles";
import { ConfirmDelete, HeartbeatSection } from "../../helpers";
import type { AgentFormState } from "../useAgentForm";
interface EditFormBodyProps {
agent: AgentRecord;
companyId: string;
agents: AgentRecord[];
workspaces: WorkspaceRecord[];
connectors: ConnectorRecord[];
connectorOptions: Array<{ value: string; label: string }>;
selectedConnectorIssue: string | null;
persistedConnectorIssue: string | null;
form: AgentFormState;
onSaved: () => Promise<void>;
onDeleted?: () => Promise<void>;
}
export function EditFormBody({
agent,
companyId,
agents,
workspaces,
connectorOptions,
selectedConnectorIssue,
persistedConnectorIssue,
form,
onSaved,
onDeleted,
}: EditFormBodyProps) {
const t = useT();
const {
name,
setName,
role,
setRole,
title,
setTitle,
department,
setDepartment,
status,
setStatus,
connectorId,
setConnectorId,
model,
setModel,
availableModels,
workspaceId,
setWorkspaceId,
budget,
setBudget,
capabilities,
setCapabilities,
reportsTo,
setReportsTo,
thinkingEffort,
setThinkingEffort,
existingMeta,
supportsEffort,
selectedDepartment,
agentPolicy,
} = form;
const [saveError, setSaveError] = useState<string | null>(null);
const [confirmingDelete, setConfirmingDelete] = useState(false);
const pendingApproval = agent.status === "pending_approval";
const skills = useAppStore((s) => s.skills);
const setSkills = useAppStore((s) => s.setSkills);
useEffect(() => {
if (skills.length === 0 && companyId) {
window.agentCompany.listSkills(companyId).then((result) => {
if (result.ok) setSkills(result.data);
});
}
}, [companyId, skills.length, setSkills]);
const managerCandidates = agents.filter((a) => a.id !== agent.id);
async function persistAgentDraft() {
const meta = { ...existingMeta };
if (thinkingEffort) meta.thinkingEffort = thinkingEffort;
else delete meta.thinkingEffort;
const budgetDollars = Number(budget || 0);
unwrap(
await window.agentCompany.saveAgent({
id: agent.id,
companyId,
name: name.trim(),
role: role.trim(),
title: title.trim(),
department: selectedDepartment,
status,
connectorId,
workspaceId: workspaceId || null,
reportsTo: reportsTo || null,
model: model || null,
metadataJson: JSON.stringify(meta),
budgetMonthlyUsd: budgetDollars,
capabilities: capabilities.trim(),
}),
);
if (budgetDollars > 0 && agent.id) {
const payload = buildBudgetPolicyInput({
policyId: agentPolicy?.id ?? null,
companyId,
scopeType: "agent",
scopeId: agent.id,
amountDollars: String(budgetDollars),
warnPercent: String(agentPolicy?.warnPercent ?? 80),
hardStopEnabled: agentPolicy?.hardStopEnabled ?? true,
notifyEnabled: agentPolicy?.notifyEnabled ?? true,
});
if (!payload) {
throw new Error("Agent budget policy is invalid.");
}
unwrap(await window.agentCompany.saveBudgetPolicy(payload));
}
}
async function handleWakeNow() {
setSaveError(null);
try {
await persistAgentDraft();
unwrap(
await window.agentCompany.triggerHeartbeat({
agentId: agent.id,
companyId,
trigger: "manual",
}),
);
await onSaved();
} catch (error) {
setSaveError(error instanceof Error ? error.message : "Failed to wake agent.");
}
}
async function handleSave() {
setSaveError(null);
try {
await persistAgentDraft();
await onSaved();
} catch (error) {
setSaveError(error instanceof Error ? error.message : "Failed to save agent.");
}
}
return (
<div className="grid min-w-0 gap-4">
<Input
label={t("agent.name")}
value={name}
onChange={setName}
placeholder={t("agent.name")}
readOnly={pendingApproval}
disabled={pendingApproval}
/>
<Input
label={t("agent.role")}
value={role}
onChange={setRole}
placeholder={t("agent.role")}
readOnly={pendingApproval}
disabled={pendingApproval}
/>
<Input
label={t("agent.title")}
value={title}
onChange={setTitle}
placeholder={t("agent.title")}
readOnly={pendingApproval}
disabled={pendingApproval}
/>
<Select
label={t("comm.department")}
value={department}
onChange={(value) => setDepartment(value as DepartmentId | "")}
disabled={pendingApproval}
options={DEPARTMENT_OPTIONS.map((option) => ({
value: option.value,
label: option.value === "" ? t("comm.selectDepartment") : option.label,
}))}
/>
<Select
label={t("agent.status")}
value={status}
onChange={(v) => setStatus(v as AgentRecord["status"])}
disabled={pendingApproval}
options={
pendingApproval
? [{ value: "pending_approval", label: t("agent.pendingApproval") }]
: [
{ value: "idle", label: "Idle" },
{ value: "active", label: "Active" },
{ value: "running", label: "Running" },
{ value: "paused", label: "Paused" },
{ value: "error", label: "Error" },
{ value: "terminated", label: "Terminated" },
]
}
/>
{pendingApproval ? (
<p className="text-[12px] text-muted-foreground">{t("agent.pendingHireNote")}</p>
) : null}
<Select
label={t("agent.connector")}
value={connectorId}
onChange={(v) => setConnectorId(v as ConnectorRecord["id"])}
disabled={pendingApproval}
options={connectorOptions}
/>
{selectedConnectorIssue ? (
<p className="text-[12px] text-warning">{selectedConnectorIssue}</p>
) : null}
{availableModels.length > 0 ? (
<Select
label={t("agent.model")}
value={model}
onChange={setModel}
disabled={pendingApproval}
options={availableModels.map((m) => ({ value: m.id, label: m.label }))}
/>
) : null}
{supportsEffort ? (
<Select
label={t("agent.thinkingEffort")}
value={thinkingEffort}
onChange={setThinkingEffort}
disabled={pendingApproval}
options={[
{ value: "", label: "Default" },
{ value: "low", label: "Low" },
{ value: "medium", label: "Medium" },
{ value: "high", label: "High" },
]}
/>
) : null}
<Select
label={t("agent.workspace")}
value={workspaceId}
onChange={setWorkspaceId}
disabled={pendingApproval}
options={[
{ value: "", label: t("agent.noWorkspace") },
...workspaces.map((w) => ({ value: w.id, label: w.name })),
]}
/>
<Select
label={t("agent.reportsTo")}
value={reportsTo}
onChange={setReportsTo}
disabled={pendingApproval}
options={[
{ value: "", label: t("agent.noManager") },
...managerCandidates.map((a) => ({ value: a.id, label: a.name })),
]}
/>
<Input
label={t("agent.budget")}
value={budget}
onChange={setBudget}
placeholder="120"
type="number"
readOnly={pendingApproval}
disabled={pendingApproval}
/>
<TextArea
label={t("agent.capabilities")}
value={capabilities}
onChange={setCapabilities}
placeholder={t("agent.capabilities")}
readOnly={pendingApproval}
disabled={pendingApproval}
/>
<HeartbeatSection
agent={agent}
companyId={companyId}
pendingApproval={pendingApproval}
persistedConnectorIssue={selectedConnectorIssue ?? persistedConnectorIssue}
onSaved={onSaved}
onWakeNow={() => void handleWakeNow()}
/>
<div className="mt-6 border-t border-border pt-4">
<h3 className="text-[13px] font-semibold uppercase tracking-[0.08em] text-primary mb-3">
{t("agent.skills")}
</h3>
<SkillAgentToggles
mode="agent"
companyId={companyId}
agentId={agent.id}
skills={skills}
/>
</div>
<div className="flex items-center gap-3">
<ActionButton
label={t("agent.saveChanges")}
tone="accent"
icon={Pencil}
disabled={pendingApproval || !!selectedConnectorIssue}
onClick={() => void handleSave()}
/>
{confirmingDelete ? (
<ConfirmDelete
entityLabel={agent.name}
onConfirm={() =>
void (async () => {
await window.agentCompany.deleteAgent({ id: agent.id, companyId });
if (onDeleted) await onDeleted();
})()
}
onCancel={() => setConfirmingDelete(false)}
/>
) : (
<ActionButton
label="Delete"
tone="danger"
icon={Trash}
disabled={pendingApproval}
onClick={() => setConfirmingDelete(true)}
/>
)}
</div>
{pendingApproval ? (
<p className="text-[12px] text-muted-foreground">
Pending hires are read-only here. Use Hiring or Approvals to approve or reject
the request before editing the agent profile.
</p>
) : null}
{saveError ? <p className="text-[12px] text-destructive">{saveError}</p> : null}
</div>
);
}
@@ -0,0 +1,3 @@
export { AgentForm } from "./AgentForm";
export { useAgentForm } from "./useAgentForm";
export type { AgentFormMode, AgentFormState } from "./useAgentForm";
@@ -0,0 +1,226 @@
import { useEffect, useMemo, useState } from "react";
import type {
AgentRecord,
BudgetPolicyRecord,
ConnectorRecord,
DepartmentId,
} from "@shared/types";
import { isConnectorExecutionReady } from "@shared/connector-policy";
import { safeJson } from "../../../lib/safe-json";
import { getBudgetPolicyAmountDollars } from "../../../lib/budget-policy-input";
export type AgentFormMode = "create" | "edit";
export interface UseAgentFormArgs {
mode: AgentFormMode;
agent: AgentRecord | null;
connectors: ConnectorRecord[];
workspaces: { id: string; name: string }[];
companyId: string;
}
export interface AgentFormState {
name: string;
setName: (value: string) => void;
role: string;
setRole: (value: string) => void;
title: string;
setTitle: (value: string) => void;
department: DepartmentId | "";
setDepartment: (value: DepartmentId | "") => void;
status: AgentRecord["status"];
setStatus: (value: AgentRecord["status"]) => void;
connectorId: ConnectorRecord["id"];
setConnectorId: (value: ConnectorRecord["id"]) => void;
model: string;
setModel: (value: string) => void;
availableModels: Array<{ id: string; label: string }>;
workspaceId: string;
setWorkspaceId: (value: string) => void;
budget: string;
setBudget: (value: string) => void;
capabilities: string;
setCapabilities: (value: string) => void;
reportsTo: string;
setReportsTo: (value: string) => void;
thinkingEffort: string;
setThinkingEffort: (value: string) => void;
existingMeta: Record<string, unknown>;
supportsEffort: boolean;
executionReadyConnectors: ConnectorRecord[];
selectedConnectorRecord: ConnectorRecord | null;
selectedDepartment: DepartmentId | null;
agentPolicy: BudgetPolicyRecord | null;
}
export function useAgentForm({
mode,
agent,
connectors,
workspaces,
companyId,
}: UseAgentFormArgs): AgentFormState {
const executionReadyConnectors = useMemo(
() => connectors.filter((connector) => isConnectorExecutionReady(connector)),
[connectors],
);
const initialConnectorId: ConnectorRecord["id"] =
agent?.connectorId ?? executionReadyConnectors[0]?.id ?? ("" as ConnectorRecord["id"]);
const existingMeta = useMemo(
() => safeJson<Record<string, unknown>>(agent?.metadataJson ?? null, {}),
[agent?.metadataJson],
);
const [name, setName] = useState(agent?.name ?? "");
const [role, setRole] = useState(agent?.role ?? "");
const [title, setTitle] = useState(agent?.title ?? "");
const [department, setDepartment] = useState<DepartmentId | "">(agent?.department ?? "");
const [status, setStatus] = useState<AgentRecord["status"]>(agent?.status ?? "idle");
const [connectorId, setConnectorId] = useState<ConnectorRecord["id"]>(initialConnectorId);
const [model, setModel] = useState(agent?.model ?? "");
const [availableModels, setAvailableModels] = useState<Array<{ id: string; label: string }>>([]);
const [workspaceId, setWorkspaceId] = useState(
agent ? agent.workspaceId ?? "" : workspaces[0]?.id ?? "",
);
const [budget, setBudget] = useState(agent ? String(agent.budgetMonthlyUsd) : "120");
const [capabilities, setCapabilities] = useState(agent?.capabilities ?? "");
const [reportsTo, setReportsTo] = useState(agent?.reportsTo ?? "");
const [thinkingEffort, setThinkingEffort] = useState<string>(
typeof existingMeta.thinkingEffort === "string" ? existingMeta.thinkingEffort : "",
);
const supportsEffort =
connectorId === "claude_local" ||
connectorId === "codex_local" ||
(mode === "edit" && typeof connectorId === "string" && connectorId.startsWith("claude_compat"));
const selectedConnectorRecord =
(mode === "edit" ? connectors : executionReadyConnectors).find(
(connector) => connector.id === connectorId,
) ?? null;
const persistedConnectorId = agent?.connectorId ?? null;
useEffect(() => {
if (mode === "create") {
if (!connectorId) {
setAvailableModels([]);
setModel("");
return;
}
void (async () => {
const result = await window.agentCompany.listConnectorModels(connectorId);
if (result.ok) {
setAvailableModels(result.data);
setModel(result.data[0]?.id ?? "");
}
})();
if (connectorId !== "claude_local" && connectorId !== "codex_local") {
setThinkingEffort("");
}
return;
}
if (typeof connectorId === "string" && connectorId.startsWith("claude_compat")) {
const compat = connectors.find((c) => c.id === connectorId);
const raw = compat?.model ?? "";
const models = raw
.split(",")
.map((m) => m.trim())
.filter(Boolean)
.map((m) => ({ id: m, label: m }));
setAvailableModels(models);
if (persistedConnectorId && connectorId !== persistedConnectorId && models.length > 0) {
setModel(models[0].id);
}
} else {
void (async () => {
const result = await window.agentCompany.listConnectorModels(connectorId);
if (result.ok) {
setAvailableModels(result.data);
if (persistedConnectorId && connectorId !== persistedConnectorId) {
setModel(result.data[0]?.id ?? "");
}
}
})();
}
if (
connectorId !== "claude_local" &&
!(typeof connectorId === "string" && connectorId.startsWith("claude_compat")) &&
connectorId !== "codex_local"
) {
setThinkingEffort("");
}
}, [connectorId, mode, persistedConnectorId, connectors]);
useEffect(() => {
if (mode !== "create") return;
if (!connectorId && executionReadyConnectors[0]) {
setConnectorId(executionReadyConnectors[0].id);
return;
}
if (
connectorId &&
!executionReadyConnectors.some((connector) => connector.id === connectorId)
) {
setConnectorId(executionReadyConnectors[0]?.id ?? ("" as ConnectorRecord["id"]));
}
}, [executionReadyConnectors, connectorId, mode]);
const [agentPolicy, setAgentPolicy] = useState<BudgetPolicyRecord | null>(null);
const agentIdForPolicy = agent?.id ?? null;
useEffect(() => {
if (mode !== "edit" || !agentIdForPolicy) return;
void (async () => {
const result = await window.agentCompany.listBudgetPolicies(companyId);
if (result.ok) {
const policy = result.data
.filter((p) => p.scopeType === "agent" && p.scopeId === agentIdForPolicy)
.sort((left, right) => {
if (left.isActive !== right.isActive) return left.isActive ? -1 : 1;
return right.updatedAt.localeCompare(left.updatedAt);
})[0];
if (policy) {
setAgentPolicy(policy);
setBudget(getBudgetPolicyAmountDollars(policy));
}
}
})();
}, [companyId, agentIdForPolicy, mode]);
const selectedDepartment: DepartmentId | null = department === "" ? null : department;
return {
name,
setName,
role,
setRole,
title,
setTitle,
department,
setDepartment,
status,
setStatus,
connectorId,
setConnectorId,
model,
setModel,
availableModels,
workspaceId,
setWorkspaceId,
budget,
setBudget,
capabilities,
setCapabilities,
reportsTo,
setReportsTo,
thinkingEffort,
setThinkingEffort,
existingMeta,
supportsEffort,
executionReadyConnectors,
selectedConnectorRecord,
selectedDepartment,
agentPolicy,
};
}