feat(05-03): policy-level BudgetSummary, incident-based BudgetAlertBanner, AgentEditor policy wiring

- BudgetSummary shows policy-level utilization bars when companyId provided, falls back to agent-level
- BudgetAlertBanner loads open incidents for policy-based alerts (warn_threshold/hard_stop), falls back to agent thresholds
- AgentEditor reads budget from budget_policies and writes budget as policy on save (D-11)
- Dashboard passes companyId through to BudgetAlertBanner
This commit is contained in:
ZacharyZhang-NY committed 2026-03-26 21:17:02 -04:00
1 parent 3a8e4c40ad
commit 1c462d571c
5 files changed
+217 -46

No files matched your search

@@ -1,6 +1,6 @@
import { useEffect, useMemo, useState } from "react";
import { Pencil, Trash } from "@phosphor-icons/react";
import type { AgentRecord, ConnectorRecord, WorkspaceRecord } from "@shared/types";
import type { AgentRecord, BudgetPolicyRecord, ConnectorRecord, WorkspaceRecord } from "@shared/types";
import { getConnectorExecutionReadinessIssue, isConnectorExecutionReady } from "@shared/connector-policy";
import { unwrap } from "../../lib/desktop";
import { useAppStore } from "../../state/store";
@@ -87,6 +87,23 @@ export function AgentEditor({
}
}, [companyId, skills.length, setSkills]);
// Load agent's budget policy (D-11: read from budget_policies)
const [agentPolicy, setAgentPolicy] = useState<BudgetPolicyRecord | null>(null);
useEffect(() => {
void (async () => {
const result = await window.agentCompany.listBudgetPolicies(companyId);
if (result.ok) {
const policy = result.data.find(
(p) => p.scopeType === "agent" && p.scopeId === agent.id
);
if (policy) {
setAgentPolicy(policy);
setBudget(String((policy.amount / 100).toFixed(2)));
}
}
})();
}, [companyId, agent.id]);
const managerCandidates = agents.filter((a) => a.id !== agent.id);
return (
@@ -194,6 +211,7 @@ export function AgentEditor({
if (thinkingEffort) meta.thinkingEffort = thinkingEffort;
else delete meta.thinkingEffort;
try {
const budgetDollars = Number(budget || 0);
unwrap(await window.agentCompany.saveAgent({
id: agent.id,
companyId,
@@ -206,9 +224,24 @@ export function AgentEditor({
reportsTo: reportsTo || null,
model: model || null,
metadataJson: JSON.stringify(meta),
budgetMonthlyUsd: Number(budget || 0),
budgetMonthlyUsd: budgetDollars,
capabilities,
}));
// D-11: Also write to budget_policies for policy-based enforcement
if (budgetDollars > 0 && agent.id) {
await window.agentCompany.saveBudgetPolicy({
companyId,
scopeType: "agent",
scopeId: agent.id,
metric: "billed_cents",
windowKind: "calendar_month_utc",
amount: Math.round(budgetDollars * 100),
warnPercent: agentPolicy?.warnPercent ?? 80,
hardStopEnabled: agentPolicy?.hardStopEnabled ?? true,
notifyEnabled: agentPolicy?.notifyEnabled ?? true,
isActive: true,
});
}
await onSaved();
} catch (error) {
setSaveError(error instanceof Error ? error.message : "Failed to save agent.");