diff --git a/src/main/database/approvals.ts b/src/main/database/approvals.ts index 947e9341..25da7786 100644 --- a/src/main/database/approvals.ts +++ b/src/main/database/approvals.ts @@ -415,9 +415,10 @@ export function buildApprovalMethods( .prepare( `SELECT 1 as hit FROM approvals a JOIN tasks t ON a.related_task_id = t.id + JOIN approval_policies p ON p.approval_type = a.type WHERE a.company_id = ? AND a.state = 'pending' AND t.assignee_agent_id = ? - AND a.type IN ('plan_review','scope_review','draft_review','strategy_review','deliverable_review','exception_review') + AND p.blocks_execution = 1 LIMIT 1`, ) .get(companyId, agentId) as { hit: number } | undefined; diff --git a/src/main/orchestration/heartbeat.ts b/src/main/orchestration/heartbeat.ts index e68c6660..fb2475e9 100644 --- a/src/main/orchestration/heartbeat.ts +++ b/src/main/orchestration/heartbeat.ts @@ -196,6 +196,8 @@ export function createHeartbeat( return BLOCKED("policy_blocked", "wake_on_demand_disabled"); } if (db.core.agents.isAgentPendingApproval(agentId)) return BLOCKED("human_blocked", "pending_approval"); + const agentLevelBlock = db.core.approvals.getBlockingApproval(companyId, agentId, null); + if (agentLevelBlock) return BLOCKED("human_blocked", `approval_gate:${agentLevelBlock.type}`); const companyMode = db.core.company.getCompanyExecutionMode(companyId); const approvalSettings = db.core.company.getCompanyApprovalSettings(companyId); @@ -268,6 +270,11 @@ export function createHeartbeat( logger.info(`[heartbeat] Starting taskless heartbeat run for ${agent.name}`); } + if (task) { + const taskBlock = db.core.approvals.getBlockingApproval(companyId, agent.id, task.id); + if (taskBlock) return SKIPPED(BLOCKED("human_blocked", `approval_gate:${taskBlock.type}`)); + } + let workspaceResolution: ReturnType; try { workspaceResolution = resolveWorkspace(task, agent, companyId); diff --git a/src/main/orchestration/task-runner.ts b/src/main/orchestration/task-runner.ts index e9861966..16f9d6fd 100644 --- a/src/main/orchestration/task-runner.ts +++ b/src/main/orchestration/task-runner.ts @@ -270,6 +270,24 @@ export function createTaskRunner( const task = run.taskId ? db.core.entity.getTask(run.taskId) : null; const agent = db.core.entity.getAgent(run.agentId); const connector = db.core.connectors.getConnector(run.connectorId); + const blockingApproval = db.core.approvals.getBlockingApproval(run.companyId, agent.id, run.taskId); + if (blockingApproval) { + finishQueuedRecoveryRun( + run, + "cancelled", + `Queued run blocked by pending approval: ${blockingApproval.type}`, + null, + ); + db.core.activity.addActivity({ + companyId: run.companyId, + actor: "system", + action: "run.recovery_blocked_approval", + entityType: "run", + entityId: run.id, + detail: `Recovered queued run blocked by ${blockingApproval.type} for task "${task?.title ?? "(heartbeat)"}"`, + }); + continue; + } const eligibilityFailure = getRunEligibilityFailure(agent, connector, task?.projectId ?? null); if (eligibilityFailure) { if (eligibilityFailure === "CONNECTOR_MODEL_NOT_ALLOWED") {