diff --git a/AgentCompany-New/src/renderer/src/app/App.tsx b/AgentCompany-New/src/renderer/src/app/App.tsx index 26434b57..424b8f16 100644 --- a/AgentCompany-New/src/renderer/src/app/App.tsx +++ b/AgentCompany-New/src/renderer/src/app/App.tsx @@ -13,7 +13,6 @@ import { DocumentsPage } from "../pages/DocumentsPage"; import { KnowledgePage } from "../pages/KnowledgePage"; import { InboxPage } from "../pages/InboxPage"; import { RunsPage } from "../pages/RunsPage"; -import { AutomationPage } from "../pages/AutomationPage"; import { MeetingsPage } from "../pages/MeetingsPage"; import { SprintsPage } from "../pages/SprintsPage"; import { SettingsPage } from "../pages/SettingsPage"; @@ -48,7 +47,6 @@ export function App() { knowledge: , inbox: , runs: , - automation: , meetings: , sprints: , approvals: , diff --git a/AgentCompany-New/src/renderer/src/pages/AutomationPage.tsx b/AgentCompany-New/src/renderer/src/pages/AutomationPage.tsx deleted file mode 100644 index 3ac83ca9..00000000 --- a/AgentCompany-New/src/renderer/src/pages/AutomationPage.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import { useMemo, useState, type FormEvent } from "react"; -import { Button, Field, Icon, IconButton, Pill } from "../shared/ui"; -import { TopbarActions } from "../shared/TopbarActions"; -import { Dialog } from "../shared/Dialog"; -import type { AutomationRuleRecord } from "../../../shared/domain"; -import type { PageProps } from "./types"; -import { activeOrgId, formatRelative } from "./types"; - -const TRIGGER_EVENTS = [ - "task_created", - "task_status_changed", - "agent_hired", - "approval_created", - "message_sent", - "file_uploaded", - "goal_created", - "project_created", -] as const; - -const ACTION_TYPES = [ - "assign_to_department", - "create_task", - "send_notification", - "update_status", - "escalate", -] as const; - -function text(form: FormData, key: string): string { - return String(form.get(key) ?? "").trim(); -} - -export function AutomationPage({ snapshot, actions }: PageProps) { - const orgId = activeOrgId(snapshot); - const rules = useMemo(() => snapshot.automationRules.filter((r) => r.orgId === orgId), [snapshot.automationRules, orgId]); - const [createOpen, setCreateOpen] = useState(false); - const [editingId, setEditingId] = useState(null); - - const editing = editingId ? rules.find((r) => r.id === editingId) ?? null : null; - const enabledCount = rules.filter((r) => r.enabled).length; - const totalFires = rules.reduce((sum, r) => sum + r.fireCount, 0); - - const create = (event: FormEvent) => { - event.preventDefault(); - const form = new FormData(event.currentTarget); - void actions.run(() => window.agentCompany.createAutomationRule({ - orgId, - name: text(form, "name"), - triggerEvent: text(form, "triggerEvent"), - conditions: {}, - actionType: text(form, "actionType"), - actionConfig: {}, - priority: Number(text(form, "priority")) || 100, - enabled: true, - })).then((r) => { if (r) setCreateOpen(false); }); - }; - - const save = (event: FormEvent) => { - event.preventDefault(); - if (!editing) return; - const form = new FormData(event.currentTarget); - void actions.run(() => window.agentCompany.updateAutomationRule({ - orgId, - ruleId: editing.id, - name: text(form, "name"), - triggerEvent: text(form, "triggerEvent"), - conditions: {}, - actionType: text(form, "actionType"), - actionConfig: {}, - priority: Number(text(form, "priority")) || editing.priority, - enabled: form.get("enabled") === "on", - })).then((r) => { if (r) setEditingId(null); }); - }; - - const remove = (ruleId: string) => { - void actions.run(() => window.agentCompany.deleteAutomationRule({ orgId, ruleId })); - setEditingId(null); - }; - - return ( -
- - - -

Automation

-

{enabledCount} active · {totalFires} total fires

- -
-
-
Name
Trigger
Action
Fires
Status
-
- {rules.map((rule, index) => ( -
setEditingId(rule.id)} - onKeyDown={(e) => { if (e.key === "Enter") setEditingId(rule.id); }} - style={{ gridTemplateColumns: "minmax(0, 2fr) minmax(0, 1.2fr) minmax(0, 1.2fr) 70px 80px 50px", borderBottom: index === rules.length - 1 ? "none" : "1px solid var(--border-soft)" }} - > -
{rule.name}
-
{humanize(rule.triggerEvent)}
-
{humanize(rule.actionType)}
-
{rule.fireCount}
-
{rule.enabled ? Active : Paused}
-
e.stopPropagation()}> - remove(rule.id)} /> -
-
- ))} -
- - setCreateOpen(false)} onSubmit={create}> - - - - - - - - - - - {editing && ( - setEditingId(null)} onSubmit={save}> - - - - - - - - - - - - {editing.lastFiredAt &&
Last fired {formatRelative(editing.lastFiredAt)}
} -
- )} -
- ); -} - -function humanize(value: string): string { - return value.replace(/_/g, " "); -} diff --git a/AgentCompany-New/src/renderer/src/pages/SettingsPage.tsx b/AgentCompany-New/src/renderer/src/pages/SettingsPage.tsx index 3a1f4b4d..54485ee7 100644 --- a/AgentCompany-New/src/renderer/src/pages/SettingsPage.tsx +++ b/AgentCompany-New/src/renderer/src/pages/SettingsPage.tsx @@ -2,59 +2,25 @@ import { useMemo, useState } from "react"; import { ProvidersPanel } from "./settings/ProvidersPanel"; import { SkillsPanel } from "./settings/SkillsPanel"; import { WorkspacePanel } from "./settings/WorkspacePanel"; -import { MembersPanel } from "./settings/MembersPanel"; import { BillingPanel } from "./settings/BillingPanel"; -import { ModelsPanel } from "./settings/ModelsPanel"; -import { PoliciesPanel } from "./settings/PoliciesPanel"; -import { OperationsPanel } from "./settings/OperationsPanel"; -import { SdlcPanel } from "./settings/SdlcPanel"; import { MigrationPanel } from "./settings/MigrationPanel"; -import { CompliancePanel } from "./settings/CompliancePanel"; -import { NotificationsPanel, ProfilePanel, SecurityPanel } from "./settings/AccountPanels"; import type { PageProps } from "./types"; import { activeOrgId, requireActiveEmployee, requireActiveOrganization } from "./types"; -type SettingsTab = - | "workspace" - | "members" - | "billing" - | "models" - | "providers" - | "skills" - | "policies" - | "operations" - | "sdlc" - | "compliance" - | "migration" - | "profile" - | "notifications" - | "security"; +type SettingsTab = "workspace" | "billing" | "providers" | "skills" | "migration"; interface NavSpec { id: SettingsTab; label: string; } -const WORKSPACE_GROUP: NavSpec[] = [ +const TABS: NavSpec[] = [ { id: "workspace", label: "Workspace" }, - { id: "members", label: "Members" }, { id: "billing", label: "Billing" }, -]; -const AGENTS_GROUP: NavSpec[] = [ - { id: "models", label: "Models" }, { id: "providers", label: "Providers" }, { id: "skills", label: "Skills" }, - { id: "policies", label: "Policies" }, - { id: "operations", label: "Operations" }, - { id: "sdlc", label: "SDLC" }, - { id: "compliance", label: "Compliance" }, { id: "migration", label: "Migration" }, ]; -const ACCOUNT_GROUP: NavSpec[] = [ - { id: "profile", label: "Profile" }, - { id: "notifications", label: "Notifications" }, - { id: "security", label: "Security" }, -]; export function SettingsPage({ snapshot, actions }: PageProps) { const orgId = activeOrgId(snapshot); @@ -62,7 +28,6 @@ export function SettingsPage({ snapshot, actions }: PageProps) { const employee = requireActiveEmployee(snapshot); const [tab, setTab] = useState("providers"); - const employees = useMemo(() => snapshot.employees.filter((item) => item.orgId === orgId), [snapshot.employees, orgId]); 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]); const providerAuthProfiles = useMemo(() => snapshot.providerAuthProfiles.filter((item) => item.orgId === orgId), [snapshot.providerAuthProfiles, orgId]); @@ -77,36 +42,10 @@ export function SettingsPage({ snapshot, actions }: PageProps) { return snapshot.skillTests.filter((test) => revisionIds.has(test.revisionId)); }, [snapshot.skillTests, skillRevisions]); const skillBindings = useMemo(() => snapshot.skillBindings.filter((item) => item.orgId === orgId), [snapshot.skillBindings, orgId]); - const policies = useMemo(() => snapshot.policyRules.filter((item) => item.orgId === orgId), [snapshot.policyRules, orgId]); const workspaces = useMemo(() => snapshot.workspaces.filter((item) => item.orgId === orgId), [snapshot.workspaces, orgId]); - const chatSpaces = useMemo(() => snapshot.chatSpaces.filter((item) => item.orgId === orgId), [snapshot.chatSpaces, orgId]); - const chatThreads = useMemo(() => snapshot.chatThreads.filter((item) => item.orgId === orgId), [snapshot.chatThreads, orgId]); - const files = useMemo(() => snapshot.files.filter((item) => item.orgId === orgId), [snapshot.files, orgId]); const providerRequests = useMemo(() => snapshot.providerRequests.filter((item) => item.orgId === orgId), [snapshot.providerRequests, orgId]); - const auditEvents = useMemo(() => snapshot.auditEvents.filter((event) => event.orgId === orgId), [snapshot.auditEvents, orgId]); - const migrationReports = useMemo(() => snapshot.migrationReports.filter((report) => report.orgId === orgId), [snapshot.migrationReports, orgId]); - const complianceExports = useMemo(() => snapshot.complianceExports.filter((item) => item.orgId === orgId), [snapshot.complianceExports, orgId]); - const legalHolds = useMemo(() => snapshot.legalHolds.filter((item) => item.orgId === orgId), [snapshot.legalHolds, orgId]); - const retentionFreezes = useMemo(() => snapshot.retentionFreezes.filter((item) => item.orgId === orgId), [snapshot.retentionFreezes, orgId]); - const employeeIdentities = useMemo(() => snapshot.employeeIdentities.filter((item) => item.orgId === orgId), [snapshot.employeeIdentities, orgId]); - const serviceIdentities = useMemo(() => snapshot.serviceIdentities.filter((item) => item.orgId === orgId), [snapshot.serviceIdentities, orgId]); - const roleGrants = useMemo(() => snapshot.roleGrants.filter((item) => item.orgId === orgId), [snapshot.roleGrants, orgId]); - const orgDepartments = useMemo(() => snapshot.orgDepartments.filter((item) => item.orgId === orgId), [snapshot.orgDepartments, orgId]); const hiringPolicies = useMemo(() => snapshot.hiringPolicies.filter((item) => item.orgId === orgId), [snapshot.hiringPolicies, orgId]); - const approvalRoutingRules = useMemo(() => snapshot.approvalRoutingRules.filter((item) => item.orgId === orgId), [snapshot.approvalRoutingRules, orgId]); - const approvalAssignments = useMemo(() => snapshot.approvalAssignments.filter((item) => item.orgId === orgId), [snapshot.approvalAssignments, orgId]); - const featureFlags = useMemo(() => snapshot.featureFlags.filter((item) => item.orgId === orgId), [snapshot.featureFlags, orgId]); - const sloTargets = useMemo(() => snapshot.sloTargets.filter((item) => item.orgId === orgId), [snapshot.sloTargets, orgId]); - const operationalEvents = useMemo(() => snapshot.operationalEvents.filter((item) => item.orgId === orgId), [snapshot.operationalEvents, orgId]); - const chaosDrills = useMemo(() => snapshot.chaosDrills.filter((item) => item.orgId === orgId), [snapshot.chaosDrills, orgId]); - const chaosDrillRuns = useMemo(() => snapshot.chaosDrillRuns.filter((item) => item.orgId === orgId), [snapshot.chaosDrillRuns, orgId]); - const sdlcReviewAgents = useMemo(() => snapshot.sdlcReviewAgents.filter((item) => item.orgId === orgId), [snapshot.sdlcReviewAgents, orgId]); - const sdlcReviewRuns = useMemo(() => snapshot.sdlcReviewRuns.filter((item) => item.orgId === orgId), [snapshot.sdlcReviewRuns, orgId]); - const evalSuites = useMemo(() => snapshot.evalSuites.filter((item) => item.orgId === orgId), [snapshot.evalSuites, orgId]); - const evalCases = useMemo(() => snapshot.evalCases.filter((item) => item.orgId === orgId), [snapshot.evalCases, orgId]); - const evalRuns = useMemo(() => snapshot.evalRuns.filter((item) => item.orgId === orgId), [snapshot.evalRuns, orgId]); - const prBotGates = useMemo(() => snapshot.prBotGates.filter((item) => item.orgId === orgId), [snapshot.prBotGates, orgId]); - const auditReplayRuns = useMemo(() => snapshot.auditReplayRuns.filter((item) => item.orgId === orgId), [snapshot.auditReplayRuns, orgId]); + const migrationReports = useMemo(() => snapshot.migrationReports.filter((report) => report.orgId === orgId), [snapshot.migrationReports, orgId]); const rollbackSnapshots = useMemo(() => snapshot.rollbackSnapshots.filter((item) => item.orgId === orgId), [snapshot.rollbackSnapshots, orgId]); return ( @@ -114,27 +53,13 @@ export function SettingsPage({ snapshot, actions }: PageProps) {
{tab === "workspace" && p.orgId === orgId)} orgId={orgId} actions={actions} />} - {tab === "members" && ( - - )} {tab === "billing" && } - {tab === "models" && } {tab === "providers" && ( )} - {tab === "policies" && ( - - )} - {tab === "operations" && ( - - )} - {tab === "sdlc" && ( - - )} - {tab === "compliance" && ( - - )} {tab === "migration" && ( )} - {tab === "profile" && } - {tab === "notifications" && } - {tab === "security" && }
@@ -256,9 +126,3 @@ function NavGroup({ ); } - -function NavGroupLabel({ children }: { children: string }) { - return ( -
{children}
- ); -} diff --git a/AgentCompany-New/src/renderer/src/pages/settings/AccountPanels.tsx b/AgentCompany-New/src/renderer/src/pages/settings/AccountPanels.tsx deleted file mode 100644 index 8905587b..00000000 --- a/AgentCompany-New/src/renderer/src/pages/settings/AccountPanels.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { Avatar, Pill } from "../../shared/ui"; -import type { AuditEventRecord, EmployeeRecord } from "../../../../shared/domain"; -import { formatRelative, humanizeToken } from "../types"; - -export function ProfilePanel({ employee }: { employee: EmployeeRecord }) { - return ( -
-

Profile

-

Your identity in this workspace.

-
-
- -
-
{employee.displayName}
-
{employee.role}
-
-
-
- Joined - {new Date(employee.createdAt).toLocaleDateString()} -
-
- Employee ID - {employee.id} -
-
-
- ); -} - -export function NotificationsPanel({ events, employee }: { events: AuditEventRecord[]; employee: EmployeeRecord }) { - const visible = events.filter((event) => event.actorId === employee.id || event.actorType === "system").slice(0, 40); - return ( -
-

Notifications

-

Real audit events flagged for you.

- {visible.length > 0 && ( -
- {visible.map((event, index) => ( -
- {event.entityType.replaceAll("_", " ")} - {humanizeToken(event.action)} - {formatRelative(event.createdAt)} -
- ))} -
- )} -
- ); -} - -export function SecurityPanel({ events }: { events: AuditEventRecord[] }) { - const security = events.filter((event) => isSecurityEvent(event.action, event.entityType)).slice(0, 50); - return ( -
-

Security

-

Recent security-relevant audit events.

- {security.length > 0 && ( -
- {security.map((event, index) => ( -
- {event.entityType.replaceAll("_", " ")} - {humanizeToken(event.action)} - {formatRelative(event.createdAt)} -
- ))} -
- )} -
- ); -} - -function isSecurityEvent(action: string, entityType: string): boolean { - return ( - action.includes("credential") || - action.includes("login") || - entityType.includes("approval") || - entityType.includes("policy") - ); -} diff --git a/AgentCompany-New/src/renderer/src/pages/settings/CompliancePanel.tsx b/AgentCompany-New/src/renderer/src/pages/settings/CompliancePanel.tsx deleted file mode 100644 index 386c5f52..00000000 --- a/AgentCompany-New/src/renderer/src/pages/settings/CompliancePanel.tsx +++ /dev/null @@ -1,284 +0,0 @@ -import { Children, type FormEvent, type ReactNode } from "react"; -import { Button, Icon, Pill } from "../../shared/ui"; -import type { - AgentRecord, - AuditReplayRunRecord, - ChatSpaceRecord, - ChatThreadRecord, - ComplianceExportRecord, - EmployeeRecord, - FileObjectRecord, - LegalHoldRecord, - RetentionFreezeRecord, -} from "../../../../shared/domain"; -import type { SnapshotActions } from "../../app/useSnapshot"; -import { formatRelative } from "../types"; - -export function CompliancePanel({ - orgId, - employeeId, - employees, - agents, - chatSpaces, - chatThreads, - files, - exports, - legalHolds, - retentionFreezes, - auditReplayRuns, - actions, -}: { - orgId: string; - employeeId: string; - employees: EmployeeRecord[]; - agents: AgentRecord[]; - chatSpaces: ChatSpaceRecord[]; - chatThreads: ChatThreadRecord[]; - files: FileObjectRecord[]; - exports: ComplianceExportRecord[]; - legalHolds: LegalHoldRecord[]; - retentionFreezes: RetentionFreezeRecord[]; - auditReplayRuns: AuditReplayRunRecord[]; - actions: SnapshotActions; -}) { - const submitExport = (event: FormEvent) => { - event.preventDefault(); - const form = new FormData(event.currentTarget); - const [subjectType, subjectId] = splitPair(String(form.get("subject") ?? "")); - void actions.run(() => window.agentCompany.createComplianceExport({ - orgId, - subjectType: subjectType as "employee" | "agent", - subjectId, - exportType: String(form.get("exportType")) as "gdpr_export" | "gdpr_delete", - requestedByEmployeeId: employeeId, - })); - }; - - const submitHold = (event: FormEvent) => { - event.preventDefault(); - const form = new FormData(event.currentTarget); - const [entityType, entityId] = splitPair(String(form.get("entity") ?? "")); - const reason = String(form.get("reason") ?? "").trim(); - void actions.run(() => window.agentCompany.createLegalHold({ - orgId, - entityType: entityType as "organization" | "employee" | "agent" | "chat_thread" | "file_object", - entityId, - reason, - createdByEmployeeId: employeeId, - })).then((result) => { if (result) event.currentTarget.reset(); }); - }; - - const submitFreeze = (event: FormEvent) => { - event.preventDefault(); - const form = new FormData(event.currentTarget); - const [scopeType, scopeId] = splitPair(String(form.get("scope") ?? "")); - const reason = String(form.get("reason") ?? "").trim(); - void actions.run(() => window.agentCompany.createRetentionFreeze({ - orgId, - scopeType: scopeType as "organization" | "chat_space" | "file_object", - scopeId, - reason, - createdByEmployeeId: employeeId, - })).then((result) => { if (result) event.currentTarget.reset(); }); - }; - - const submitReplay = (event: FormEvent) => { - event.preventDefault(); - const form = new FormData(event.currentTarget); - void actions.run(() => window.agentCompany.createAuditReplay({ - orgId, - threadId: String(form.get("threadId") ?? ""), - createdByEmployeeId: employeeId, - })); - }; - - return ( -
-

Compliance

-

GDPR exports, legal holds, and retention freezes.

-
-
- -
- - - - - - {employees.map((employee) => )} - {agents.map((agent) => )} - -
- -
-
-
- -
- -
- - - {employees.map((employee) => )} - {agents.map((agent) => )} - {chatThreads.map((thread) => )} - {files.map((file) => )} - - -
- -
- -
- -
- -
- - - {chatSpaces.map((space) => )} - {files.map((file) => )} - - -
- -
- -
- -
- -
- - {chatThreads.map((thread) => )} - -
- -
-
-
- -
- - - {exports.map((item) => ( - - ))} - -
-
- -
- - - {auditReplayRuns.map((run) => ( - - ))} - -
- -
- - - {legalHolds.map((hold) => ( - void actions.run(() => window.agentCompany.setLegalHoldState({ orgId, legalHoldId: hold.id, active: !hold.active, employeeId }))} - /> - ))} - {retentionFreezes.map((freeze) => ( - void actions.run(() => window.agentCompany.setRetentionFreezeState({ orgId, retentionFreezeId: freeze.id, active: !freeze.active, employeeId }))} - /> - ))} - -
-
- ); -} - -function SectionTitle({ icon, title }: { icon: "approve" | "docs" | "inbox" | "list" | "pause" | "refresh" | "shield"; title: string }) { - return ( -
- -
{title}
-
- ); -} - -function LabeledSelect({ label, name, children }: { label: string; name: string; children: ReactNode }) { - return ( - - ); -} - -function LabeledInput({ label, name }: { label: string; name: string }) { - return ( - - ); -} - -function RecordList({ emptyLabel, children }: { emptyLabel: string; children: ReactNode }) { - const items = Children.toArray(children).filter(Boolean); - return items.length > 0 ?
{items}
: {emptyLabel}; -} - -function RecordRow({ - title, - meta, - active, - actionLabel, - onAction, -}: { - title: string; - meta: string; - active?: boolean; - actionLabel?: string; - onAction?: () => void; -}) { - return ( -
-
-
{title}
-
{meta}
-
- {active !== undefined && {active ? "active" : "released"}} - {onAction && actionLabel && } -
- ); -} - -function splitPair(value: string): [string, string] { - const index = value.indexOf(":"); - if (index < 1) throw new Error("Compliance selector value is invalid."); - return [value.slice(0, index), value.slice(index + 1)]; -} - -function threadTitle(threads: ChatThreadRecord[], threadId: string): string { - const thread = threads.find((item) => item.id === threadId); - if (!thread) throw new Error("Audit replay thread is missing from snapshot."); - return thread.title; -} diff --git a/AgentCompany-New/src/renderer/src/pages/settings/MembersPanel.tsx b/AgentCompany-New/src/renderer/src/pages/settings/MembersPanel.tsx deleted file mode 100644 index 3791a970..00000000 --- a/AgentCompany-New/src/renderer/src/pages/settings/MembersPanel.tsx +++ /dev/null @@ -1,161 +0,0 @@ -import { Avatar, Pill } from "../../shared/ui"; -import type { AgentRecord, EmployeeIdentityRecord, EmployeeRecord, RoleGrantRecord, ServiceIdentityRecord } from "../../../../shared/domain"; -import { money } from "../types"; - -export function MembersPanel({ - employees, - agents, - employeeIdentities, - serviceIdentities, - roleGrants, -}: { - employees: EmployeeRecord[]; - agents: AgentRecord[]; - employeeIdentities: EmployeeIdentityRecord[]; - serviceIdentities: ServiceIdentityRecord[]; - roleGrants: RoleGrantRecord[]; -}) { - const total = employees.length + agents.length; - return ( -
-

Members

-

- {total} {total === 1 ? "member" : "members"} · {employees.length} {employees.length === 1 ? "human" : "humans"} · {agents.length} {agents.length === 1 ? "agent" : "agents"} -

-
-
-
Member
Type
Department
Budget
-
- {employees.map((employee) => ( -
-
- -
-
{employee.displayName}
-
{employee.role}
-
-
-
Human
-
—
-
—
-
- ))} - {agents.map((agent, index) => ( -
-
- -
-
{agent.name}
-
{agent.role}
-
-
-
Agent
-
{agent.department}
-
{money(agent.budgetCents)}
-
- ))} -
-
Identity & Access
-
-
-
Principal
Identity source
Roles
-
- {employees.map((employee, index) => ( - - ))} - {agents.map((agent, index) => { - const serviceIdentity = serviceIdentities.find((identity) => identity.agentId === agent.id); - return ( - - ); - })} -
-
- ); -} - -function AccessRow({ - name, - detail, - source, - roles, - bot, - last, -}: { - name: string; - detail: string; - source: string; - roles: string[]; - bot: boolean; - last: boolean; -}) { - return ( -
-
- -
-
{name}
-
{detail}
-
-
-
{source}
-
- {roles.map((role) => {role})} -
-
- ); -} - -function identitySource(employeeId: string, identities: EmployeeIdentityRecord[]): string { - const identity = identities.find((item) => item.employeeId === employeeId && item.active); - if (!identity) return "Unlinked"; - if (identity.source === "sso") return "SSO"; - if (identity.source === "scim") return "SCIM"; - return "Bootstrap"; -} - -function principalRoles(principalType: RoleGrantRecord["principalType"], principalId: string, grants: RoleGrantRecord[]): string[] { - return grants - .filter((grant) => grant.principalType === principalType && grant.principalId === principalId) - .map((grant) => grant.roleKey.replaceAll("_", " ")) - .sort(); -} diff --git a/AgentCompany-New/src/renderer/src/pages/settings/ModelsPanel.tsx b/AgentCompany-New/src/renderer/src/pages/settings/ModelsPanel.tsx deleted file mode 100644 index 49bc7c31..00000000 --- a/AgentCompany-New/src/renderer/src/pages/settings/ModelsPanel.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { Pill } from "../../shared/ui"; -import type { AgentRecord, ProviderRecord } from "../../../../shared/domain"; - -export function ModelsPanel({ - providers, - agents, -}: { - providers: ProviderRecord[]; - agents: AgentRecord[]; -}) { - const rows = collectModels(providers, agents); - return ( -
-

Models

-

Models that agents reference, derived from connected providers and agent assignments.

-
-
-
Provider
Model
Agents
Source
-
- {rows.map((row, index) => ( -
-
-
{row.providerLabel}
-
{row.authType}
-
-
{row.model}
-
{row.agentCount}
-
{row.source === "default" ? Provider default : Agent assignment}
-
- ))} -
-
- ); -} - -interface ModelRow { - providerId: string; - providerLabel: string; - authType: string; - model: string; - agentCount: number; - source: "default" | "agent"; -} - -function collectModels(providers: ProviderRecord[], agents: AgentRecord[]): ModelRow[] { - const map = new Map(); - for (const provider of providers) { - if (!provider.enabled || !provider.model) continue; - const key = `${provider.id}::${provider.model}`; - map.set(key, { - providerId: provider.id, - providerLabel: provider.label, - authType: humanAuth(provider.authType), - model: provider.model, - agentCount: 0, - source: "default", - }); - } - for (const agent of agents) { - if (!agent.modelProviderId) continue; - const provider = providers.find((item) => item.id === agent.modelProviderId); - if (!provider) continue; - const effectiveModel = agent.modelId ?? provider.model; - if (!effectiveModel) continue; - const key = `${provider.id}::${effectiveModel}`; - const existing = map.get(key); - if (existing) { - existing.agentCount += 1; - continue; - } - map.set(key, { - providerId: provider.id, - providerLabel: provider.label, - authType: humanAuth(provider.authType), - model: effectiveModel, - agentCount: 1, - source: "agent", - }); - } - return [...map.values()].sort((a, b) => a.providerLabel.localeCompare(b.providerLabel)); -} - -function humanAuth(authType: string): string { - if (authType === "api_key") return "API key"; - if (authType === "oauth_pkce") return "OAuth"; - if (authType === "account_subscription") return "Account"; - if (authType === "cloud") return "Cloud"; - return authType; -} diff --git a/AgentCompany-New/src/renderer/src/pages/settings/OperationsPanel.tsx b/AgentCompany-New/src/renderer/src/pages/settings/OperationsPanel.tsx deleted file mode 100644 index 954e605d..00000000 --- a/AgentCompany-New/src/renderer/src/pages/settings/OperationsPanel.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import { Button, Pill } from "../../shared/ui"; -import type { - ChaosDrillRecord, - ChaosDrillRunRecord, - FeatureFlagRecord, - OperationalEventRecord, - SloTargetRecord, -} from "../../../../shared/domain"; -import type { SnapshotActions } from "../../app/useSnapshot"; -import { formatRelative } from "../types"; - -export function OperationsPanel({ - orgId, - featureFlags, - sloTargets, - operationalEvents, - chaosDrills, - chaosDrillRuns, - actions, -}: { - orgId: string; - featureFlags: FeatureFlagRecord[]; - sloTargets: SloTargetRecord[]; - operationalEvents: OperationalEventRecord[]; - chaosDrills: ChaosDrillRecord[]; - chaosDrillRuns: ChaosDrillRunRecord[]; - actions: SnapshotActions; -}) { - return ( -
-

Operations

-

{featureFlags.length} flags / {sloTargets.length} SLOs / {chaosDrills.length} drills.

- - - -
- ); -} - -function FeatureFlagsTable({ - orgId, - flags, - actions, -}: { - orgId: string; - flags: FeatureFlagRecord[]; - actions: SnapshotActions; -}) { - return ( - <> -
Feature flags
-
-
-
Flag
State
Updated
-
- {flags.map((flag, index) => ( -
-
-
{flag.label}
-
{flag.key}
-
- -
{formatRelative(flag.updatedAt)}
-
- ))} -
- - ); -} - -function SloTable({ - targets, - events, -}: { - targets: SloTargetRecord[]; - events: OperationalEventRecord[]; -}) { - return ( - <> -
SLO targets
-
-
-
Signal
Target
Current
Samples
-
- {targets.map((target, index) => { - const samples = events.filter((event) => event.signalKey === target.signalKey && withinWindow(event.createdAt, target.windowMinutes)); - const successes = samples.filter((event) => event.success).length; - return ( -
-
-
{target.label}
-
{target.signalKey}
-
-
{formatBasisPoints(target.targetBasisPoints)}
-
0 && currentBasisPoints(successes, samples.length) < target.targetBasisPoints ? "amber" : undefined}>{formatCurrent(successes, samples.length)}
-
{samples.length}
-
- ); - })} -
- - ); -} - -function ChaosDrillsTable({ - orgId, - drills, - runs, - actions, -}: { - orgId: string; - drills: ChaosDrillRecord[]; - runs: ChaosDrillRunRecord[]; - actions: SnapshotActions; -}) { - return ( - <> -
Chaos drills
-
-
-
Drill
Last run
Result
Action
-
- {drills.map((drill, index) => { - const run = runs.find((item) => item.drillId === drill.id) ?? null; - return ( -
-
-
{drill.label}
-
{drill.scenarioKey}
-
-
{run ? formatRelative(run.startedAt) : "0 runs"}
-
{run?.state ?? "Ready"}
- -
- ); - })} -
- - ); -} - -function withinWindow(iso: string, windowMinutes: number): boolean { - const createdAt = new Date(iso).getTime(); - if (Number.isNaN(createdAt)) return false; - return Date.now() - createdAt <= windowMinutes * 60_000; -} - -function currentBasisPoints(successes: number, samples: number): number { - if (samples === 0) return 0; - return Math.round((successes / samples) * 10000); -} - -function formatCurrent(successes: number, samples: number): string { - if (samples === 0) return "0 / 0"; - return formatBasisPoints(currentBasisPoints(successes, samples)); -} - -function formatBasisPoints(value: number): string { - return `${(value / 100).toFixed(value % 100 === 0 ? 0 : 2)}%`; -} diff --git a/AgentCompany-New/src/renderer/src/pages/settings/PoliciesPanel.tsx b/AgentCompany-New/src/renderer/src/pages/settings/PoliciesPanel.tsx deleted file mode 100644 index 8837fb64..00000000 --- a/AgentCompany-New/src/renderer/src/pages/settings/PoliciesPanel.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import { Pill } from "../../shared/ui"; -import type { - AgentRecord, - ApprovalAssignmentRecord, - ApprovalRoutingRuleRecord, - EmployeeRecord, - HiringPolicyRecord, - OrgDepartmentRecord, - PolicyRuleRecord, -} from "../../../../shared/domain"; -import { money } from "../types"; - -export function PoliciesPanel({ - policies, - agents, - employees, - orgDepartments, - hiringPolicies, - approvalRoutingRules, - approvalAssignments, -}: { - policies: PolicyRuleRecord[]; - agents: AgentRecord[]; - employees: EmployeeRecord[]; - orgDepartments: OrgDepartmentRecord[]; - hiringPolicies: HiringPolicyRecord[]; - approvalRoutingRules: ApprovalRoutingRuleRecord[]; - approvalAssignments: ApprovalAssignmentRecord[]; -}) { - return ( -
-

Policies

-

Policy rules evaluated for actions across the workspace.

-
Rules
-
-
-
Action
Effect
Risk ceiling
Subject
-
- {policies.map((policy, index) => ( -
-
{policy.action.replaceAll("_", " ")}
-
{policy.effect.replaceAll("_", " ")}
-
- - {policy.riskCeiling} - -
-
{policy.subjectType}
-
- ))} -
- -
Hiring policies
-
-
-
Department
Headcount
Budget cap
Review
-
- {hiringPolicies.map((policy, index) => ( - item.name === policy.departmentName) ?? null} - agents={agents} - last={index === hiringPolicies.length - 1} - /> - ))} -
- -
Review routing
-
-
-
Kind
Min risk
Route
Assigned
-
- {approvalRoutingRules.map((rule, index) => ( - assignment.routingRuleId === rule.id).length} - last={index === approvalRoutingRules.length - 1} - /> - ))} -
-
- ); -} - -function HiringPolicyRow({ - policy, - department, - agents, - last, -}: { - policy: HiringPolicyRecord; - department: OrgDepartmentRecord | null; - agents: AgentRecord[]; - last: boolean; -}) { - if (!department) throw new Error(`Department governance is missing for ${policy.departmentName}.`); - const roster = agents.filter((agent) => agent.department === policy.departmentName && agent.lifecycle === "active"); - const budgetCents = roster.reduce((sum, agent) => sum + agent.budgetCents, 0); - return ( -
-
{policy.departmentName}
-
{roster.length} / {department.headcountLimit}
-
{money(budgetCents)} / {money(policy.monthlyBudgetLimitCents)}
-
{money(policy.requireApprovalAboveCents)}
-
- ); -} - -function RoutingRuleRow({ - rule, - employees, - agents, - assignmentCount, - last, -}: { - rule: ApprovalRoutingRuleRecord; - employees: EmployeeRecord[]; - agents: AgentRecord[]; - assignmentCount: number; - last: boolean; -}) { - return ( -
-
{rule.kind}
-
{rule.minimumRisk}
-
- {routeLabel(rule, employees, agents)} -
-
{assignmentCount}
-
- ); -} - -function routeLabel(rule: ApprovalRoutingRuleRecord, employees: EmployeeRecord[], agents: AgentRecord[]): string { - if (rule.routeToType === "board") return "Board"; - if (rule.routeToType === "employee") { - const employee = employees.find((item) => item.id === rule.routeToId); - if (!employee) throw new Error("Approval route employee is missing."); - return employee.displayName; - } - const agent = agents.find((item) => item.id === rule.routeToId); - if (!agent) throw new Error("Approval route agent is missing."); - return agent.name; -} diff --git a/AgentCompany-New/src/renderer/src/pages/settings/SdlcPanel.tsx b/AgentCompany-New/src/renderer/src/pages/settings/SdlcPanel.tsx deleted file mode 100644 index 0e623f9a..00000000 --- a/AgentCompany-New/src/renderer/src/pages/settings/SdlcPanel.tsx +++ /dev/null @@ -1,240 +0,0 @@ -import { Button, Pill } from "../../shared/ui"; -import type { - EvalCaseRecord, - EvalRunRecord, - EvalSuiteRecord, - MigrationReportRecord, - PolicyRuleRecord, - PrBotGateRecord, - SdlcReviewAgentRecord, - SdlcReviewRunRecord, - SdlcReviewTargetType, -} from "../../../../shared/domain"; -import { SDLC_REVIEW_AGENT_KEY, SDLC_REVIEW_TARGET_TYPE } from "../../../../shared/domain"; -import type { SnapshotActions } from "../../app/useSnapshot"; -import { formatRelative } from "../types"; - -export function SdlcPanel({ - orgId, - reviewAgents, - reviewRuns, - evalSuites, - evalCases, - evalRuns, - prBotGates, - migrationReports, - policies, - actions, -}: { - orgId: string; - reviewAgents: SdlcReviewAgentRecord[]; - reviewRuns: SdlcReviewRunRecord[]; - evalSuites: EvalSuiteRecord[]; - evalCases: EvalCaseRecord[]; - evalRuns: EvalRunRecord[]; - prBotGates: PrBotGateRecord[]; - migrationReports: MigrationReportRecord[]; - policies: PolicyRuleRecord[]; - actions: SnapshotActions; -}) { - return ( -
-

SDLC

-

{reviewAgents.length} agents / {evalSuites.length} suites / {prBotGates.length} gates.

- - - -
- ); -} - -function ReviewAgentsTable({ - orgId, - agents, - runs, - migrationReports, - policies, - actions, -}: { - orgId: string; - agents: SdlcReviewAgentRecord[]; - runs: SdlcReviewRunRecord[]; - migrationReports: MigrationReportRecord[]; - policies: PolicyRuleRecord[]; - actions: SnapshotActions; -}) { - return ( - <> -
Review agents
-
-
-
Agent
Target
Last run
Action
-
- {agents.map((agent, index) => { - const target = resolveReviewTarget(agent, orgId, migrationReports, policies); - const run = runs.find((item) => item.reviewAgentId === agent.id) ?? null; - return ( -
-
-
{agent.label}
-
{agent.specialty}
-
-
{target.label}
-
{run ? `${run.state} · ${formatRelative(run.createdAt)}` : "0 runs"}
- -
- ); - })} -
- - ); -} - -function EvalSuitesTable({ - orgId, - suites, - cases, - runs, - actions, -}: { - orgId: string; - suites: EvalSuiteRecord[]; - cases: EvalCaseRecord[]; - runs: EvalRunRecord[]; - actions: SnapshotActions; -}) { - return ( - <> -
Eval suites
-
-
-
Suite
Cases
Last run
Action
-
- {suites.map((suite, index) => { - const run = runs.find((item) => item.suiteId === suite.id) ?? null; - const caseCount = cases.filter((item) => item.suiteId === suite.id).length; - return ( -
-
-
{suite.label}
-
{suite.scope}
-
-
{caseCount}
-
{run ? `${run.state} · ${formatRelative(run.createdAt)}` : "0 runs"}
- -
- ); - })} -
- - ); -} - -function PrBotGatesTable({ - orgId, - gates, - actions, -}: { - orgId: string; - gates: PrBotGateRecord[]; - actions: SnapshotActions; -}) { - return ( - <> -
PR bot gates
-
-
-
Gate
State
Updated
-
- {gates.map((gate, index) => ( -
-
-
{gate.label}
-
{gate.gateType}
-
- -
{formatRelative(gate.updatedAt)}
-
- ))} -
- - ); -} - -function resolveReviewTarget( - agent: SdlcReviewAgentRecord, - orgId: string, - migrationReports: MigrationReportRecord[], - policies: PolicyRuleRecord[], -): { type: SdlcReviewTargetType; ref: string; label: string; ready: boolean } { - if (agent.key === SDLC_REVIEW_AGENT_KEY.MIGRATION_REVIEWER) { - const report = migrationReports[0] ?? null; - return { - type: SDLC_REVIEW_TARGET_TYPE.MIGRATION, - ref: report?.id ?? orgId, - label: report ? "migration" : "migration report", - ready: Boolean(report), - }; - } - if (agent.key === SDLC_REVIEW_AGENT_KEY.POLICY_REVIEWER) { - const policy = policies[0] ?? null; - return { - type: SDLC_REVIEW_TARGET_TYPE.POLICY, - ref: policy?.id ?? orgId, - label: policy ? "policy" : "policy rule", - ready: Boolean(policy), - }; - } - return { type: SDLC_REVIEW_TARGET_TYPE.WORKSPACE, ref: orgId, label: "workspace", ready: true }; -} diff --git a/AgentCompany-New/src/renderer/src/shared/Shell.tsx b/AgentCompany-New/src/renderer/src/shared/Shell.tsx index fd94d674..417a9128 100644 --- a/AgentCompany-New/src/renderer/src/shared/Shell.tsx +++ b/AgentCompany-New/src/renderer/src/shared/Shell.tsx @@ -14,7 +14,6 @@ export type RouteId = | "files" | "docs" | "knowledge" - | "automation" | "inbox" | "runs" | "meetings" @@ -35,18 +34,17 @@ const FLUSH_ROUTES = new Set(["chat", "settings", "files", "social", "r const primaryNav: NavSpec[] = [ { id: "home", label: "Home", icon: "home" }, - { id: "team", label: "Team", icon: "team" }, + { id: "inbox", label: "Inbox", icon: "approve" }, { id: "work", label: "Work", icon: "work" }, + { id: "approvals", label: "Approvals", icon: "approve" }, + { id: "team", label: "Team", icon: "team" }, { id: "chat", label: "Messages", icon: "chat" }, { id: "files", label: "Files", icon: "docs" }, - { id: "inbox", label: "Inbox", icon: "approve" }, { id: "docs", label: "Documents", icon: "file" }, { id: "knowledge", label: "Knowledge", icon: "sparkle" }, { id: "runs", label: "Runs", icon: "send" }, - { id: "automation", label: "Automation", icon: "bot" }, { id: "meetings", label: "Meetings", icon: "team" }, { id: "sprints", label: "Sprints", icon: "work" }, - { id: "approvals", label: "Approvals", icon: "approve" }, { id: "social", label: "Social", icon: "social" }, ]; @@ -134,7 +132,6 @@ export function Shell({
{employee.displayName} -
diff --git a/AgentCompany-New/tests/e2e/desktop.spec.ts b/AgentCompany-New/tests/e2e/desktop.spec.ts index 36125948..762be85c 100644 --- a/AgentCompany-New/tests/e2e/desktop.spec.ts +++ b/AgentCompany-New/tests/e2e/desktop.spec.ts @@ -76,29 +76,6 @@ test("onboards, hires, and creates work in the Electron shell", async () => { expect(hiredAgent.workspaceId).toBe(hiredAgent.primaryWorkspaceId); expect(hiredAgent.heartbeatEnabled).toBe(true); expect(hiredAgent.heartbeatIntervalSec).toBe(120); - await page.getByRole("button", { name: "Settings" }).click(); - await page.getByRole("button", { name: "Members" }).click(); - await expect(page.getByText("Identity & Access")).toBeVisible(); - await expect(page.getByText("org admin").first()).toBeVisible(); - await expect(page.getByText("scoped agent").first()).toBeVisible(); - await page.getByRole("button", { name: "Policies" }).click(); - await expect(page.getByText("Hiring policies")).toBeVisible(); - await expect(page.getByText("Review routing")).toBeVisible(); - await page.getByRole("button", { name: "Operations", exact: true }).click(); - await expect(page.getByText("Feature flags")).toBeVisible(); - await expect(page.getByText("SLO targets")).toBeVisible(); - await expect(page.getByText("Chaos drills").first()).toBeVisible(); - await page.getByRole("button", { name: "Run" }).first().click(); - await expect(page.getByText("passed").first()).toBeVisible(); - await page.getByRole("button", { name: "SDLC", exact: true }).click(); - await expect(page.getByText("Review agents", { exact: true })).toBeVisible(); - await expect(page.getByText("Eval suites", { exact: true })).toBeVisible(); - await expect(page.getByText("PR bot gates", { exact: true })).toBeVisible(); - await page.getByRole("button", { name: "Run" }).first().click(); - await expect(page.getByText("passed").first()).toBeVisible(); - await page.getByRole("button", { name: "Run" }).nth(3).click(); - await expect(page.getByText(/passed ·/).first()).toBeVisible(); - await page.getByRole("button", { name: "Work", exact: true }).click(); await page.getByRole("button", { name: "New task" }).click(); await page.getByLabel("Title").fill("Production deploy"); @@ -177,14 +154,6 @@ test("onboards, hires, and creates work in the Electron shell", async () => { await expect(page.getByText("Never run").first()).toBeVisible(); await page.getByRole("button", { name: "Run tests" }).click(); await expect(page.getByText("1/1 passed").first()).toBeVisible(); - await page.getByRole("button", { name: "Compliance" }).click(); - await page.getByRole("button", { name: "Create" }).click(); - await expect(page.getByText("gdpr export · employee").first()).toBeVisible(); - await expect(page.getByText("Thread replay")).toBeVisible(); - await page.getByRole("button", { name: "Run" }).click(); - await expect(page.getByText("Replay runs", { exact: true })).toBeVisible(); - await expect(page.getByText(/\d+ events ·/).first()).toBeVisible(); - const legacyPath = createLegacyDatabase(); await page.getByRole("button", { name: "Migration" }).click(); await page.getByRole("button", { name: "Report" }).click(); @@ -238,7 +207,7 @@ async function verifyPagesDoNotOverflow(page: Page): Promise { await page.getByRole("button", { name: nav, exact: true }).click(); await expectPageToFit(page); } - for (const tab of ["Workspace", "Members", "Billing", "Models", "Providers", "Skills", "Policies", "Operations", "SDLC", "Compliance", "Migration", "Profile", "Notifications", "Security"]) { + for (const tab of ["Workspace", "Billing", "Providers", "Skills", "Migration"]) { 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 31b696b0..e6b1872e 100644 --- a/AgentCompany-New/tests/e2e/screenshots.spec.ts +++ b/AgentCompany-New/tests/e2e/screenshots.spec.ts @@ -49,7 +49,7 @@ test("captures every page for visual review", async () => { await go(page, "Social", outDir, "16-social"); await go(page, "Settings", outDir, "17-settings"); - for (const tab of ["Workspace", "Members", "Billing", "Models", "Skills", "Policies", "Compliance", "Migration", "Profile", "Notifications", "Security"]) { + for (const tab of ["Workspace", "Billing", "Skills", "Migration"]) { await page.getByRole("button", { name: "Settings" }).click(); await page.getByRole("button", { name: tab }).first().click(); await snap(page, outDir, `settings-${tab.toLowerCase()}`);