Ship Composio as a built-in integration and trim Settings

- Resolve the API key from cred.txt at the project root in development
  and the packaged resources directory in production; the file is the
  source of truth, so a rotated key replaces the stored one. Fixes the
  401 packaged builds hit when a stale database key won.
- Remove the user-facing Composio key editor; the key ships with the
  product and is not configurable.
- Remove the Migration console from Settings.
- Remove the Connectors and Home refresh buttons; connector statuses
  sync when the page opens.
This commit is contained in:
ZacharyZhang-NY committed 2026-07-07 01:20:21 -04:00
1 parent 947cd8605b
commit 256e0680c6
16 files changed
+53 -396

No files matched your search

+7 -11
View File
@@ -66,6 +66,12 @@ export function ConnectorsPage({ snapshot, actions }: PageProps) {
if (pollTimer.current) clearTimeout(pollTimer.current);
}, []);
// Account statuses sync from Composio when the page opens; a failure here
// is already expressed by the catalog error state.
useEffect(() => {
void window.agentCompany.refreshComposioAccounts({ orgId });
}, [orgId]);
const refreshAccounts = () =>
actions.run(() => window.agentCompany.refreshComposioAccounts({ orgId }));
@@ -104,17 +110,7 @@ export function ConnectorsPage({ snapshot, actions }: PageProps) {
return (
<div style={{ maxWidth: 1060 }}>
<div className="row" style={{ marginBottom: 18 }}>
<div>
<h2 style={{ margin: 0, fontSize: 16, fontWeight: 600 }}>Connectors</h2>
<div style={{ fontSize: 12.5, color: "var(--text-3)", marginTop: 2 }}>
Company connections your agents can be granted to use.
</div>
</div>
<div style={{ marginLeft: "auto" }}>
<Button size="small" onClick={() => void refreshAccounts()}>Refresh</Button>
</div>
</div>
<h2 style={{ margin: "0 0 18px", fontSize: 16, fontWeight: 600 }}>Connectors</h2>
<ConnectedList accounts={accounts} grants={grants} agents={agents} onGrant={grant} onRevoke={revoke} />
-1
View File
@@ -49,7 +49,6 @@ export function HomePage({ snapshot, actions, navigate }: PageProps) {
return (
<div className="page-wide" style={{ maxWidth: 1240 }}>
<TopbarActions>
<Button tone="ghost" onClick={() => void actions.reload()} title="Refresh"><Icon name="refresh" size={16} /></Button>
<Button tone="primary" onClick={() => setNewTaskOpen(true)}><Icon name="plus" size={16} /> New task</Button>
</TopbarActions>
<h1 className="page-h1">{greeting}, {firstName}</h1>
-7
View File
@@ -43,8 +43,6 @@ export function SettingsPage({ snapshot, actions }: PageProps) {
const workspaces = useMemo(() => snapshot.workspaces.filter((item) => item.orgId === orgId), [snapshot.workspaces, orgId]);
const providerRequests = useMemo(() => snapshot.providerRequests.filter((item) => item.orgId === orgId), [snapshot.providerRequests, orgId]);
const hiringPolicies = useMemo(() => snapshot.hiringPolicies.filter((item) => item.orgId === orgId), [snapshot.hiringPolicies, 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 (
<div className="split-main">
@@ -83,17 +81,12 @@ export function SettingsPage({ snapshot, actions }: PageProps) {
{tab === "advanced" && (
<AdvancedPanel
orgId={orgId}
employeeId={employee.id}
agents={agents}
providers={providers}
catalog={snapshot.providerCatalog}
workspaces={workspaces}
providerRequests={providerRequests}
hiringPolicies={hiringPolicies}
composioSettings={snapshot.composioSettings}
migrationReports={migrationReports}
rollbackSnapshots={rollbackSnapshots}
reconciliationItems={snapshot.migrationReconciliationItems.filter((item) => item.orgId === orgId)}
actions={actions}
/>
)}
@@ -1,49 +1,33 @@
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);
@@ -51,11 +35,8 @@ export function AdvancedPanel({
<div className="page">
<h1 className="page-h1">Advanced</h1>
<div className="section-label" style={{ margin: "16px 0 10px" }}>Composio</div>
<ComposioPanel orgId={orgId} settings={composioSettings} actions={actions} />
{connected.length > 0 && (
<div style={{ marginTop: 28 }}>
<div style={{ marginTop: 16 }}>
<ProviderRequestPanel
orgId={orgId}
providers={connected}
@@ -70,17 +51,6 @@ export function AdvancedPanel({
<div style={{ marginTop: 28 }}>
<BillingPanel agents={agents} providers={providers} providerRequests={providerRequests} hiringPolicies={hiringPolicies} />
</div>
<div style={{ marginTop: 28 }}>
<MigrationPanel
orgId={orgId}
employeeId={employeeId}
reports={migrationReports}
rollbackSnapshots={rollbackSnapshots}
reconciliationItems={reconciliationItems}
actions={actions}
/>
</div>
</div>
);
}
@@ -1,52 +0,0 @@
import { useMemo, useState, type FormEvent } from "react";
import { Button } from "../../shared/ui";
import type { SnapshotActions } from "../../app/useSnapshot";
import type { ComposioSettingRecord } from "../../../../shared/snapshot-domain";
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,
actions,
}: {
orgId: string;
settings: ComposioSettingRecord[];
actions: SnapshotActions;
}) {
const keyConfigured = useMemo(() => hasSavedKey(settings, orgId), [settings, orgId]);
const [apiKey, setApiKey] = useState("");
const handleSave = (event: FormEvent) => {
event.preventDefault();
const key = apiKey.trim();
if (!key) return;
void actions.run(() => window.agentCompany.saveComposioSettings({
orgId,
apiKey: key,
toolkits: [],
})).then(() => setApiKey(""));
};
return (
<form onSubmit={handleSave} className="row" style={{ gap: 8, maxWidth: 480 }}>
<input
type="password"
className="input"
aria-label="Composio API key"
value={apiKey}
onChange={(event) => setApiKey(event.target.value)}
placeholder={keyConfigured ? "••••••••" : "ak_..."}
autoComplete="off"
style={{ flex: 1 }}
/>
<Button tone="primary" type="submit" disabled={!apiKey.trim()}>Save</Button>
</form>
);
}
@@ -1,228 +0,0 @@
import { useState, type FormEvent } from "react";
import { Button, Field, Icon, Pill } from "../../shared/ui";
import { Dialog } from "../../shared/Dialog";
import type { MigrationReconciliationItemRecord, MigrationReportRecord, RollbackSnapshotRecord } from "../../../../shared/domain";
import type { SnapshotActions } from "../../app/useSnapshot";
import { formatRelative } from "../types";
interface ParsedReport {
tableCounts: Record<string, number>;
tableChecksums: Record<string, string>;
idMaps: Record<string, string[]>;
failures: Array<{ table: string; reason: string }>;
rollbackPack?: { sourceDatabasePath: string; capturedAt: string; restoreStrategy: string };
}
export function MigrationPanel({
orgId,
employeeId,
reports,
rollbackSnapshots,
reconciliationItems,
actions,
}: {
orgId: string;
employeeId: string;
reports: MigrationReportRecord[];
rollbackSnapshots: RollbackSnapshotRecord[];
reconciliationItems: MigrationReconciliationItemRecord[];
actions: SnapshotActions;
}) {
const [open, setOpen] = useState(false);
const submit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
const form = new FormData(event.currentTarget);
void actions.run(() => window.agentCompany.createMigrationReport({
orgId,
sourceDatabasePath: String(form.get("sourceDatabasePath") ?? "").trim(),
})).then((result) => { if (result) setOpen(false); });
};
return (
<div className="reveal-row">
<div className="row" style={{ marginBottom: 10 }}>
<div className="section-label">Migration</div>
<div className="reveal" style={{ marginLeft: "auto" }}>
<Button tone="primary" onClick={() => setOpen(true)}><Icon name="plus" size={16} /> Report</Button>
</div>
</div>
{reports.length > 0 && (
<div className="card" style={{ padding: 0, overflow: "hidden" }}>
{reports.map((report, index) => (
<ReportRow
key={report.id}
orgId={orgId}
employeeId={employeeId}
report={report}
actions={actions}
last={index === reports.length - 1}
/>
))}
</div>
)}
<section className="card" style={{ marginTop: 16, padding: 0, overflow: "hidden" }}>
<div className="row" style={{ padding: "14px 16px", borderBottom: "1px solid var(--border-soft)" }}>
<Icon name="refresh" size={14} color="var(--text-3)" />
<div style={{ fontSize: 14, fontWeight: 600 }}>Rollback snapshots</div>
</div>
{rollbackSnapshots.length > 0 ? rollbackSnapshots.map((snapshot, index) => (
<RollbackRow
key={snapshot.id}
orgId={orgId}
employeeId={employeeId}
snapshot={snapshot}
actions={actions}
last={index === rollbackSnapshots.length - 1}
/>
)) : <div style={{ padding: 16 }}><Pill>0 rollback snapshots</Pill></div>}
</section>
<section className="card" style={{ marginTop: 16, padding: 0, overflow: "hidden" }}>
<div className="row" style={{ padding: "14px 16px", borderBottom: "1px solid var(--border-soft)" }}>
<Icon name="list" size={14} color="var(--text-3)" />
<div style={{ fontSize: 14, fontWeight: 600 }}>Reconciliation</div>
</div>
{reconciliationItems.length > 0 ? reconciliationItems.map((item, index) => (
<ReconciliationRow
key={item.id}
orgId={orgId}
employeeId={employeeId}
item={item}
actions={actions}
last={index === reconciliationItems.length - 1}
/>
)) : <div style={{ padding: 16 }}><Pill>0 reconciliation items</Pill></div>}
</section>
<Dialog title="Migration report" open={open} submitLabel="Create" onClose={() => setOpen(false)} onSubmit={submit}>
<Field label="Source database path"><input className="input" name="sourceDatabasePath" required /></Field>
</Dialog>
</div>
);
}
function ReportRow({
orgId,
employeeId,
report,
actions,
last,
}: {
orgId: string;
employeeId: string;
report: MigrationReportRecord;
actions: SnapshotActions;
last: boolean;
}) {
const body = parseReport(report.reportJson);
const idCount = Object.values(body.idMaps).reduce((sum, ids) => sum + ids.length, 0);
return (
<div
className="table-row"
style={{ gridTemplateColumns: "minmax(0, 1.35fr) minmax(0, 1fr) 120px 88px 96px", borderBottom: last ? "none" : "1px solid var(--border-soft)" }}
>
<div style={{ minWidth: 0 }}>
<div style={{ fontWeight: 540, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }} title={report.sourceRef}>{report.sourceRef}</div>
<div style={{ fontSize: 12, color: "var(--text-3)" }}>{formatRelative(report.createdAt)}</div>
</div>
<div className="row" style={{ flexWrap: "wrap", gap: 4 }}>
{Object.entries(body.tableCounts).map(([table, count]) => (
<Pill key={table}>{table}: {count}</Pill>
))}
</div>
<Pill>{Object.keys(body.tableChecksums).length} checksums</Pill>
<div className="row" style={{ flexWrap: "wrap", gap: 4 }}>
<Pill>{idCount} ids</Pill>
{body.failures.length > 0 ? <Pill tone="amber">{body.failures.length} failures</Pill> : null}
</div>
<Button
size="small"
disabled={!body.rollbackPack}
onClick={() => void actions.run(() => window.agentCompany.createRollbackSnapshot({ orgId, migrationReportId: report.id, createdByEmployeeId: employeeId }))}
>
Snapshot
</Button>
</div>
);
}
function RollbackRow({
orgId,
employeeId,
snapshot,
actions,
last,
}: {
orgId: string;
employeeId: string;
snapshot: RollbackSnapshotRecord;
actions: SnapshotActions;
last: boolean;
}) {
const manifest = JSON.parse(snapshot.workspaceManifestJson) as { workspaces: unknown[]; files: unknown[] };
return (
<div
className="table-row"
style={{ gridTemplateColumns: "minmax(0, 1.2fr) minmax(0, .8fr) 96px 96px 72px", borderBottom: last ? "none" : "1px solid var(--border-soft)" }}
>
<div style={{ minWidth: 0 }}>
<div style={{ fontWeight: 540, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }} title={snapshot.sourceDatabasePath}>{snapshot.sourceDatabasePath}</div>
<div style={{ fontSize: 12, color: "var(--text-3)" }}>{formatRelative(snapshot.createdAt)}</div>
</div>
<Pill>{snapshot.sourceDatabaseSha256.slice(0, 12)}</Pill>
<Pill>{manifest.workspaces.length} workspaces</Pill>
<Pill>{manifest.files.length} files</Pill>
<Button
size="small"
disabled={snapshot.active}
onClick={() => void actions.run(() => window.agentCompany.setRollbackSnapshotActive({ orgId, snapshotId: snapshot.id, active: true, employeeId }))}
>
{snapshot.active ? "active" : "Set"}
</Button>
</div>
);
}
function ReconciliationRow({
orgId,
employeeId,
item,
actions,
last,
}: {
orgId: string;
employeeId: string;
item: MigrationReconciliationItemRecord;
actions: SnapshotActions;
last: boolean;
}) {
const resolved = item.state === "resolved";
return (
<div
className="table-row"
style={{ gridTemplateColumns: "minmax(0, 1fr) minmax(0, 1fr) 86px 112px 84px", borderBottom: last ? "none" : "1px solid var(--border-soft)" }}
>
<div style={{ minWidth: 0 }}>
<div style={{ fontWeight: 540, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{item.targetType} · {item.targetId}</div>
<div style={{ fontSize: 12, color: "var(--text-3)" }}>{formatRelative(item.createdAt)}</div>
</div>
<div style={{ fontSize: 12, color: "var(--text-2)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{item.reason}</div>
<Pill tone={resolved ? "green" : "amber"}>{item.state}</Pill>
<Pill>{item.sourceEventId.slice(0, 12)}</Pill>
<Button
size="small"
disabled={resolved}
onClick={() => void actions.run(() => window.agentCompany.resolveMigrationReconciliation({ orgId, reconciliationItemId: item.id, employeeId }))}
>
{resolved ? "Resolved" : "Resolve"}
</Button>
</div>
);
}
function parseReport(reportJson: string): ParsedReport {
const parsed = JSON.parse(reportJson) as ParsedReport;
if (!parsed.tableCounts || !parsed.tableChecksums || !parsed.idMaps || !Array.isArray(parsed.failures)) {
throw new Error("Migration report is invalid.");
}
return parsed;
}