fix: Terraform Agent uses SQLite RAG only, terminal shows errors properly

- Skip ADB vector search for Terraform Agent (uses local SQLite resource reference instead)
- Show error message in terminal when plan fails validation (no more stuck "Aguardando execução")
- Write region validation error to plan_output field for terminal display
- Fix Plan/Re-plan button visibility for all workspace states (including stuck planning/applying)
This commit is contained in:
nogueiraguh
2026-03-12 08:45:54 -03:00
parent e4ba2cfaab
commit 981f77b1eb
2 changed files with 9 additions and 7 deletions

View File

@@ -3962,7 +3962,7 @@ async def _chat_background(mid: str, sid: str, msg: ChatMsg, user: dict, genai_c
log.info(f"RAG: filtering by tenancy '{rag_tenancy}'") log.info(f"RAG: filtering by tenancy '{rag_tenancy}'")
rag_context = "" rag_context = ""
adb_cfgs = _get_active_adb_configs(user["id"]) adb_cfgs = _get_active_adb_configs(user["id"]) if agent_type != "terraform" else []
if adb_cfgs: if adb_cfgs:
all_documents = [] all_documents = []
for adb_cfg in adb_cfgs: for adb_cfg in adb_cfgs:
@@ -5176,8 +5176,8 @@ async def _terraform_exec(wid: str, action: str, user: dict):
) )
log.warning(f"Workspace {wid}: missing variable 'region' — blocking plan") log.warning(f"Workspace {wid}: missing variable 'region' — blocking plan")
with db() as c: with db() as c:
c.execute("UPDATE terraform_workspaces SET status='failed', error=?, updated_at=datetime('now') WHERE id=?", c.execute("UPDATE terraform_workspaces SET status='failed', plan_output=?, error=?, updated_at=datetime('now') WHERE id=?",
(error_msg, wid)) (error_msg, error_msg, wid))
return return
region_value = 'var.region' region_value = 'var.region'

View File

