diff --git a/AgentCompany-New/src/renderer/src/pages/SettingsPage.tsx b/AgentCompany-New/src/renderer/src/pages/SettingsPage.tsx index 69ed4f18..fb38abb2 100644 --- a/AgentCompany-New/src/renderer/src/pages/SettingsPage.tsx +++ b/AgentCompany-New/src/renderer/src/pages/SettingsPage.tsx @@ -2,13 +2,11 @@ import { useMemo, useState } from "react"; import { ProvidersPanel } from "./settings/ProvidersPanel"; import { SkillsPanel } from "./settings/SkillsPanel"; import { WorkspacePanel } from "./settings/WorkspacePanel"; -import { BillingPanel } from "./settings/BillingPanel"; -import { MigrationPanel } from "./settings/MigrationPanel"; -import { ComposioPanel } from "./settings/ComposioPanel"; +import { AdvancedPanel } from "./settings/AdvancedPanel"; import type { PageProps } from "./types"; import { activeOrgId, requireActiveEmployee, requireActiveOrganization } from "./types"; -type SettingsTab = "workspace" | "billing" | "providers" | "tools" | "skills" | "migration"; +type SettingsTab = "general" | "providers" | "skills" | "advanced"; interface NavSpec { id: SettingsTab; @@ -16,19 +14,17 @@ interface NavSpec { } const TABS: NavSpec[] = [ - { id: "workspace", label: "Workspace" }, - { id: "billing", label: "Billing" }, + { id: "general", label: "General" }, { id: "providers", label: "Providers" }, - { id: "tools", label: "Tools" }, { id: "skills", label: "Skills" }, - { id: "migration", label: "Migration" }, + { id: "advanced", label: "Advanced" }, ]; export function SettingsPage({ snapshot, actions }: PageProps) { const orgId = activeOrgId(snapshot); const organization = requireActiveOrganization(snapshot); const employee = requireActiveEmployee(snapshot); - const [tab, setTab] = useState("providers"); + const [tab, setTab] = useState("general"); const agents = useMemo(() => snapshot.agents.filter((item) => item.orgId === orgId), [snapshot.agents, orgId]); const providers = useMemo(() => snapshot.providers.filter((item) => item.orgId === orgId), [snapshot.providers, orgId]); @@ -60,8 +56,7 @@ export function SettingsPage({ snapshot, actions }: PageProps) {
- {tab === "workspace" && p.orgId === orgId)} orgId={orgId} actions={actions} />} - {tab === "billing" && } + {tab === "general" && p.orgId === orgId)} orgId={orgId} actions={actions} />} {tab === "providers" && ( )} - {tab === "tools" && } {tab === "skills" && ( )} - {tab === "migration" && ( - item.orgId === orgId)} actions={actions} diff --git a/AgentCompany-New/src/renderer/src/pages/settings/AdvancedPanel.tsx b/AgentCompany-New/src/renderer/src/pages/settings/AdvancedPanel.tsx new file mode 100644 index 00000000..901c8bcc --- /dev/null +++ b/AgentCompany-New/src/renderer/src/pages/settings/AdvancedPanel.tsx @@ -0,0 +1,86 @@ +import type { + AgentRecord, + HiringPolicyRecord, + MigrationReportRecord, + ProviderCatalogEntry, + ProviderRecord, + ProviderRequestRecord, + RollbackSnapshotRecord, + WorkspaceRecord, +} from "../../../../shared/domain"; +import type { MigrationReconciliationItemRecord } from "../../../../shared/rollback-domain"; +import type { ComposioSettingRecord } from "../../../../shared/snapshot-domain"; +import type { SnapshotActions } from "../../app/useSnapshot"; +import { BillingPanel } from "./BillingPanel"; +import { ComposioPanel } from "./ComposioPanel"; +import { MigrationPanel } from "./MigrationPanel"; +import { ProviderRequestPanel } from "./ProviderRequestPanel"; + +/** The low-frequency operator surfaces, one section each. */ +export function AdvancedPanel({ + orgId, + employeeId, + agents, + providers, + catalog, + workspaces, + providerRequests, + hiringPolicies, + composioSettings, + migrationReports, + rollbackSnapshots, + reconciliationItems, + actions, +}: { + orgId: string; + employeeId: string; + agents: AgentRecord[]; + providers: ProviderRecord[]; + catalog: ProviderCatalogEntry[]; + workspaces: WorkspaceRecord[]; + providerRequests: ProviderRequestRecord[]; + hiringPolicies: HiringPolicyRecord[]; + composioSettings: ComposioSettingRecord[]; + migrationReports: MigrationReportRecord[]; + rollbackSnapshots: RollbackSnapshotRecord[]; + reconciliationItems: MigrationReconciliationItemRecord[]; + actions: SnapshotActions; +}) { + const connected = providers.filter((provider) => provider.enabled); + return ( +
+

Advanced

+ +
Composio
+ + + {connected.length > 0 && ( +
+ +
+ )} + +
+ +
+ +
+ +
+
+ ); +} diff --git a/AgentCompany-New/src/renderer/src/pages/settings/BillingPanel.tsx b/AgentCompany-New/src/renderer/src/pages/settings/BillingPanel.tsx index 9f4e4006..50284b89 100644 --- a/AgentCompany-New/src/renderer/src/pages/settings/BillingPanel.tsx +++ b/AgentCompany-New/src/renderer/src/pages/settings/BillingPanel.tsx @@ -22,9 +22,8 @@ export function BillingPanel({ const agentSpend = buildAgentSpend(agents, providers, monthRequests); return ( -
-

Billing

-

Allocations, spend, and cost breakdown.

+
+
Billing
diff --git a/AgentCompany-New/src/renderer/src/pages/settings/ComposioPanel.tsx b/AgentCompany-New/src/renderer/src/pages/settings/ComposioPanel.tsx index 7e71e113..756b6ca9 100644 --- a/AgentCompany-New/src/renderer/src/pages/settings/ComposioPanel.tsx +++ b/AgentCompany-New/src/renderer/src/pages/settings/ComposioPanel.tsx @@ -3,44 +3,14 @@ import { Button } from "../../shared/ui"; import type { SnapshotActions } from "../../app/useSnapshot"; import type { ComposioSettingRecord } from "../../../../shared/snapshot-domain"; -const AVAILABLE_TOOLKITS = [ - { id: "github", label: "GitHub" }, - { id: "gmail", label: "Gmail" }, - { id: "slack", label: "Slack" }, - { id: "notion", label: "Notion" }, - { id: "linear", label: "Linear" }, - { id: "jira", label: "Jira" }, - { id: "google_calendar", label: "Google Calendar" }, - { id: "google_drive", label: "Google Drive" }, - { id: "google_docs", label: "Google Docs" }, - { id: "google_sheets", label: "Google Sheets" }, - { id: "trello", label: "Trello" }, - { id: "asana", label: "Asana" }, - { id: "discord", label: "Discord" }, - { id: "twitter", label: "Twitter / X" }, - { id: "youtube", label: "YouTube" }, - { id: "hubspot", label: "HubSpot" }, - { id: "salesforce", label: "Salesforce" }, - { id: "zendesk", label: "Zendesk" }, - { id: "figma", label: "Figma" }, - { id: "airtable", label: "Airtable" }, -]; - -function parseSavedToolkits(settings: ComposioSettingRecord[], orgId: string): Set { - const row = settings.find((s) => s.orgId === orgId && s.key === "toolkits"); - if (!row?.value) return new Set(); - try { - const parsed = JSON.parse(row.value); - return new Set(Array.isArray(parsed) ? parsed.filter((v): v is string => typeof v === "string") : []); - } catch { - return new Set(); - } -} - function hasSavedKey(settings: ComposioSettingRecord[], orgId: string): boolean { return settings.some((s) => s.orgId === orgId && s.key === "api_key" && s.value.length > 0); } +/** + * The Composio project API key. Which toolkits agents may use is decided by + * connections and per-agent grants on the Connectors page, not here. + */ export function ComposioPanel({ orgId, settings, @@ -50,98 +20,33 @@ export function ComposioPanel({ settings: ComposioSettingRecord[]; actions: SnapshotActions; }) { - const savedToolkits = useMemo(() => parseSavedToolkits(settings, orgId), [settings, orgId]); const keyConfigured = useMemo(() => hasSavedKey(settings, orgId), [settings, orgId]); - const [apiKey, setApiKey] = useState(""); - const [selected, setSelected] = useState>(savedToolkits); - const [saved, setSaved] = useState(false); - const toggle = (id: string) => { - setSelected((prev) => { - const next = new Set(prev); - if (next.has(id)) next.delete(id); - else next.add(id); - return next; - }); - }; - - const handleSave = async (e: FormEvent) => { - e.preventDefault(); + const handleSave = (event: FormEvent) => { + event.preventDefault(); const key = apiKey.trim(); - if (!key && !keyConfigured) return; - const row = settings.find((s) => s.orgId === orgId && s.key === "api_key"); - const effectiveKey = key || row?.value || ""; - if (!effectiveKey) return; - await actions.run(() => window.agentCompany.saveComposioSettings({ + if (!key) return; + void actions.run(() => window.agentCompany.saveComposioSettings({ orgId, - apiKey: effectiveKey, - toolkits: [...selected], - })); - setApiKey(""); - setSaved(true); - setTimeout(() => setSaved(false), 2000); + apiKey: key, + toolkits: [], + })).then(() => setApiKey("")); }; return ( -
-

External Tools

-

- Connect your agents to external services via Composio. -

-
- -
- Toolkits -
- {AVAILABLE_TOOLKITS.map((toolkit) => ( - - ))} -
-
-
- - {saved && Saved} -
-
-
+
+ setApiKey(event.target.value)} + placeholder={keyConfigured ? "••••••••" : "ak_..."} + autoComplete="off" + style={{ flex: 1 }} + /> + +
); } diff --git a/AgentCompany-New/src/renderer/src/pages/settings/MigrationPanel.tsx b/AgentCompany-New/src/renderer/src/pages/settings/MigrationPanel.tsx index b92c9d6b..3ff57501 100644 --- a/AgentCompany-New/src/renderer/src/pages/settings/MigrationPanel.tsx +++ b/AgentCompany-New/src/renderer/src/pages/settings/MigrationPanel.tsx @@ -40,13 +40,10 @@ export function MigrationPanel({ }; return ( -
-
-
-

Migration

-

{reports.length} migration {reports.length === 1 ? "report" : "reports"}.

-
-
+
+
+
Migration
+
diff --git a/AgentCompany-New/src/renderer/src/pages/settings/ProvidersPanel.tsx b/AgentCompany-New/src/renderer/src/pages/settings/ProvidersPanel.tsx index be1dee1b..8d3a6c0c 100644 --- a/AgentCompany-New/src/renderer/src/pages/settings/ProvidersPanel.tsx +++ b/AgentCompany-New/src/renderer/src/pages/settings/ProvidersPanel.tsx @@ -7,12 +7,9 @@ import type { ProviderAuthProfileRecord, ProviderRateLimitStateRecord, ProviderRecord, - ProviderRequestRecord, - WorkspaceRecord, } from "../../../../shared/domain"; import { PROVIDER_DATA_POLICY } from "../../../../shared/domain"; import { ProviderLogo } from "./ProviderLogo"; -import { ProviderRequestPanel } from "./ProviderRequestPanel"; import { ProviderConnectFields, ProviderOAuthFields } from "./ProviderConnectionDialog"; import { composeSecret } from "./provider-secret"; import type { SnapshotActions } from "../../app/useSnapshot"; @@ -24,16 +21,14 @@ interface ProvidersPanelProps { authProfiles: ProviderAuthProfileRecord[]; catalog: ProviderCatalogEntry[]; agents: AgentRecord[]; - workspaces: WorkspaceRecord[]; providerRateLimits: ProviderRateLimitStateRecord[]; - providerRequests: ProviderRequestRecord[]; actions: SnapshotActions; } const POLICY_GRID = "minmax(150px, 1.2fr) minmax(118px, 0.8fr) minmax(140px, 1fr) 112px 124px 92px"; type WorkspaceScope = "organization" | "workspace"; -export function ProvidersPanel({ orgId, employeeId, providers, authProfiles, catalog, agents, workspaces, providerRateLimits, providerRequests, actions }: ProvidersPanelProps) { +export function ProvidersPanel({ orgId, employeeId, providers, authProfiles, catalog, agents, providerRateLimits, actions }: ProvidersPanelProps) { const [active, setActive] = useState(null); const connected = providers.filter((provider) => provider.enabled); const available = providers.filter((provider) => !provider.enabled); @@ -62,11 +57,10 @@ export function ProvidersPanel({ orgId, employeeId, providers, authProfiles, cat return (

Providers

-

Connect model providers — keys, accounts, OAuth.

{connected.length > 0 && ( <> -
Connected
+
Connected
{connected.map((provider) => ( - )} @@ -159,7 +145,7 @@ function ProviderPolicyTable({ const rateLimit = requireProviderRateLimit(provider, providerRateLimits); return ( agent.modelProviderId === provider.id).length; return (
-
-
{boundCount} {boundCount === 1 ? "agent" : "agents"}
-
bound
+
+ {boundCount} {boundCount === 1 ? "agent" : "agents"}
- - - + + + + +
); } diff --git a/AgentCompany-New/src/renderer/src/pages/settings/SkillsPanel.tsx b/AgentCompany-New/src/renderer/src/pages/settings/SkillsPanel.tsx index d99b2f54..f42246be 100644 --- a/AgentCompany-New/src/renderer/src/pages/settings/SkillsPanel.tsx +++ b/AgentCompany-New/src/renderer/src/pages/settings/SkillsPanel.tsx @@ -44,15 +44,10 @@ export function SkillsPanel({ orgId, skills, revisions, bindings, tests, agents, }; return ( -
+
-
-

Skills

-

- {skills.length} {skills.length === 1 ? "definition" : "definitions"} · {revisions.length} revisions · {bindings.filter((b) => b.enabled).length} bindings -

-
-
+

Skills

+
diff --git a/AgentCompany-New/src/renderer/src/pages/settings/WorkspacePanel.tsx b/AgentCompany-New/src/renderer/src/pages/settings/WorkspacePanel.tsx index 8ce4b522..2b2ddf9a 100644 --- a/AgentCompany-New/src/renderer/src/pages/settings/WorkspacePanel.tsx +++ b/AgentCompany-New/src/renderer/src/pages/settings/WorkspacePanel.tsx @@ -48,44 +48,38 @@ export function WorkspacePanel({ return (
-

Workspace

-

Identity, mission, and execution mode for {organization.name}.

+

General

-
-
Identity
+
+ {organization.executionMode === "ai_ceo" ? "AI CEO" : "AI Co-Founder"} + {organization.autoApproveHires ? Auto-approve hires : Hires require approval} + {organization.reviewDeliverables ? Reviews deliverables : Ships without review} +
+ +
-
-
Execution mode
-
- {organization.executionMode === "ai_ceo" ? "AI CEO" : "AI Co-Founder"} - {organization.autoApproveHires ? Auto-approve hires : Hires require approval} - {organization.reviewDeliverables ? Reviews deliverables : Deliverables ship without review} -
+
+
Workspaces
+ + +
- -
-
-
Workspaces
- -
- {workspaces.map((workspace) => ( -
-
-
{workspace.name}
-
{workspace.localPath}
-
-
- {workspace.isPrimary ? Primary : Secondary} -
+ {workspaces.map((workspace) => ( +
+
+
{workspace.name}
+
{workspace.localPath}
- ))} - {workspaces.length === 0 &&
No workspaces configured
} -
+
+ {workspace.isPrimary && Primary} +
+
+ ))} setCreateOpen(false)} onSubmit={submit}> diff --git a/AgentCompany-New/src/renderer/src/styles.css b/AgentCompany-New/src/renderer/src/styles.css index c4b91ff9..c904a7df 100644 --- a/AgentCompany-New/src/renderer/src/styles.css +++ b/AgentCompany-New/src/renderer/src/styles.css @@ -226,6 +226,11 @@ a { color: inherit; } .convo-item:hover { background: var(--hover); color: var(--text-1); } .convo-item.active { background: var(--panel-3); color: var(--text-1); } +/* Controls that appear on intent: hidden until the row is hovered or focused. */ +.reveal-row .reveal { opacity: 0; transition: opacity var(--motion-fast) var(--ease-out); } +.reveal-row:hover .reveal, +.reveal-row:focus-within .reveal { opacity: 1; } + /* Selectable list row used by master-detail panes (Runs, etc.). width:100% + min-width:0 lets the inner flex child ellipsize instead of pushing the row past the pane and forcing a horizontal scrollbar. */ diff --git a/AgentCompany-New/tests/e2e/desktop.spec.ts b/AgentCompany-New/tests/e2e/desktop.spec.ts index 8c597308..7a897ec1 100644 --- a/AgentCompany-New/tests/e2e/desktop.spec.ts +++ b/AgentCompany-New/tests/e2e/desktop.spec.ts @@ -113,19 +113,27 @@ test("onboards, hires, and creates work in the Electron shell", async () => { await expect(page.getByRole("button", { name: "Refresh" })).toBeVisible(); await page.getByRole("button", { name: "Settings" }).click(); + await expect(page.getByText("AI CEO", { exact: true })).toBeVisible(); await page.getByRole("button", { name: "Providers" }).click(); await page.getByLabel("OpenAI background execution").check(); await page.getByRole("button", { name: "Save OpenAI policy" }).click(); - await expect(page.getByText("Requests", { exact: true })).toBeVisible(); + + // The operator request console lives under Advanced. + await page.getByRole("button", { name: "Advanced" }).click(); + await expect(page.getByText("Requests", { exact: true }).first()).toBeVisible(); await expect(page.getByLabel("Request provider")).toBeVisible(); await expect(page.getByRole("button", { name: "Background" })).toBeVisible(); await page.getByLabel("Request prompt").fill("Draft a provider control memo."); await page.getByLabel("Request instructions").fill("Use direct language."); + await expect(page.getByLabel("Composio API key")).toBeVisible(); + + await page.getByRole("button", { name: "Providers" }).click(); await page.getByLabel("OpenAI data policy").selectOption("zero_data_retention"); await expect(page.getByLabel("OpenAI background execution")).not.toBeChecked(); await page.getByLabel("OpenAI monthly request limit").fill("25"); await page.getByRole("button", { name: "Save OpenAI policy" }).click(); await expect(page.getByText("0 / 25").first()).toBeVisible(); + await page.getByRole("button", { name: "Skills" }).click(); await page.getByRole("button", { name: "Import" }).click(); await page.getByLabel("Directory path").fill(createSkillDirectory()); @@ -135,7 +143,7 @@ test("onboards, hires, and creates work in the Electron shell", async () => { await page.getByRole("button", { name: "Run tests" }).click(); await expect(page.getByText("1/1 passed").first()).toBeVisible(); const legacyPath = createLegacyDatabase(); - await page.getByRole("button", { name: "Migration" }).click(); + await page.getByRole("button", { name: "Advanced" }).click(); await page.getByRole("button", { name: "Report" }).click(); await page.getByLabel("Source database path").fill(legacyPath); await page.locator(".modal").getByRole("button", { name: "Create" }).click(); @@ -150,12 +158,6 @@ test("onboards, hires, and creates work in the Electron shell", async () => { await page.getByRole("button", { name: "Office" }).click(); await expect(page.locator("canvas")).toBeVisible(); - // Composio settings panel - await page.getByRole("button", { name: "Settings" }).click(); - await page.getByRole("button", { name: "Tools", exact: true }).click(); - await expect(page.getByText("External Tools")).toBeVisible(); - await expect(page.getByText("GitHub")).toBeVisible(); - // @mention with canonical handle in chat await page.getByRole("button", { name: "Messages" }).click(); await page.getByText("general").first().click(); @@ -204,7 +206,7 @@ async function verifyPagesDoNotOverflow(page: Page): Promise { await page.getByRole("button", { name: nav, exact: true }).click(); await expectPageToFit(page); } - for (const tab of ["Workspace", "Billing", "Providers", "Tools", "Skills", "Migration"]) { + for (const tab of ["General", "Providers", "Skills", "Advanced"]) { await page.getByRole("button", { name: "Settings" }).click(); await page.getByRole("button", { name: tab, exact: true }).click(); await expectPageToFit(page); diff --git a/AgentCompany-New/tests/e2e/screenshots.spec.ts b/AgentCompany-New/tests/e2e/screenshots.spec.ts index 1e242095..ace30509 100644 --- a/AgentCompany-New/tests/e2e/screenshots.spec.ts +++ b/AgentCompany-New/tests/e2e/screenshots.spec.ts @@ -50,7 +50,7 @@ test("captures every page for visual review", async () => { await go(page, "Settings", outDir, "17-settings"); await go(page, "Office", outDir, "18-office"); - for (const tab of ["Workspace", "Billing", "Skills", "Migration"]) { + for (const tab of ["General", "Providers", "Skills", "Advanced"]) { await page.getByRole("button", { name: "Settings" }).click(); await page.getByRole("button", { name: tab }).first().click(); await snap(page, outDir, `settings-${tab.toLowerCase()}`);