@fabioforest/openclaw 3.10.0 → 3.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -3
- package/lib/cli/ide.js +59 -79
- package/lib/cli/init.js +46 -75
- package/lib/cli/uninstall.js +67 -81
- package/lib/cli/update.js +89 -92
- package/lib/context/context-engine.js +89 -0
- package/lib/core/orchestrator.js +113 -0
- package/lib/utils/scope_guard.js +12 -1
- package/package.json +1 -1
- package/templates/.agent/skills/workspace-snapshot/SKILL.md +20 -0
- package/templates/.agent/workflows/chat-router.md +19 -0
- package/templates/ide/.cursor/rules/openclaw.mdc +11 -0
- package/templates/ide/.cursorrules +7 -0
- package/templates/ide/.github/copilot-instructions.md +11 -0
package/README.md
CHANGED
|
@@ -280,6 +280,7 @@ npx @fabioforest/openclaw ide install --apply --force
|
|
|
280
280
|
│ ├── mission_control.json # Empresa de Agentes
|
|
281
281
|
│ └── MEMORY.md # Memória do workspace
|
|
282
282
|
└── workflows/ # Slash commands e runbooks
|
|
283
|
+
└── chat-first.md # Workflow default de roteamento inteligente
|
|
283
284
|
```
|
|
284
285
|
|
|
285
286
|
---
|
|
@@ -446,6 +447,7 @@ Além de skills isoladas, o OpenClaw traz **fluxos de trabalho completos** (runb
|
|
|
446
447
|
|
|
447
448
|
| Workflow | Descrição | Comando Trigger |
|
|
448
449
|
|----------|-----------|-----------------|
|
|
450
|
+
| **`chat-first`** | **Roteamento Seguro** via INSPECT e PLAN antes de agira | Workflow Padrão (Core) |
|
|
449
451
|
| **`ai-capture`** | Captura inteligente de dados/tickets usando IA | *"Iniciar captura de dados"* |
|
|
450
452
|
| **`doctor`** | Diagnóstico e reparo automático do ambiente | `openclaw doctor` |
|
|
451
453
|
| **`healthcheck`** | Verificação rápida de saúde (API, DB, cache) | `openclaw healthcheck` |
|
|
@@ -458,15 +460,18 @@ Workflows são arquivos `.md` em `.agent/workflows/` que o agente lê e executa
|
|
|
458
460
|
|
|
459
461
|
## 🔒 Segurança
|
|
460
462
|
|
|
461
|
-
O OpenClaw segue
|
|
463
|
+
O OpenClaw segue 4 princípios fundamentais:
|
|
462
464
|
|
|
463
465
|
### 1. Read-only por padrão
|
|
464
|
-
Todo comando opera em **modo PLAN** (simulação). Nada é alterado sem `--apply`.
|
|
466
|
+
Todo comando opera em **modo PLAN** (simulação). Nada é alterado sem `--apply`. A flag `--apply` tem precedência absoluta, substituindo comportamento visual de planejamento.
|
|
465
467
|
|
|
466
468
|
### 2. Consent-first
|
|
467
469
|
Antes de qualquer alteração, o sistema mostra exatamente o que vai fazer e pede confirmação. Ações destrutivas exigem **confirmação forte** (digitar frase específica).
|
|
468
470
|
|
|
469
|
-
### 3.
|
|
471
|
+
### 3. Scope Guard (Proteção Anti-Destruição)
|
|
472
|
+
Qualquer tentativa do CLI de criar, deletar ou sobrescrever arquivos que estejam **fora** da bolha de segurança `.agent/` falha por padrão com o Módulo Scope Guard em ação total para isolar agentes. Pode ser ignorado interativamente no terminal digitando senhas de perigo.
|
|
473
|
+
|
|
474
|
+
### 4. Audit-first
|
|
470
475
|
Toda ação gera log detalhado em `.agent/audit/` com timestamp, comando, modo, contexto e resultado.
|
|
471
476
|
|
|
472
477
|
### Proteções ativas
|
|
@@ -474,6 +479,7 @@ Toda ação gera log detalhado em `.agent/audit/` com timestamp, comando, modo,
|
|
|
474
479
|
| Proteção | Como funciona |
|
|
475
480
|
|----------|---------------|
|
|
476
481
|
| **Hook pre-tool-use** | Bloqueia 12+ padrões destrutivos (`rm -rf`, `mkfs`, `dd`, `shutdown`) |
|
|
482
|
+
| **Scope Guard** | Protege arquivos/framework nativos do projeto/usuário host de reescritas inadvertidas CLI |
|
|
477
483
|
| **VPN-first** | Sem VPN, sem acesso remoto |
|
|
478
484
|
| **Bind localhost** | Serviços só acessíveis localmente por padrão |
|
|
479
485
|
| **Auth token** | Token obrigatório para acesso |
|
package/lib/cli/ide.js
CHANGED
|
@@ -14,7 +14,7 @@ const readline = require("readline");
|
|
|
14
14
|
const { detectContext, getAuditHeader } = require("../context");
|
|
15
15
|
const { copyDirRecursive } = require("./init");
|
|
16
16
|
const { writeCliAudit } = require("../utils/audit-writer");
|
|
17
|
-
const {
|
|
17
|
+
const { executeAction } = require("../core/orchestrator");
|
|
18
18
|
|
|
19
19
|
// Caminho dos templates do pacote
|
|
20
20
|
const TEMPLATES_DIR = path.join(__dirname, "..", "..", "templates");
|
|
@@ -65,6 +65,9 @@ async function runInstall({ targetPath, flags }) {
|
|
|
65
65
|
console.log(" 📦 COPY templates/.agent -> .agent/");
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
const ideSrc = path.join(TEMPLATES_DIR, "ide");
|
|
69
|
+
console.log(" 🌟 ADDON Adaptadores de IDE (.cursorrules, copilot-instructions, .mdc)");
|
|
70
|
+
|
|
68
71
|
// State templates (mission_control.json, MEMORY.md)
|
|
69
72
|
const stateDir = path.join(agentDst, "state");
|
|
70
73
|
if (!fs.existsSync(stateDir)) {
|
|
@@ -74,87 +77,64 @@ async function runInstall({ targetPath, flags }) {
|
|
|
74
77
|
}
|
|
75
78
|
|
|
76
79
|
// 2.5 Scope Guard
|
|
77
|
-
const intents = {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
80
|
+
const intents = {
|
|
81
|
+
writes: [
|
|
82
|
+
agentDst,
|
|
83
|
+
stateDir,
|
|
84
|
+
path.join(targetPath, ".cursorrules"),
|
|
85
|
+
path.join(targetPath, ".github"),
|
|
86
|
+
path.join(targetPath, ".cursor")
|
|
87
|
+
],
|
|
88
|
+
deletes: [],
|
|
89
|
+
overwrites: []
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const confirmationWord = (flags.force && fs.existsSync(agentDst)) ? "DELETE .agent" : null;
|
|
93
|
+
|
|
94
|
+
await executeAction({
|
|
95
|
+
actionName: "ide install",
|
|
96
|
+
context: ctx,
|
|
97
|
+
flags,
|
|
98
|
+
intents,
|
|
99
|
+
targetPath,
|
|
100
|
+
confirmationWord,
|
|
101
|
+
planFn: async () => { }, // O console.log inicial já fez o plan visual acima
|
|
102
|
+
executeFn: async () => {
|
|
103
|
+
if (fs.existsSync(agentDst) && flags.force) {
|
|
104
|
+
fs.rmSync(agentDst, { recursive: true, force: true });
|
|
99
105
|
}
|
|
100
|
-
} else {
|
|
101
|
-
const ok = await ask("\nAplicar instalação IDE? (y/N): ");
|
|
102
|
-
if (ok.toLowerCase() !== "y") {
|
|
103
|
-
console.log("⏹️ Cancelado.");
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Execução
|
|
110
|
-
try {
|
|
111
|
-
console.log("\n🚀 Executando...");
|
|
112
|
-
|
|
113
|
-
if (fs.existsSync(agentDst) && flags.force) {
|
|
114
|
-
fs.rmSync(agentDst, { recursive: true, force: true });
|
|
115
|
-
audit.push("- ACT: DELETED .agent/");
|
|
116
|
-
}
|
|
117
106
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
107
|
+
const isMerge = fs.existsSync(agentDst);
|
|
108
|
+
copyDirRecursive(agentSrc, agentDst, undefined, isMerge);
|
|
109
|
+
copyDirRecursive(ideSrc, targetPath, undefined, true); // Copiar/Merge ide adaptors
|
|
110
|
+
|
|
111
|
+
// Criar state se necessário
|
|
112
|
+
const stateTarget = path.join(agentDst, "state");
|
|
113
|
+
if (!fs.existsSync(stateTarget)) {
|
|
114
|
+
fs.mkdirSync(stateTarget, { recursive: true });
|
|
115
|
+
// Criar mission_control.json default
|
|
116
|
+
const mcDefault = {
|
|
117
|
+
project_status: "active",
|
|
118
|
+
project_name: path.basename(targetPath),
|
|
119
|
+
sprint_goal: "",
|
|
120
|
+
agents: [
|
|
121
|
+
{ id: "orchestrator", role: "orchestrator", active: true },
|
|
122
|
+
{ id: "researcher", role: "researcher", active: true },
|
|
123
|
+
{ id: "writer", role: "writer", active: true },
|
|
124
|
+
],
|
|
125
|
+
task_queue: [],
|
|
126
|
+
history: [],
|
|
127
|
+
settings: {
|
|
128
|
+
work_dir: "mission_control",
|
|
129
|
+
max_tasks_per_tick: 2,
|
|
130
|
+
default_priority: "medium",
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
fs.writeFileSync(path.join(stateTarget, "mission_control.json"), JSON.stringify(mcDefault, null, 2));
|
|
134
|
+
fs.writeFileSync(path.join(stateTarget, "MEMORY.md"), "# Memória Persistente\n\n(Adicione aqui resumos e decisões importantes)\n");
|
|
135
|
+
}
|
|
147
136
|
}
|
|
148
|
-
|
|
149
|
-
console.log("\n✨ IDE install concluído com sucesso!");
|
|
150
|
-
writeAudit(targetPath, audit, flags);
|
|
151
|
-
|
|
152
|
-
} catch (err) {
|
|
153
|
-
console.error(`\n❌ Falha: ${err.message}`);
|
|
154
|
-
audit.push(`\n## ERROR: ${err.message}`);
|
|
155
|
-
writeAudit(targetPath, audit, flags);
|
|
156
|
-
process.exit(1);
|
|
157
|
-
}
|
|
137
|
+
});
|
|
158
138
|
}
|
|
159
139
|
|
|
160
140
|
/**
|
package/lib/cli/init.js
CHANGED
|
@@ -14,7 +14,7 @@ const readline = require("readline");
|
|
|
14
14
|
const { initConfigDefaults, writeJsonSafe } = require("../config");
|
|
15
15
|
const { detectContext, getAuditHeader } = require("../context");
|
|
16
16
|
const { writeCliAudit } = require("../utils/audit-writer");
|
|
17
|
-
const {
|
|
17
|
+
const { executeAction } = require("../core/orchestrator");
|
|
18
18
|
|
|
19
19
|
// Caminho dos templates incluídos no pacote
|
|
20
20
|
const TEMPLATES_DIR = path.join(__dirname, "..", "..", "templates", ".agent");
|
|
@@ -117,87 +117,58 @@ async function run({ targetPath, flags }) {
|
|
|
117
117
|
if (a.type === "COPY_DIR" || a.type === "MERGE_DIR") intents.writes.push(a.to);
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (!flags.yes) {
|
|
143
|
-
if (actions.some(a => a.type === "DELETE_DIR")) {
|
|
144
|
-
console.log("\n⚠️ PERIGO: Operação destrutiva detectada (--force).");
|
|
145
|
-
const phrase = await ask("Digite 'DELETE .agent' para confirmar: ");
|
|
146
|
-
if (phrase !== "DELETE .agent") {
|
|
147
|
-
console.log("⏹️ Cancelado.");
|
|
148
|
-
return;
|
|
120
|
+
// Definir Palavra de Confirmação Forte se houver Force Delete
|
|
121
|
+
const hasDelete = actions.some(a => a.type === "DELETE_DIR");
|
|
122
|
+
const confirmationWord = hasDelete ? "DELETE .agent" : null;
|
|
123
|
+
|
|
124
|
+
// Delegar todo o fluxo final (Guard, Confirm, Execute, Audit) para o Orchestrator
|
|
125
|
+
await executeAction({
|
|
126
|
+
actionName: "init",
|
|
127
|
+
context: ctx,
|
|
128
|
+
flags,
|
|
129
|
+
intents,
|
|
130
|
+
targetPath,
|
|
131
|
+
confirmationWord,
|
|
132
|
+
planFn: async () => {
|
|
133
|
+
console.log(`\n🧭 Plano de Execução:\n`);
|
|
134
|
+
console.log(` Contexto: ${ctx.env.platform} | IDE: ${ctx.ide}\n`);
|
|
135
|
+
for (const a of actions) {
|
|
136
|
+
if (a.type === "DELETE_DIR") console.log(` 🔥 DELETE ${safeRel(targetPath, a.path)} (${a.reason})`);
|
|
137
|
+
if (a.type === "CREATE_DIR") console.log(` 📁 CREATE ${safeRel(targetPath, a.path)}`);
|
|
138
|
+
if (a.type === "COPY_DIR") console.log(` 📦 COPY templates -> ${safeRel(targetPath, a.to)}`);
|
|
139
|
+
if (a.type === "MERGE_DIR") console.log(` 🔄 MERGE templates -> ${safeRel(targetPath, a.to)} (Preservando existentes)`);
|
|
140
|
+
if (a.type === "CREATE_FILE") console.log(` 📝 CREATE ${safeRel(targetPath, a.path)}`);
|
|
141
|
+
if (a.type === "NOOP") console.log(` ✅ KEEP ${safeRel(targetPath, a.path)}`);
|
|
149
142
|
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
143
|
+
},
|
|
144
|
+
executeFn: async () => {
|
|
145
|
+
for (const a of actions) {
|
|
146
|
+
if (a.type === "DELETE_DIR") {
|
|
147
|
+
fs.rmSync(a.path, { recursive: true, force: true });
|
|
148
|
+
}
|
|
155
149
|
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
150
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
fs.rmSync(a.path, { recursive: true, force: true });
|
|
166
|
-
audit.push(`- ACT: DELETED ${a.path}`);
|
|
151
|
+
// Executar cópia/merge se necessário
|
|
152
|
+
const copyAction = actions.find(a => a.type === "COPY_DIR" || a.type === "MERGE_DIR");
|
|
153
|
+
if (copyAction) {
|
|
154
|
+
const isMerge = copyAction.type === "MERGE_DIR";
|
|
155
|
+
copyDirRecursive(TEMPLATES_DIR, agentDir, undefined, isMerge);
|
|
156
|
+
console.log(` ✅ Templates processados.`);
|
|
167
157
|
}
|
|
168
|
-
}
|
|
169
158
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
console.log(` ✅ Templates processados.`);
|
|
177
|
-
}
|
|
159
|
+
// Criar config se necessário
|
|
160
|
+
if (actions.some(a => a.type === "CREATE_FILE" && a.path === configPath)) {
|
|
161
|
+
const defaults = initConfigDefaults({});
|
|
162
|
+
writeJsonSafe(configPath, defaults);
|
|
163
|
+
console.log(` ✅ Config criada.`);
|
|
164
|
+
}
|
|
178
165
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
audit.push(`- ACT: CREATED openclaw.json`);
|
|
184
|
-
console.log(` ✅ Config criada.`);
|
|
166
|
+
// Gravar contexto se ainda não existe
|
|
167
|
+
const contextDir = path.join(agentDir, "context");
|
|
168
|
+
if (!fs.existsSync(contextDir)) fs.mkdirSync(contextDir, { recursive: true });
|
|
169
|
+
fs.writeFileSync(path.join(contextDir, "context.json"), JSON.stringify(ctx, null, 2));
|
|
185
170
|
}
|
|
186
|
-
|
|
187
|
-
// Gravar contexto se ainda não existe
|
|
188
|
-
const contextDir = path.join(agentDir, "context");
|
|
189
|
-
if (!fs.existsSync(contextDir)) fs.mkdirSync(contextDir, { recursive: true });
|
|
190
|
-
fs.writeFileSync(path.join(contextDir, "context.json"), JSON.stringify(ctx, null, 2));
|
|
191
|
-
|
|
192
|
-
console.log("\n✨ Concluído com sucesso!");
|
|
193
|
-
writeAudit(targetPath, audit, flags);
|
|
194
|
-
|
|
195
|
-
} catch (err) {
|
|
196
|
-
console.error(`\n❌ Falha na execução: ${err.message}`);
|
|
197
|
-
audit.push(`\n## ERROR: ${err.message}`);
|
|
198
|
-
writeAudit(targetPath, audit, flags);
|
|
199
|
-
process.exit(1);
|
|
200
|
-
}
|
|
171
|
+
});
|
|
201
172
|
}
|
|
202
173
|
|
|
203
174
|
module.exports = { run, copyDirRecursive };
|
package/lib/cli/uninstall.js
CHANGED
|
@@ -15,7 +15,7 @@ const fs = require("fs");
|
|
|
15
15
|
const path = require("path");
|
|
16
16
|
const readline = require("readline");
|
|
17
17
|
const { detectContext, getAuditHeader } = require("../context");
|
|
18
|
-
const {
|
|
18
|
+
const { executeAction } = require("../core/orchestrator");
|
|
19
19
|
|
|
20
20
|
function ask(q) {
|
|
21
21
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -104,96 +104,82 @@ async function run({ targetPath, flags }) {
|
|
|
104
104
|
console.log(` 🔴 REMOVER openclaw.json`);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
// Acionar
|
|
107
|
+
// Acionar Orquestrador
|
|
108
108
|
const intents = { writes: [], deletes: toRemove.map(i => i.path), overwrites: [] };
|
|
109
|
-
await guardPlan(targetPath, intents, flags);
|
|
110
|
-
|
|
111
|
-
// Verificar audit logs que seriam perdidos
|
|
112
|
-
const auditDir = path.join(agentDir, "audit");
|
|
113
|
-
if (fs.existsSync(auditDir)) {
|
|
114
|
-
const auditCount = countFiles(auditDir);
|
|
115
|
-
if (auditCount > 0) {
|
|
116
|
-
console.log(`\n ⚠️ ${auditCount} log(s) de auditoria serão perdidos!`);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Verificar state que seria perdido
|
|
121
|
-
const stateDir = path.join(agentDir, "state");
|
|
122
|
-
if (fs.existsSync(stateDir)) {
|
|
123
|
-
const stateCount = countFiles(stateDir);
|
|
124
|
-
if (stateCount > 0) {
|
|
125
|
-
console.log(` ⚠️ ${stateCount} arquivo(s) de estado serão perdidos (mission_control, MEMORY)!`);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Modo PLAN: não faz nada
|
|
130
|
-
if (planMode) {
|
|
131
|
-
console.log("\n🔒 Modo PLAN (Read-Only). Nenhuma alteração feita.");
|
|
132
|
-
console.log(" Para desinstalar, rode: npx @fabioforest/openclaw uninstall --apply");
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
109
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
110
|
+
await executeAction({
|
|
111
|
+
actionName: "uninstall",
|
|
112
|
+
context: ctx,
|
|
113
|
+
flags,
|
|
114
|
+
intents,
|
|
115
|
+
targetPath,
|
|
116
|
+
skipAudit: true, // Audit manual pós-remoção
|
|
117
|
+
confirmationWord: "UNINSTALL",
|
|
118
|
+
planFn: async () => {
|
|
119
|
+
// Verificar audit logs que seriam perdidos
|
|
120
|
+
const auditDir = path.join(agentDir, "audit");
|
|
121
|
+
if (fs.existsSync(auditDir)) {
|
|
122
|
+
const auditCount = countFiles(auditDir);
|
|
123
|
+
if (auditCount > 0) {
|
|
124
|
+
console.log(`\n ⚠️ ${auditCount} log(s) de auditoria serão perdidos!`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
145
127
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
128
|
+
// Verificar state que seria perdido
|
|
129
|
+
const stateDir = path.join(agentDir, "state");
|
|
130
|
+
if (fs.existsSync(stateDir)) {
|
|
131
|
+
const stateCount = countFiles(stateDir);
|
|
132
|
+
if (stateCount > 0) {
|
|
133
|
+
console.log(` ⚠️ ${stateCount} arquivo(s) de estado serão perdidos (mission_control, MEMORY)!`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
executeFn: async () => {
|
|
138
|
+
// Backup opcional
|
|
139
|
+
if (!flags.force) {
|
|
140
|
+
const doBackup = flags.yes ? "s" : await ask("\n💾 Fazer backup antes de remover? (S/n): ");
|
|
141
|
+
if (doBackup.toLowerCase() !== "n") {
|
|
142
|
+
const backupName = `.agent.backup-${Date.now()}`;
|
|
143
|
+
const backupPath = path.join(targetPath, backupName);
|
|
144
|
+
try {
|
|
145
|
+
fs.cpSync(agentDir, backupPath, { recursive: true });
|
|
146
|
+
console.log(` ✅ Backup criado: ${backupName}/`);
|
|
147
|
+
} catch (err) {
|
|
148
|
+
console.error(` ⚠️ Falha no backup: ${err.message}`);
|
|
149
|
+
const cont = await ask(" Continuar sem backup? (y/N): ");
|
|
150
|
+
if (cont.toLowerCase() !== "y") {
|
|
151
|
+
throw new Error("Usuário abortou por falha no backup.");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
161
154
|
}
|
|
162
155
|
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
156
|
|
|
166
|
-
|
|
167
|
-
const audit = [getAuditHeader(ctx, "uninstall", flags)];
|
|
157
|
+
const audit = [getAuditHeader(ctx, "uninstall", flags)];
|
|
168
158
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
159
|
+
for (const item of toRemove) {
|
|
160
|
+
if (item.isDir) {
|
|
161
|
+
fs.rmSync(item.path, { recursive: true, force: true });
|
|
162
|
+
} else {
|
|
163
|
+
fs.unlinkSync(item.path);
|
|
164
|
+
}
|
|
165
|
+
console.log(` ✅ Removido: ${item.label}`);
|
|
166
|
+
audit.push(`- ACT: REMOVED ${item.label}`);
|
|
175
167
|
}
|
|
176
|
-
console.log(` ✅ Removido: ${item.label}`);
|
|
177
|
-
audit.push(`- ACT: REMOVED ${item.label}`);
|
|
178
|
-
}
|
|
179
168
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
169
|
+
console.log("\n✨ OpenClaw desinstalado com sucesso!");
|
|
170
|
+
console.log(" Para reinstalar: npx @fabioforest/openclaw init --apply\n");
|
|
171
|
+
|
|
172
|
+
// Gravar audit no diretório pai (já que .agent/ foi removido)
|
|
173
|
+
if (flags.audit !== false) {
|
|
174
|
+
const filename = `openclaw-uninstall-${new Date().toISOString().replace(/[:.]/g, "-")}.md`;
|
|
175
|
+
const auditPath = path.join(targetPath, filename);
|
|
176
|
+
try {
|
|
177
|
+
fs.writeFileSync(auditPath, audit.join("\n") + "\n", "utf8");
|
|
178
|
+
console.log(` 📝 Log de auditoria: ${filename}`);
|
|
179
|
+
} catch (e) { /* silencioso */ }
|
|
180
|
+
}
|
|
191
181
|
}
|
|
192
|
-
|
|
193
|
-
} catch (err) {
|
|
194
|
-
console.error(`\n❌ Falha: ${err.message}`);
|
|
195
|
-
process.exit(1);
|
|
196
|
-
}
|
|
182
|
+
});
|
|
197
183
|
}
|
|
198
184
|
|
|
199
185
|
module.exports = { run };
|
package/lib/cli/update.js
CHANGED
|
@@ -14,8 +14,7 @@ const path = require("path");
|
|
|
14
14
|
const crypto = require("crypto");
|
|
15
15
|
const readline = require("readline");
|
|
16
16
|
const { detectContext, getAuditHeader } = require("../context");
|
|
17
|
-
const {
|
|
18
|
-
const { guardPlan } = require("../utils/scope_guard");
|
|
17
|
+
const { executeAction } = require("../core/orchestrator");
|
|
19
18
|
|
|
20
19
|
// Caminho dos templates incluídos no pacote
|
|
21
20
|
const TEMPLATES_DIR = path.join(__dirname, "..", "..", "templates", ".agent");
|
|
@@ -29,10 +28,7 @@ function safeRel(targetPath, p) {
|
|
|
29
28
|
return path.relative(targetPath, p);
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
// writeAudit
|
|
33
|
-
function writeAudit(targetPath, lines, flags) {
|
|
34
|
-
writeCliAudit(targetPath, lines, flags, "update");
|
|
35
|
-
}
|
|
31
|
+
// Removida prop writeAudit (o orquestrador ou o executeFn fará isso manualmente ou deixaremos rolar)
|
|
36
32
|
|
|
37
33
|
/**
|
|
38
34
|
* Calcula o SHA-256 de um arquivo (Utilitário mantido)
|
|
@@ -124,112 +120,113 @@ async function run({ targetPath, flags }) {
|
|
|
124
120
|
process.exit(1);
|
|
125
121
|
}
|
|
126
122
|
|
|
127
|
-
// 1. Planejar
|
|
123
|
+
// 1. Planejar arrays
|
|
128
124
|
const actions = planUpdates(TEMPLATES_DIR, agentDir);
|
|
129
|
-
const audit = [getAuditHeader(ctx, "update", flags)];
|
|
130
125
|
|
|
131
|
-
//
|
|
126
|
+
// Mapear intencionalidades
|
|
132
127
|
const intents = { writes: [], deletes: [], overwrites: [] };
|
|
133
128
|
for (const a of actions.added) intents.writes.push(a.dest);
|
|
134
129
|
for (const a of actions.updated) intents.overwrites.push(a.dest);
|
|
135
|
-
await guardPlan(targetPath, intents, flags);
|
|
136
|
-
|
|
137
|
-
// 2. Exibir Plano
|
|
138
|
-
console.log(`\n🧭 Plano de Atualização (${planMode ? "SIMULAÇÃO" : "APPLY"}):\n`);
|
|
139
|
-
console.log(` Contexto: ${ctx.env} | IDE: ${ctx.ide}\n`);
|
|
140
130
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
131
|
+
await executeAction({
|
|
132
|
+
actionName: "update",
|
|
133
|
+
context: ctx,
|
|
134
|
+
flags,
|
|
135
|
+
intents,
|
|
136
|
+
targetPath,
|
|
137
|
+
skipConfirm: true, // Manter o loop de override manual
|
|
138
|
+
skipAudit: true, // Escrevemos manual no final por causa dos arquivos "skipped" interativos
|
|
139
|
+
planFn: async () => {
|
|
140
|
+
console.log(`\n🧭 Plano de Atualização:\n`);
|
|
141
|
+
console.log(` Contexto: ${ctx.env.platform} | IDE: ${ctx.ide}\n`);
|
|
142
|
+
|
|
143
|
+
if (actions.added.length > 0) {
|
|
144
|
+
console.log(`📄 Novos (${actions.added.length}):`);
|
|
145
|
+
actions.added.forEach(a => console.log(` + CREATE ${safeRel(targetPath, a.dest)}`));
|
|
146
|
+
}
|
|
147
|
+
if (actions.updated.length > 0) {
|
|
148
|
+
console.log(`\n🔄 Modificados (${actions.updated.length}):`);
|
|
149
|
+
actions.updated.forEach(a => {
|
|
150
|
+
console.log(` ~ UPDATE ${safeRel(targetPath, a.dest)} (Exige confirmação interativa)`);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
if (actions.skipped.length > 0) {
|
|
154
|
+
console.log(`\n⏭️ Ignorados (${actions.skipped.length} arquivos idênticos)`);
|
|
155
|
+
}
|
|
154
156
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
if (actions.added.length === 0 && actions.updated.length === 0) {
|
|
158
|
+
console.log("\n✅ Tudo atualizado. Nenhuma alteração necessária.");
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
executeFn: async () => {
|
|
162
|
+
if (actions.added.length === 0 && actions.updated.length === 0) {
|
|
163
|
+
return; // Nothing to apply
|
|
164
|
+
}
|
|
159
165
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
+
// Confirmação inicial (se customizado)
|
|
167
|
+
if (!flags.yes && actions.updated.length === 0) {
|
|
168
|
+
const ok = await ask("\nAplicar e copiar arquivos novos? (y/N): ");
|
|
169
|
+
if (ok.toLowerCase() !== "y") {
|
|
170
|
+
console.log("⏹️ Cancelado.");
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
166
174
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
+
// Criar diretórios necessários
|
|
176
|
+
function ensureDir(p) {
|
|
177
|
+
const dir = path.dirname(p);
|
|
178
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
179
|
+
}
|
|
175
180
|
|
|
176
|
-
|
|
177
|
-
try {
|
|
178
|
-
console.log("\n🚀 Executando atualizações...");
|
|
181
|
+
const audit = [getAuditHeader(ctx, "update", flags)];
|
|
179
182
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
183
|
+
// Action: Added
|
|
184
|
+
for (const action of actions.added) {
|
|
185
|
+
ensureDir(action.dest);
|
|
186
|
+
fs.copyFileSync(action.src, action.dest);
|
|
187
|
+
audit.push(`- ACT: CREATED ${safeRel(targetPath, action.dest)}`);
|
|
188
|
+
}
|
|
185
189
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
audit.push(`- ACT: CREATED ${safeRel(targetPath, action.dest)}`);
|
|
191
|
-
}
|
|
190
|
+
// Action: Updated (Conflict Resolver)
|
|
191
|
+
for (const action of actions.updated) {
|
|
192
|
+
ensureDir(action.dest);
|
|
193
|
+
const rPath = safeRel(targetPath, action.dest);
|
|
192
194
|
|
|
193
|
-
|
|
194
|
-
for (const action of actions.updated) {
|
|
195
|
-
ensureDir(action.dest);
|
|
196
|
-
const rPath = safeRel(targetPath, action.dest);
|
|
195
|
+
let overwrite = flags.yes || flags.force;
|
|
197
196
|
|
|
198
|
-
|
|
197
|
+
if (!overwrite) {
|
|
198
|
+
console.log(`\n⚠️ CONFLITO DETECTADO: ${rPath}`);
|
|
199
|
+
console.log("------------------------------------------------");
|
|
200
|
+
const oldContent = fs.readFileSync(action.dest, "utf-8");
|
|
201
|
+
const newContent = fs.readFileSync(action.src, "utf-8");
|
|
202
|
+
console.log(simpleDiff(oldContent, newContent));
|
|
203
|
+
console.log("------------------------------------------------");
|
|
199
204
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const oldContent = fs.readFileSync(action.dest, "utf-8");
|
|
204
|
-
const newContent = fs.readFileSync(action.src, "utf-8");
|
|
205
|
-
console.log(simpleDiff(oldContent, newContent));
|
|
206
|
-
console.log("------------------------------------------------");
|
|
205
|
+
const ans = await ask(`Substituir a sua versão de ${rPath} pelo código acima? [y/N]: `);
|
|
206
|
+
overwrite = ans.toLowerCase() === "y";
|
|
207
|
+
}
|
|
207
208
|
|
|
208
|
-
|
|
209
|
-
|
|
209
|
+
if (overwrite) {
|
|
210
|
+
const backupPath = action.dest + ".bak";
|
|
211
|
+
fs.copyFileSync(action.dest, backupPath);
|
|
212
|
+
fs.copyFileSync(action.src, action.dest);
|
|
213
|
+
console.log(`✅ Sobrescrito: ${rPath} (Backup guardado localmente: .bak)`);
|
|
214
|
+
audit.push(`- ACT: UPDATED ${rPath} (Backup: ${path.basename(backupPath)})`);
|
|
215
|
+
} else {
|
|
216
|
+
console.log(`⏭️ Ignorado (Mantido customização em ${rPath})`);
|
|
217
|
+
audit.push(`- ACT: SKIPPED UPDATE FOR CUSTOMIZED FILE ${rPath}`);
|
|
218
|
+
}
|
|
210
219
|
}
|
|
211
220
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
} else {
|
|
219
|
-
console.log(`⏭️ Ignorado (Mantido customização em ${rPath})`);
|
|
220
|
-
audit.push(`- ACT: SKIPPED UPDATE FOR CUSTOMIZED FILE ${rPath}`);
|
|
221
|
+
console.log("\n✨ Atualização concluída com sucesso!");
|
|
222
|
+
|
|
223
|
+
// Auditing manual
|
|
224
|
+
if (flags.audit !== false) {
|
|
225
|
+
const { writeCliAudit } = require("../utils/audit-writer");
|
|
226
|
+
writeCliAudit(targetPath, audit, flags, "update");
|
|
221
227
|
}
|
|
222
228
|
}
|
|
223
|
-
|
|
224
|
-
console.log("\n✨ Atualização concluída com sucesso!");
|
|
225
|
-
writeAudit(targetPath, audit, flags);
|
|
226
|
-
|
|
227
|
-
} catch (err) {
|
|
228
|
-
console.error(`\n❌ Falha na execução: ${err.message}`);
|
|
229
|
-
audit.push(`\n## ERROR: ${err.message}`);
|
|
230
|
-
writeAudit(targetPath, audit, flags);
|
|
231
|
-
process.exit(1);
|
|
232
|
-
}
|
|
229
|
+
});
|
|
233
230
|
}
|
|
234
231
|
|
|
235
232
|
module.exports = { run, fileHash };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Context Engine do OpenClaw AI OS
|
|
5
|
+
*
|
|
6
|
+
* Responsável por gerenciar, carregar e persistir o conhecimento e estado de operação:
|
|
7
|
+
* 1. system.json - Variáveis de ambiente, config, OS, Docker
|
|
8
|
+
* 2. workspace.json - Mapeamento de linguagens, dependências, arquivos principais, histórico de testes
|
|
9
|
+
* 3. user.json - Preferências, IDE, token settings, perfil de agressividade do AI
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require("fs");
|
|
13
|
+
const path = require("path");
|
|
14
|
+
|
|
15
|
+
class ContextEngine {
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} projectRoot - Raiz do projeto (onde fica .agent/)
|
|
18
|
+
*/
|
|
19
|
+
constructor(projectRoot) {
|
|
20
|
+
this.projectRoot = projectRoot;
|
|
21
|
+
this.contextDir = path.join(projectRoot, ".agent", "context");
|
|
22
|
+
|
|
23
|
+
// Cria o diretório de contexto caso não exista (independente do init)
|
|
24
|
+
if (!fs.existsSync(this.contextDir)) {
|
|
25
|
+
try {
|
|
26
|
+
fs.mkdirSync(this.contextDir, { recursive: true });
|
|
27
|
+
} catch (err) {
|
|
28
|
+
// Ignorar em diretórios hostis onde openclaw não está armado
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
_getFilePath(type) {
|
|
34
|
+
return path.join(this.contextDir, `${type}.json`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Carrega um fragmento do contexto ou retorna defaults
|
|
39
|
+
* @param {string} type - "system", "workspace" ou "user"
|
|
40
|
+
*/
|
|
41
|
+
load(type) {
|
|
42
|
+
const file = this._getFilePath(type);
|
|
43
|
+
if (fs.existsSync(file)) {
|
|
44
|
+
try {
|
|
45
|
+
return JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
46
|
+
} catch (err) {
|
|
47
|
+
// Falha de parse, retorna default seguro
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Salva ou sobrepõe as flags no contexto
|
|
56
|
+
* @param {string} type - "system", "workspace" ou "user"
|
|
57
|
+
* @param {Object} data - Objeto de dados (será feito um object merge shallow com key overwrites)
|
|
58
|
+
*/
|
|
59
|
+
save(type, data) {
|
|
60
|
+
// Se .agent não existe ou não foi inicializado, silenciosamente bypass (para CLI cmds sujos antes do IDE_INSTALL)
|
|
61
|
+
if (!fs.existsSync(this.contextDir)) return false;
|
|
62
|
+
|
|
63
|
+
const current = this.load(type);
|
|
64
|
+
const merged = { ...current, ...data };
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
fs.writeFileSync(this._getFilePath(type), JSON.stringify(merged, null, 2), "utf-8");
|
|
68
|
+
return true;
|
|
69
|
+
} catch (err) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Agrega todos os arquivos de contexto para compor a mentalidade da query.
|
|
76
|
+
*/
|
|
77
|
+
getFullContext() {
|
|
78
|
+
return {
|
|
79
|
+
system: this.load("system"),
|
|
80
|
+
workspace: this.load("workspace"),
|
|
81
|
+
user: this.load("user"),
|
|
82
|
+
meta: {
|
|
83
|
+
timestamp: new Date().toISOString()
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = ContextEngine;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Core Orchestrator do OpenClaw AI OS
|
|
5
|
+
*
|
|
6
|
+
* Atua como o "Kernel" do sistema, centralizando o ciclo de vida:
|
|
7
|
+
* INSPECT -> PLAN -> CONSENT -> APPLY -> AUDIT
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const readline = require("readline");
|
|
11
|
+
const fs = require("fs");
|
|
12
|
+
const path = require("path");
|
|
13
|
+
const { guardPlan } = require("../utils/scope_guard");
|
|
14
|
+
const { writeCliAudit } = require("../utils/audit-writer");
|
|
15
|
+
|
|
16
|
+
function ask(q) {
|
|
17
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
18
|
+
return new Promise((res) => rl.question(q, (ans) => { rl.close(); res(ans.trim()); }));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Executa uma ação unificada no OpenClaw AI OS.
|
|
23
|
+
* Garante que regras de segurança, escopo e auditoria sejam aplicadas.
|
|
24
|
+
*
|
|
25
|
+
* @param {Object} options
|
|
26
|
+
* @param {string} options.actionName - Nome da ação (ex: "ide install", "uninstall")
|
|
27
|
+
* @param {Object} options.context - Contexto do workspace/módulo coletado
|
|
28
|
+
* @param {Object} options.flags - Flags da CLI (--apply, --force, --yes)
|
|
29
|
+
* @param {Object} options.intents - { writes: [], deletes: [], overwrites: [] }
|
|
30
|
+
* @param {Function} options.executeFn - Função assíncrona que aplica as mudanças de fato
|
|
31
|
+
* @param {Function} options.planFn - Função de exibição do plano (read-only)
|
|
32
|
+
* @param {string} options.targetPath - Root path do projeto
|
|
33
|
+
* @param {boolean} options.skipAudit - Se true, não grava o audit log no .agent/audit (útil para uninstall)
|
|
34
|
+
* @param {boolean} options.skipConfirm - Se true, pula o prompt padrão de apply (útil para per-file diff loops)
|
|
35
|
+
* @returns {boolean} true se executado, false se cancelado ou em modo plan
|
|
36
|
+
*/
|
|
37
|
+
async function executeAction({
|
|
38
|
+
actionName,
|
|
39
|
+
context,
|
|
40
|
+
flags,
|
|
41
|
+
intents = { writes: [], deletes: [], overwrites: [] },
|
|
42
|
+
executeFn,
|
|
43
|
+
planFn,
|
|
44
|
+
confirmationWord = null,
|
|
45
|
+
targetPath,
|
|
46
|
+
skipAudit = false,
|
|
47
|
+
skipConfirm = false
|
|
48
|
+
}) {
|
|
49
|
+
const planMode = !flags.apply;
|
|
50
|
+
|
|
51
|
+
// 1. INSPECT / PLAN - Scope Guard
|
|
52
|
+
await guardPlan(targetPath, intents, flags);
|
|
53
|
+
|
|
54
|
+
// Exibir o plano real
|
|
55
|
+
if (planFn && typeof planFn === "function") {
|
|
56
|
+
await planFn();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (planMode) {
|
|
60
|
+
console.log(`\n🔒 Modo PLAN (Read-Only). Nenhuma alteração feita.`);
|
|
61
|
+
console.log(` Para aplicar, rode com a flag --apply`);
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 2. CONSENT
|
|
66
|
+
if (!flags.yes && !skipConfirm) {
|
|
67
|
+
if (confirmationWord) {
|
|
68
|
+
const confirm = await ask(`\n⚠️ Ação destrutiva requer confirmação forte. Digite '${confirmationWord}' para confirmar: `);
|
|
69
|
+
if (confirm !== confirmationWord) {
|
|
70
|
+
console.log("⏹️ Cancelado. Nenhuma alteração feita.");
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
const confirm = await ask(`\n⚠️ Deseja APLICAR as alterações acima? (s/N): `);
|
|
75
|
+
if (confirm.toLowerCase() !== "s") {
|
|
76
|
+
console.log("⏹️ Cancelado. Nenhuma alteração feita.");
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 3. APPLY
|
|
83
|
+
console.log(`\n⚙️ Executando [${actionName}]...`);
|
|
84
|
+
try {
|
|
85
|
+
await executeFn();
|
|
86
|
+
console.log(`✅ Ação [${actionName}] concluída com sucesso.`);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.error(`❌ Erro ao executar [${actionName}]:`, err.message);
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 4. AUDIT
|
|
93
|
+
if (!skipAudit && flags.audit !== false) {
|
|
94
|
+
try {
|
|
95
|
+
const auditPayload = [
|
|
96
|
+
`--- AUDIT LOG: ${actionName} ---`,
|
|
97
|
+
`Date: ${new Date().toISOString()}`,
|
|
98
|
+
`User Flags: ${JSON.stringify(flags)}`,
|
|
99
|
+
`Intents: writes=${intents.writes.length} overwrites=${intents.overwrites.length} deletes=${intents.deletes.length}`,
|
|
100
|
+
`Status: SUCCESS`
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
// Reutiliza a função de escrita de log da CLI
|
|
104
|
+
writeCliAudit(targetPath, auditPayload, flags, actionName);
|
|
105
|
+
} catch (e) {
|
|
106
|
+
console.log("⚠️ Não foi possível escrever o audit log.");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = { executeAction };
|
package/lib/utils/scope_guard.js
CHANGED
|
@@ -36,8 +36,19 @@ function isPathInSafeScope(targetPath, fileToMutate) {
|
|
|
36
36
|
// allow openclaw.json default config
|
|
37
37
|
if (absoluteMutate === path.join(targetPath, "openclaw.json")) return true;
|
|
38
38
|
|
|
39
|
+
// permitemos as configs nativas da IDE copiadas no install
|
|
40
|
+
const safeIdioms = [
|
|
41
|
+
path.join(targetPath, ".cursorrules"),
|
|
42
|
+
path.join(targetPath, ".github"),
|
|
43
|
+
path.join(targetPath, ".cursor")
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
if (safeIdioms.some(idiom => absoluteMutate.startsWith(idiom))) return true;
|
|
47
|
+
|
|
39
48
|
// Retorna true se começa com o caminho the escopo.
|
|
40
|
-
|
|
49
|
+
if (absoluteMutate.startsWith(safeScope)) return true;
|
|
50
|
+
|
|
51
|
+
return false;
|
|
41
52
|
}
|
|
42
53
|
|
|
43
54
|
/**
|
package/package.json
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Criação de um snapshot do contexto do workspace atual para o router
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Workspace Snapshot
|
|
6
|
+
|
|
7
|
+
## Objetivo
|
|
8
|
+
Analisar e capturar o estado global do projeto (stack, dependências, rotas, testes e pontos cegos) gerando um artefato `context.json` otimizado. Isso alimenta os roteadores de agentes reduzindo solicitações repetitivas de entendimento e consumos de Token por scans demorados.
|
|
9
|
+
|
|
10
|
+
## Fluxo
|
|
11
|
+
1. Ler `.gitignore` e `package.json` (ou similares de outras linguagens).
|
|
12
|
+
2. Escanear diretórios chave (src, lib, tests, config).
|
|
13
|
+
3. Detectar frameworks, bibliotecas primárias e scripts customizados.
|
|
14
|
+
4. Identificar zonas de risco (pastas legacy, componentes em migração, vulnerabilidades).
|
|
15
|
+
5. Escrever/Atualizar `.agent/context/context.json` e notificar roteadores.
|
|
16
|
+
|
|
17
|
+
## Regras
|
|
18
|
+
- Nunca ler arquivos grandes ignorados como `node_modules` e pastas compiladas.
|
|
19
|
+
- O resultado no JSON não deve passar de 5kb (resumo em alto nível) para manter os tokens baixos no contexto das prompts do LLM.
|
|
20
|
+
- Exigir aprovação de Apply para gravar a imagem do Snapshot.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# OpenClaw - Chat Router Workflow
|
|
2
|
+
# Roteamento de Entrada para o Chat da IDE
|
|
3
|
+
|
|
4
|
+
**description**: Workflow de entrada padrão para o modelo ao iniciar um chat. Força o reconhecimento de contexto antes de qualquer ação.
|
|
5
|
+
|
|
6
|
+
## 1. Inspect
|
|
7
|
+
Sempre comece investigando o estado atual do repositório, procurando pelas regras e diretrizes estabelecidas no `.agent/rules/`. Leia `openclaw.json` para definir o `targetPath` base.
|
|
8
|
+
|
|
9
|
+
## 2. Plan & Select Skill
|
|
10
|
+
Com base no seu objetivo e nas skills ativas listadas em `.agent/skills/`, escolha a melhor abordagem e desenvolva um rascunho. Comunique o plano detalhado no chat.
|
|
11
|
+
|
|
12
|
+
## 3. Consent
|
|
13
|
+
Nunca modifique arquivos críticos, delete diretórios ou crie instâncias de serviço sem perguntar ativamente e aguardar o "Sim" explicíto do usuário sobre o Plano (Consent-First).
|
|
14
|
+
|
|
15
|
+
## 4. Apply
|
|
16
|
+
Proceda com a implementação guiando-se estritamente pela sua proposta. Mantenha os arquivos enxutos (pequenos) e faça checagens consistentes.
|
|
17
|
+
|
|
18
|
+
## 5. Audit
|
|
19
|
+
Deixe um sumário claro do que foi modificado como prova da sessão de alterações. Utilize `lib/utils/audit-writer.js` se for chamado via OpenClaw CLI ou relate concisamente.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: openclaw-global-system-rules
|
|
3
|
+
globs: *
|
|
4
|
+
---
|
|
5
|
+
# OpenClaw AI OS - Global Rules
|
|
6
|
+
Você é controlado pelo sistema OpenClaw AI OS instalado neste workspace. Seu comportamento principal deve ser "Consent-First".
|
|
7
|
+
|
|
8
|
+
- Ler as políticas do diretório `.agent/rules` ANTES de agir.
|
|
9
|
+
- Seguir o workflow padrão `.agent/workflows/chat-router.md` de Roteamento (Inspect -> Plan -> Consent -> Apply -> Audit).
|
|
10
|
+
- Jamais mutar, deletar ou sobrescrever arquivos sem o DEVIDO Consentimento do Usuário atestado em Plano de Ação.
|
|
11
|
+
- Consultar e preservar `.agent/state/MEMORY.md` com as decisões de projeto.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# OpenClaw AI OS - Global Rules
|
|
2
|
+
Você é controlado pelo sistema OpenClaw AI OS instalado neste workspace. Seu comportamento principal deve ser "Consent-First".
|
|
3
|
+
|
|
4
|
+
- Ler as políticas do diretório `.agent/rules` ANTES de agir.
|
|
5
|
+
- Seguir o workflow padrão `.agent/workflows/chat-router.md` de Roteamento (Inspect -> Plan -> Consent -> Apply -> Audit).
|
|
6
|
+
- Jamais mutar, deletar ou sobrescrever arquivos sem o DEVIDO Consentimento do Usuário atestado em Plano de Ação.
|
|
7
|
+
- Consultar e preservar `.agent/state/MEMORY.md` com as decisões de projeto.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# OpenClaw AI OS - Copilot Instructions
|
|
2
|
+
Você faz parte do ecosistema OpenClaw instalado neste projeto, que reforça arquiteturas de agentes seguras.
|
|
3
|
+
|
|
4
|
+
## 1. Safety Protocols
|
|
5
|
+
Respeite e leia a diretriz de Consentimento: `.agent/rules/CONSENT_FIRST.md`. Todas as mutações do projeto exigem aprovações prévias visuais baseadas em um Plano (Inspect -> Plan -> Consent -> Apply -> Audit).
|
|
6
|
+
|
|
7
|
+
## 2. Tools & Skills
|
|
8
|
+
Sempre verifique extensões instaladas e documentação do projeto via `.agent/skills/` ou referências locais do diretório `.agent` para aplicar soluções sob o escopo esperado do mantenedor.
|
|
9
|
+
|
|
10
|
+
## 3. Comportamento
|
|
11
|
+
Preserve a integridade do sistema do usuário e nunca faça deleções não solicitadas em arquivos da pipeline e fora das permissões.
|