feat: robust TF file correction, move Start/Stop to Explorer, increase context

- Inject existing workspace files + plan/apply output into Terraform chat
  context so model corrects in-place instead of regenerating from scratch
- Add "REGRA CRÍTICA: Correção de Arquivos Existentes" to system prompt
  requiring the model to preserve file structure and fix surgically
- Increase Terraform Agent max_tokens from 16K to 32K
- Remove "Nova conversa" button from Terraform toolbar
- Move Start/Stop Instance and ADB buttons from Terraform Resources
  panel to OCI Account Explorer (instances and databases tabs)
- Remove unused _ociResOid helper and old action functions
This commit is contained in:
nogueiraguh
2026-03-08 01:34:17 -03:00
parent 9de4c2bc7b
commit bb25c3dc29
2 changed files with 45 additions and 12 deletions

View File

@@ -2038,6 +2038,20 @@ Abaixo estão os nomes corretos de resource types do provider oracle/oci. O mode
- **DRG Route Table**: `oci_core_drg_route_table`
- **DRG Route Distribution**: `oci_core_drg_route_distribution`
- **DRG Route Table Route Rule**: `oci_core_drg_route_table_route_rule`
### REGRA CRÍTICA: Correção de Arquivos Existentes
Quando a mensagem do usuário incluir a seção "ARQUIVOS TERRAFORM ATUAIS NO WORKSPACE", significa que JÁ EXISTEM arquivos gerados anteriormente. Neste caso:
1. **NUNCA descarte os arquivos existentes**. Eles são o trabalho atual do usuário.
2. **Gere TODOS os arquivos completos** — mesmo os que não precisaram de alteração. O sistema substitui todos os arquivos do workspace pelo que você gerar.
3. **Corrija APENAS o que está errado**: se o erro é em `networking.tf`, mantenha `variables.tf`, `compute.tf`, `outputs.tf` etc. EXATAMENTE como estão e corrija apenas `networking.tf`.
4. **Analise o output do terraform plan/apply** se incluído na mensagem — ele mostra exatamente qual recurso, atributo ou argumento causou o erro.
5. **Mantenha a mesma estrutura de arquivos**: use os mesmos nomes de arquivo (`// filename: xxx.tf`) que já existem. NÃO renomeie, NÃO reorganize, NÃO mescle arquivos.
6. **Preserve nomes de recursos**: NÃO renomeie recursos existentes (ex: `oci_core_vcn.hub` não deve virar `oci_core_vcn.main`), pois isso causaria destroy+recreate desnecessário.
7. Se o erro for um resource type inválido, consulte a referência de recursos OCI neste prompt para encontrar o nome correto.
8. Se o erro for um argumento inválido (Unsupported argument), remova-o ou substitua pela sintaxe correta.
**RESUMO**: Ao corrigir, regenere todos os arquivos mantendo conteúdo intacto nos que não têm erro, e corrija cirurgicamente os que têm problema. O resultado deve funcionar no `terraform plan` sem erros na primeira tentativa.
"""
# ── Terraform OCI Resource Reference (generated at build time, updatable at runtime) ──
@@ -3168,7 +3182,7 @@ async def _chat_background(mid: str, sid: str, msg: ChatMsg, user: dict, genai_c
# ── Terraform agent: boost max_tokens for complex infra code ──
if agent_type == "terraform":
cfg_dict["max_tokens"] = max(int(cfg_dict.get("max_tokens", 6000)), 16000)
cfg_dict["max_tokens"] = max(int(cfg_dict.get("max_tokens", 6000)), 32000)
# ── Inject existing OCI resources for terraform agent ──
if agent_type == "terraform" and active_oci_id:

View File

@@ -800,7 +800,6 @@ function rTerraform(){
</div>
</div>
</div>
<div class="tf-file-btn" onclick="newTfChat()" title="Nova conversa" style="width:24px;height:24px"><svg viewBox="0 0 16 16" width="12" height="12" fill="var(--t3)"><path d="M8 2v12M2 8h12" stroke="var(--t3)" stroke-width="2" stroke-linecap="round"/></svg></div>
</div>
<div class="tf-msgs" id="tfchm">${ms}</div>
<div class="tf-input">
@@ -997,12 +996,12 @@ function rTfResourcesPanel(){
const cats=[
{key:'vcns',label:'VCNs',icon:'<svg viewBox="0 0 16 16" width="14" height="14" fill="#7b42bc"><path d="M1 3h14v2H1zm2 4h10v2H3zm3 4h4v2H6z"/></svg>',fmt:r=>`${r.display_name} <span style="opacity:.6">(${(r.cidr_blocks||[]).join(', ')})</span>`},
{key:'subnets',label:'Subnets',icon:'<svg viewBox="0 0 16 16" width="14" height="14" fill="#7b42bc"><path d="M2 2h5v5H2zm7 0h5v5H9zm-7 7h5v5H2zm7 0h5v5H9z"/></svg>',fmt:r=>`${r.display_name} <span style="opacity:.6">(${r.cidr_block}, ${r.public?'pública':'privada'})</span>`},
{key:'instances',label:'Compute',icon:'<svg viewBox="0 0 16 16" width="14" height="14" fill="#7b42bc"><path d="M2 2h12v12H2z"/></svg>',fmt:r=>{const stopped=r.state==='STOPPED';const running=r.state==='RUNNING';const stC=stopped?'var(--rd)':running?'var(--gn)':'var(--t3)';return`${r.display_name} <span style="opacity:.6">(${r.shape})</span> <span style="color:${stC};font-weight:600;font-size:.62rem">${r.state}</span>${stopped?` <button class="btn bsm" style="font-size:.58rem;padding:1px 6px;background:var(--gn);color:#fff;border-color:var(--gn);margin-left:4px" onclick="event.stopPropagation();ociStartInstance('${r.id}')">▶ Start</button>`:running?` <button class="btn bsm" style="font-size:.58rem;padding:1px 6px;background:var(--rd);color:#fff;border-color:var(--rd);margin-left:4px" onclick="event.stopPropagation();ociStopInstance('${r.id}')">■ Stop</button>`:''}`}},
{key:'instances',label:'Compute',icon:'<svg viewBox="0 0 16 16" width="14" height="14" fill="#7b42bc"><path d="M2 2h12v12H2z"/></svg>',fmt:r=>{const stopped=r.state==='STOPPED';const running=r.state==='RUNNING';const stC=stopped?'var(--rd)':running?'var(--gn)':'var(--t3)';return`${r.display_name} <span style="opacity:.6">(${r.shape})</span> <span style="color:${stC};font-weight:600;font-size:.62rem">${r.state}</span>`}},
{key:'internet_gateways',label:'Internet GWs',icon:'<svg viewBox="0 0 16 16" width="14" height="14" fill="#7b42bc"><circle cx="8" cy="8" r="6" fill="none" stroke="#7b42bc" stroke-width="1.5"/><path d="M1 8h14M8 2c-2 2-2 10 0 12m0-12c2 2 2 10 0 12"/></svg>',fmt:r=>r.display_name},
{key:'nat_gateways',label:'NAT GWs',icon:'<svg viewBox="0 0 16 16" width="14" height="14" fill="#7b42bc"><path d="M3 3l5 5-5 5"/><path d="M8 8h6" stroke="#7b42bc" stroke-width="1.5"/></svg>',fmt:r=>r.display_name},
{key:'route_tables',label:'Route Tables',icon:'<svg viewBox="0 0 16 16" width="14" height="14" fill="#7b42bc"><path d="M2 3h12v2H2zm0 4h8v2H2zm0 4h10v2H2z"/></svg>',fmt:r=>r.display_name},
{key:'security_lists',label:'Security Lists',icon:'<svg viewBox="0 0 16 16" width="14" height="14" fill="#7b42bc"><path d="M8 1l6 3v4c0 3.5-2.5 6.5-6 7.5C4.5 14.5 2 11.5 2 8V4z"/></svg>',fmt:r=>r.display_name},
{key:'autonomous_databases',label:'Databases',icon:'<svg viewBox="0 0 16 16" width="14" height="14" fill="#7b42bc"><ellipse cx="8" cy="4" rx="6" ry="2.5"/><path d="M2 4v8c0 1.4 2.7 2.5 6 2.5s6-1.1 6-2.5V4"/></svg>',fmt:r=>{const stopped=r.state==='STOPPED';const avail=r.state==='AVAILABLE';const stC=stopped?'var(--rd)':avail?'var(--gn)':'var(--t3)';return`${r.display_name} <span style="opacity:.6">(${r.db_name}${r.is_free_tier?' Free':''})</span> <span style="color:${stC};font-weight:600;font-size:.62rem">${r.state}</span>${stopped?` <button class="btn bsm" style="font-size:.58rem;padding:1px 6px;background:var(--gn);color:#fff;border-color:var(--gn);margin-left:4px" onclick="event.stopPropagation();ociStartAdb('${r.id}')">▶ Start</button>`:avail?` <button class="btn bsm" style="font-size:.58rem;padding:1px 6px;background:var(--rd);color:#fff;border-color:var(--rd);margin-left:4px" onclick="event.stopPropagation();ociStopAdb('${r.id}')">■ Stop</button>`:''}`}},
{key:'autonomous_databases',label:'Databases',icon:'<svg viewBox="0 0 16 16" width="14" height="14" fill="#7b42bc"><ellipse cx="8" cy="4" rx="6" ry="2.5"/><path d="M2 4v8c0 1.4 2.7 2.5 6 2.5s6-1.1 6-2.5V4"/></svg>',fmt:r=>{const stopped=r.state==='STOPPED';const avail=r.state==='AVAILABLE';const stC=stopped?'var(--rd)':avail?'var(--gn)':'var(--t3)';return`${r.display_name} <span style="opacity:.6">(${r.db_name}${r.is_free_tier?' Free':''})</span> <span style="color:${stC};font-weight:600;font-size:.62rem">${r.state}</span>`}},
{key:'block_volumes',label:'Block Volumes',icon:'<svg viewBox="0 0 16 16" width="14" height="14" fill="#7b42bc"><path d="M3 2h10v12H3z"/></svg>',fmt:r=>`${r.display_name} <span style="opacity:.6">(${r.size_in_gbs||'?'} GB)</span>`},
{key:'load_balancers',label:'Load Balancers',icon:'<svg viewBox="0 0 16 16" width="14" height="14" fill="#7b42bc"><path d="M8 2v12M2 8h12M4 4l8 8M12 4l-8 8"/></svg>',fmt:r=>`${r.display_name} <span style="opacity:.6">(${r.shape_name||'flexible'})</span>`}
];
@@ -1025,11 +1024,16 @@ function rTfResourcesPanel(){
html+='</div>';
return html;
}
function _ociResOid(){return S.tfOci||(S.tfModel.startsWith('cfg:')?((S.genaiCfg.find(x=>x.id===S.tfModel.slice(4))||{}).oci_config_id||''):'')}
async function ociStartInstance(id){if(!confirm('Iniciar esta instância?'))return;try{await $api('/oci/instances/'+id+'/action',{method:'POST',body:{action:'START',oci_config_id:_ociResOid(),region:S.tfRegion||null}});alert('Instância iniciando...');tfLoadResources()}catch(e){alert('Erro: '+e.message)}}
async function ociStopInstance(id){if(!confirm('Parar esta instância?'))return;try{await $api('/oci/instances/'+id+'/action',{method:'POST',body:{action:'STOP',oci_config_id:_ociResOid(),region:S.tfRegion||null}});alert('Instância parando...');tfLoadResources()}catch(e){alert('Erro: '+e.message)}}
async function ociStartAdb(id){if(!confirm('Iniciar este Autonomous Database?'))return;try{await $api('/oci/autonomous-databases/'+id+'/action',{method:'POST',body:{action:'start',oci_config_id:_ociResOid(),region:S.tfRegion||null}});alert('Autonomous DB iniciando...');tfLoadResources()}catch(e){alert('Erro: '+e.message)}}
async function ociStopAdb(id){if(!confirm('Parar este Autonomous Database?'))return;try{await $api('/oci/autonomous-databases/'+id+'/action',{method:'POST',body:{action:'stop',oci_config_id:_ociResOid(),region:S.tfRegion||null}});alert('Autonomous DB parando...');tfLoadResources()}catch(e){alert('Erro: '+e.message)}}
async function ociInstanceAction(id,action){
const label=action==='START'?'Iniciar':'Parar';
if(!confirm(label+' esta instância?'))return;
try{await $api('/oci/instances/'+id+'/action',{method:'POST',body:{action,oci_config_id:S.expCfg,region:null}});alert('Instância '+(action==='START'?'iniciando':'parando')+'...');expLoadRes()}catch(e){alert('Erro: '+e.message)}
}
async function ociAdbAction(id,action){
const label=action==='start'?'Iniciar':'Parar';
if(!confirm(label+' este Autonomous Database?'))return;
try{await $api('/oci/autonomous-databases/'+id+'/action',{method:'POST',body:{action,oci_config_id:S.expCfg,region:null}});alert('Autonomous DB '+(action==='start'?'iniciando':'parando')+'...');expLoadRes()}catch(e){alert('Erro: '+e.message)}
}
function tfSwitchBtab(tab){
S.tfBtab=tab;S.tfEditIdx=-1;
if(tab==='resources'&&!S.tfResources&&S.tfCompartment)tfLoadResources();
@@ -1051,10 +1055,18 @@ async function tfSend(){
const el=document.getElementById('tfi');const m=el.value.trim();if(!m)return;el.style.height='auto';S._tfiVal='';
if(!S.tfModel){S.tfMsgs.push({r:'assistant',c:'⚠️ Selecione um modelo antes de enviar.',t:tstamp()});R();return}
if(!S.tfCompartment){S.tfMsgs.push({r:'assistant',c:'⚠️ Selecione um compartment antes de enviar.',t:tstamp()});R();return}
el.value='';S.tfMsgs.push({r:'user',c:m,t:tstamp()});R();tfScroll();
el.value='';S.tfMsgs.push({r:'user',c:m,t:tstamp()});R();tfScroll(); // show original msg without injected files
S.tfMsgs.push({r:'assistant',c:'⏳ _Gerando Terraform..._',thinking:true});R();tfScroll();
try{
const body={message:m,session_id:S.tfSid,use_tools:false};
// Inject existing files context when user is correcting/fixing
let finalMsg=m;
if(S.tfFiles.length>0){
const filesCtx=S.tfFiles.map(f=>'// filename: '+f.name+'\n'+f.content).join('\n\n');
finalMsg+='\n\n---\nARQUIVOS TERRAFORM ATUAIS NO WORKSPACE (corrija estes arquivos):\n```hcl\n'+filesCtx+'\n```';
}
if(S.tfPlanOut){finalMsg+='\n\n---\nÚLTIMO OUTPUT DO TERRAFORM PLAN:\n```\n'+S.tfPlanOut.slice(-3000)+'\n```'}
if(S.tfApplyOut){finalMsg+='\n\n---\nÚLTIMO OUTPUT DO TERRAFORM APPLY:\n```\n'+S.tfApplyOut.slice(-3000)+'\n```'}
const body={message:finalMsg,session_id:S.tfSid,use_tools:false};
if(S.tfModel.startsWith('cfg:'))body.genai_config_id=S.tfModel.slice(4);
else{
if(!S.tfOci){S.tfMsgs.pop();S.tfMsgs.push({r:'assistant',c:'⚠️ Selecione uma credencial OCI.',t:tstamp()});R();return}
@@ -1264,11 +1276,18 @@ function renderExpData(d){if(!d)return'<div class="emp"><p>Selecione um compartm
if(d.error)return`<div class="al al-e">${d.error}</div>`;if(!Array.isArray(d)||!d.length)return'<div class="emp"><p>Nenhum recurso encontrado neste compartment.</p></div>';
if(S.expResType==='security_lists')return rExpSecLists(d);
if(S.expResType==='route_tables')return rExpRouteTables(d);
const isInst=S.expResType==='instances';
const isAdb=S.expResType==='databases';
return`<div style="font-size:.74rem;color:var(--t4);margin-bottom:.5rem">${d.length} item(ns) encontrado(s)</div><div class="exp-r">${d.map(i=>{
const keys=Object.keys(i);
const name=i.display_name||i.name||i.db_name||i.application||'—';
const skip=['display_name','name','db_name','lifecycle_state','application','rules','routes'];
return`<div class="exp-c"><strong>${name}</strong>${i.lifecycle_state?` <span class="badge ${i.lifecycle_state==='RUNNING'||i.lifecycle_state==='ACTIVE'||i.lifecycle_state==='AVAILABLE'?'b-pass':'b-pend'}">${i.lifecycle_state}</span>`:''}<br>
let actionBtn='';
if(isInst&&i.lifecycle_state==='RUNNING')actionBtn=' <button class="btn bsm" style="font-size:.6rem;padding:1px 8px;background:var(--rd);color:#fff;border-color:var(--rd);margin-left:6px" onclick="event.stopPropagation();ociInstanceAction(\''+i.id+'\',\'STOP\')">■ Stop</button>';
else if(isInst&&i.lifecycle_state==='STOPPED')actionBtn=' <button class="btn bsm" style="font-size:.6rem;padding:1px 8px;background:var(--gn);color:#fff;border-color:var(--gn);margin-left:6px" onclick="event.stopPropagation();ociInstanceAction(\''+i.id+'\',\'START\')">▶ Start</button>';
else if(isAdb&&i.lifecycle_state==='AVAILABLE')actionBtn=' <button class="btn bsm" style="font-size:.6rem;padding:1px 8px;background:var(--rd);color:#fff;border-color:var(--rd);margin-left:6px" onclick="event.stopPropagation();ociAdbAction(\''+i.id+'\',\'stop\')">■ Stop</button>';
else if(isAdb&&i.lifecycle_state==='STOPPED')actionBtn=' <button class="btn bsm" style="font-size:.6rem;padding:1px 8px;background:var(--gn);color:#fff;border-color:var(--gn);margin-left:6px" onclick="event.stopPropagation();ociAdbAction(\''+i.id+'\',\'start\')">▶ Start</button>';
return`<div class="exp-c"><strong>${name}</strong>${i.lifecycle_state?` <span class="badge ${i.lifecycle_state==='RUNNING'||i.lifecycle_state==='ACTIVE'||i.lifecycle_state==='AVAILABLE'?'b-pass':'b-pend'}">${i.lifecycle_state}</span>`:''}${actionBtn}<br>
${keys.filter(k=>!skip.includes(k)).map(k=>`<span style="color:var(--t4);font-size:.66rem">${k}:</span> <span class="em">${typeof i[k]==='object'?JSON.stringify(i[k]):i[k]}</span>`).join('<br>')}</div>`}).join('')}</div>`}
function rExpSecLists(d){
return`<div style="font-size:.74rem;color:var(--t4);margin-bottom:.5rem">${d.length} security list(s)</div>${d.map((sl,idx)=>{