feat(05-02): replace flat budget check with policy-based gate, wire incident checks into run completion and heartbeat

- Replace flat budgetMonthlyUsd check with evaluateBudgetGate in getRunEligibilityFailure
- Add projectId parameter to getRunEligibilityFailure for project-scope policy evaluation
- Update all 3 call sites to pass projectId where task context is available
- Wire checkAndCreateIncidents into worker-service run-finished handler for post-run incident creation
- Wire autoCloseExpiredIncidents into heartbeat for window rollover cleanup (D-06)
- Pass budgetService dependency to orchestration-service and worker-service via service-graph
- Mark autoPauseBudgetExceededAgent as deprecated (superseded by BudgetEnforcementService)
This commit is contained in:
ZacharyZhang-NY committed 2026-03-26 21:02:12 -04:00
1 parent feea5c1fe9
commit 4e5429929a
3 files changed
+36 -9

No files matched your search

+13
View File
@@ -4,6 +4,7 @@ import { getConnectorDefinition } from "./connectors";
import type { AppDatabase } from "./database";
import type { RunRecord } from "@shared/types";
import type { NotificationOptions } from "./notification-service";
import type { createBudgetEnforcementService } from "./budget-enforcement-service";
const runStatusSchema = z.enum(["queued", "running", "succeeded", "failed", "cancelled", "timed_out", "interrupted"]);
export const workerMessageSchema = z.union([
@@ -63,6 +64,7 @@ interface WorkerServiceDependencies {
}) => void;
processRunCompletion: (run: RunRecord, event: z.infer<typeof workerMessageSchema> & { type: "run-finished" }) => void;
notify: (options: NotificationOptions) => void;
budgetService?: ReturnType<typeof createBudgetEnforcementService>;
}
export function createWorkerService({
@@ -76,6 +78,7 @@ export function createWorkerService({
dispatchRunToWorker,
processRunCompletion,
notify,
budgetService,
}: WorkerServiceDependencies) {
const MAX_RETRY_ATTEMPTS = 2;
const RETRY_COOLDOWN_MS = 30_000; // 30 seconds
@@ -330,6 +333,16 @@ export function createWorkerService({
publishDomainChanged();
}
// Policy-based incident check after run completion (Phase 5)
if (budgetService && typeof event.costUsd === "number" && Number.isFinite(event.costUsd)) {
try {
const task = db.getTask(run.taskId);
budgetService.checkAndCreateIncidents(run.companyId, run.agentId, task?.projectId ?? null);
} catch (err) {
logger.error(`[budget] Incident check failed: ${err instanceof Error ? err.message : String(err)}`);
}
}
if ((event.status === "failed" || event.status === "timed_out") && !isQuitting() && !destroyed) {
const retryCount = db.getTaskRetryCount(run.taskId);