@@ -1401,12 +1401,13 @@ function _tfBadge(st){
function rTfActions(){ function rTfActions(){
if(S.tfRunning)return'<button class="btn bsm" style="background:var(--rd);color:#fff;border-color:var(--rd);font-size:.72rem;padding:4px 12px" onclick="tfCancel()">Cancel</button>'; if(S.tfRunning)return'<button class="btn bsm" style="background:var(--rd);color:#fff;border-color:var(--rd);font-size:.72rem;padding:4px 12px" onclick="tfCancel()">Cancel</button>';
const st=S.tfStatus;let btns=''; const st=S.tfStatus;let btns='';
if(S.tfCode&&(st==='draft'||st==='failed'||st==='destroyed'))btns+=`<button class="btn bsm" style="background:var(--pp);color:#fff;border-color:var(--pp);font-size:.72rem;padding:4px 12px;gap:4px;display:inline-flex;align-items:center" onclick="tfSaveAndPlan()">${TF_ICON} Plan</button>`; const canPlan=S.tfCode&&['draft','failed','destroyed','planning','applying','destroying'].includes(st);
const canReplan=S.tfCode&&['planned','failed','applied'].includes(st);
if(canPlan&&!canReplan)btns+=`<button class="btn bsm" style="background:var(--pp);color:#fff;border-color:var(--pp);font-size:.72rem;padding:4px 12px;gap:4px;display:inline-flex;align-items:center" onclick="tfSaveAndPlan()">${TF_ICON} Plan</button>`;
if(canReplan)btns+=`<button class="btn bsm" style="background:var(--pp);color:#fff;border-color:var(--pp);font-size:.72rem;padding:4px 12px;gap:4px;display:inline-flex;align-items:center" onclick="tfSaveAndPlan()">${TF_ICON} Re-plan</button>`;
if(st==='failed'&&S.tfPlanOut&&S.tfFiles.length&&S.tfModel)btns+=`<button class="btn bsm" style="background:#e67e22;color:#fff;border-color:#e67e22;font-size:.72rem;padding:4px 12px;gap:4px;display:inline-flex;align-items:center" onclick="tfAutoFix()">${IC.fix} Auto-corrigir</button>`; if(st==='failed'&&S.tfPlanOut&&S.tfFiles.length&&S.tfModel)btns+=`<button class="btn bsm" style="background:#e67e22;color:#fff;border-color:#e67e22;font-size:.72rem;padding:4px 12px;gap:4px;display:inline-flex;align-items:center" onclick="tfAutoFix()">${IC.fix} Auto-corrigir</button>`;
if(st==='failed'&&(S.tfApplyOut||S.tfHadApply))btns+='<button class="btn bsm" style="background:var(--rd);color:#fff;border-color:var(--rd);font-size:.72rem;padding:4px 12px;gap:4px;display:inline-flex;align-items:center" onclick="S.tfConfirmRollback=true;R()">↩ Rollback</button>'; if(st==='failed'&&(S.tfApplyOut||S.tfHadApply))btns+='<button class="btn bsm" style="background:var(--rd);color:#fff;border-color:var(--rd);font-size:.72rem;padding:4px 12px;gap:4px;display:inline-flex;align-items:center" onclick="S.tfConfirmRollback=true;R()">↩ Rollback</button>';
if(st==='planned'||st==='failed')btns+=`<button class="btn bsm" style="background:var(--pp);color:#fff;border-color:var(--pp);font-size:.72rem;padding:4px 12px;gap:4px;display:inline-flex;align-items:center" onclick="tfSaveAndPlan()">${TF_ICON} Re-plan</button>`;
if(st==='planned')btns+='<button class="btn bsm" style="background:var(--gn);color:#fff;border-color:var(--gn);font-size:.72rem;padding:4px 12px" onclick="tfApply()">✓ Apply</button>'; if(st==='planned')btns+='<button class="btn bsm" style="background:var(--gn);color:#fff;border-color:var(--gn);font-size:.72rem;padding:4px 12px" onclick="tfApply()">✓ Apply</button>';
if(st==='applied')btns+=`<button class="btn bsm" style="background:var(--pp);color:#fff;border-color:var(--pp);font-size:.72rem;padding:4px 12px;gap:4px;display:inline-flex;align-items:center" onclick="tfSaveAndPlan()">${TF_ICON} Re-plan</button>`;
if(st==='applied'||st==='planned')btns+='<button class="btn bsm" style="background:var(--rd);color:#fff;border-color:var(--rd);font-size:.72rem;padding:4px 12px" onclick="S.tfConfirmDest=true;R()">✕ Destroy</button>'; if(st==='applied'||st==='planned')btns+='<button class="btn bsm" style="background:var(--rd);color:#fff;border-color:var(--rd);font-size:.72rem;padding:4px 12px" onclick="S.tfConfirmDest=true;R()">✕ Destroy</button>';
return btns; return btns;
} }
@@ -1786,13 +1787,14 @@ async function tfPollExec(){
S.tfPlanOut=r.plan_output||S.tfPlanOut; S.tfPlanOut=r.plan_output||S.tfPlanOut;
S.tfApplyOut=r.apply_output||S.tfApplyOut; S.tfApplyOut=r.apply_output||S.tfApplyOut;
S.tfDestroyOut=r.destroy_output||S.tfDestroyOut; S.tfDestroyOut=r.destroy_output||S.tfDestroyOut;
S.tfError=r.error||'';
S.tfStatus=r.status; S.tfStatus=r.status;
const done=!['planning','applying','destroying'].includes(r.status); const done=!['planning','applying','destroying'].includes(r.status);
if(done){S.tfRunning=false;if(r.status==='destroyed')S.tfHadApply=false;} if(done){S.tfRunning=false;if(r.status==='destroyed')S.tfHadApply=false;}
// Update only the terminal body without full re-render // Update only the terminal body without full re-render
const t=document.getElementById('tfTermOut'); const t=document.getElementById('tfTermOut');
if(t){ if(t){
const content=S.tfDestroyOut||S.tfApplyOut||S.tfPlanOut||''; const content=S.tfDestroyOut||S.tfApplyOut||S.tfPlanOut||(done&&S.tfError?'ERROR: '+S.tfError:'')||'';
t.innerHTML=content?escHtml(content):'<span style="opacity:.4">Aguardando execução...</span>'; t.innerHTML=content?escHtml(content):'<span style="opacity:.4">Aguardando execução...</span>';
t.scrollTop=t.scrollHeight; t.scrollTop=t.scrollHeight;
} }