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:
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.");
|
||||
|
||||
Reference in new issue
Block a user