Merge pull request #1655 from paperclipai/pr/pap-795-company-portability

feat(portability): improve company import and export flow
This commit is contained in:
Dotta 2026-03-23 19:45:05 -05:00 committed by GitHub
commit eeb7e1a91a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 5238 additions and 271 deletions

View file

@ -160,6 +160,7 @@ export const FRONTMATTER_FIELD_LABELS: Record<string, string> = {
priority: "Priority",
assignee: "Assignee",
project: "Project",
recurring: "Recurring",
targetDate: "Target date",
};

View file

@ -6,9 +6,11 @@ import { useCompany } from "../context/CompanyContext";
import { useDialog } from "../context/DialogContext";
import { useSidebar } from "../context/SidebarContext";
import { agentsApi } from "../api/agents";
import { authApi } from "../api/auth";
import { heartbeatsApi } from "../api/heartbeats";
import { queryKeys } from "../lib/queryKeys";
import { cn, agentRouteRef, agentUrl } from "../lib/utils";
import { useAgentOrder } from "../hooks/useAgentOrder";
import { AgentIcon } from "./AgentIconPicker";
import { BudgetSidebarMarker } from "./BudgetSidebarMarker";
import {
@ -17,28 +19,6 @@ import {
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import type { Agent } from "@paperclipai/shared";
/** BFS sort: roots first (no reportsTo), then their direct reports, etc. */
function sortByHierarchy(agents: Agent[]): Agent[] {
const byId = new Map(agents.map((a) => [a.id, a]));
const childrenOf = new Map<string | null, Agent[]>();
for (const a of agents) {
const parent = a.reportsTo && byId.has(a.reportsTo) ? a.reportsTo : null;
const list = childrenOf.get(parent) ?? [];
list.push(a);
childrenOf.set(parent, list);
}
const sorted: Agent[] = [];
const queue = childrenOf.get(null) ?? [];
while (queue.length > 0) {
const agent = queue.shift()!;
sorted.push(agent);
const children = childrenOf.get(agent.id);
if (children) queue.push(...children);
}
return sorted;
}
export function SidebarAgents() {
const [open, setOpen] = useState(true);
const { selectedCompanyId } = useCompany();
@ -51,6 +31,10 @@ export function SidebarAgents() {
queryFn: () => agentsApi.list(selectedCompanyId!),
enabled: !!selectedCompanyId,
});
const { data: session } = useQuery({
queryKey: queryKeys.auth.session,
queryFn: () => authApi.getSession(),
});
const { data: liveRuns } = useQuery({
queryKey: queryKeys.liveRuns(selectedCompanyId!),
@ -71,8 +55,14 @@ export function SidebarAgents() {
const filtered = (agents ?? []).filter(
(a: Agent) => a.status !== "terminated"
);
return sortByHierarchy(filtered);
return filtered;
}, [agents]);
const currentUserId = session?.user?.id ?? session?.session?.userId ?? null;
const { orderedAgents } = useAgentOrder({
agents: visibleAgents,
companyId: selectedCompanyId,
userId: currentUserId,
});
const agentMatch = location.pathname.match(/^\/(?:[^/]+\/)?agents\/([^/]+)(?:\/([^/]+))?/);
const activeAgentId = agentMatch?.[1] ?? null;
@ -109,7 +99,7 @@ export function SidebarAgents() {
<CollapsibleContent>
<div className="flex flex-col gap-0.5 mt-0.5">
{visibleAgents.map((agent: Agent) => {
{orderedAgents.map((agent: Agent) => {
const runCount = liveCountByAgent.get(agent.id) ?? 0;
return (
<NavLink