ai-maestro 1.0.0
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/CHANGELOG.md +11 -0
- package/LICENSE +15 -0
- package/README.md +727 -0
- package/bin/maestro.mjs +246 -0
- package/package.json +29 -0
- package/src/checkpoint.mjs +102 -0
- package/src/codex-diagnostics.mjs +682 -0
- package/src/codex-home-inspect.mjs +252 -0
- package/src/codex-home.mjs +258 -0
- package/src/commands.mjs +809 -0
- package/src/config.mjs +11 -0
- package/src/debug.mjs +164 -0
- package/src/encoding-guard.mjs +125 -0
- package/src/engines/ai-router-engine.mjs +37 -0
- package/src/engines/codex-engine.mjs +21 -0
- package/src/engines/engine.mjs +21 -0
- package/src/engines/registry.mjs +29 -0
- package/src/files.mjs +65 -0
- package/src/format.mjs +132 -0
- package/src/lock.mjs +439 -0
- package/src/memory-log.mjs +18 -0
- package/src/memory.mjs +1 -0
- package/src/orchestration/attempt-chain-runtime.mjs +627 -0
- package/src/orchestration/budget-manager.mjs +121 -0
- package/src/orchestration/capability-registry.mjs +108 -0
- package/src/orchestration/consolidator.mjs +772 -0
- package/src/orchestration/delegation-executor.mjs +262 -0
- package/src/orchestration/delegation-manager.mjs +116 -0
- package/src/orchestration/deployment-intent.mjs +36 -0
- package/src/orchestration/engine-history.mjs +110 -0
- package/src/orchestration/engine-policy.mjs +73 -0
- package/src/orchestration/engine-selector.mjs +187 -0
- package/src/orchestration/execution-context.mjs +45 -0
- package/src/orchestration/failure-classifier.mjs +136 -0
- package/src/orchestration/failure-evidence.mjs +237 -0
- package/src/orchestration/fallback-chain-lock.mjs +217 -0
- package/src/orchestration/fallback-chain-trail.mjs +218 -0
- package/src/orchestration/fallback-executor.mjs +146 -0
- package/src/orchestration/fallback-graph.mjs +106 -0
- package/src/orchestration/fallback-recommendation.mjs +73 -0
- package/src/orchestration/file-conflict-detector.mjs +126 -0
- package/src/orchestration/host-validation.mjs +241 -0
- package/src/orchestration/orchestration-loop.mjs +1971 -0
- package/src/orchestration/orchestration-runtime.mjs +1019 -0
- package/src/orchestration/orchestration-scheduler.mjs +135 -0
- package/src/orchestration/orchestration-trail.mjs +192 -0
- package/src/orchestration/preflight.mjs +212 -0
- package/src/orchestration/provider-health.mjs +566 -0
- package/src/orchestration/provider-router.mjs +352 -0
- package/src/orchestration/rc-check-adapters.mjs +817 -0
- package/src/orchestration/rc-check.mjs +544 -0
- package/src/orchestration/rc-policy.mjs +200 -0
- package/src/orchestration/runtime-gate.mjs +146 -0
- package/src/orchestration/sector-managers.mjs +215 -0
- package/src/orchestration/self-hosting-canary.mjs +231 -0
- package/src/orchestration/self-hosting-readiness.mjs +244 -0
- package/src/orchestration/spec-planner.mjs +877 -0
- package/src/orchestration/subtask-planner.mjs +176 -0
- package/src/orchestration/task-classifier.mjs +241 -0
- package/src/orchestration/verifier.mjs +1608 -0
- package/src/orchestration-commands.mjs +279 -0
- package/src/planner.mjs +38 -0
- package/src/project.mjs +1 -0
- package/src/run-diagnostics.mjs +641 -0
- package/src/runner.mjs +243 -0
- package/src/safety.mjs +116 -0
- package/src/schema.mjs +371 -0
- package/src/session-commands.mjs +521 -0
- package/src/shell.mjs +93 -0
- package/src/smart-planner.mjs +249 -0
- package/src/task-commands.mjs +1182 -0
- package/src/tasks.mjs +134 -0
- package/src/ui/commands.mjs +76 -0
- package/src/ui/events.mjs +45 -0
- package/src/ui/public/app.js +600 -0
- package/src/ui/public/index.html +88 -0
- package/src/ui/public/style.css +460 -0
- package/src/ui/server.mjs +112 -0
- package/src/ui/state.mjs +504 -0
- package/src/usage.mjs +178 -0
- package/src/workspace-diff.mjs +228 -0
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { CONFIG } from './config.mjs';
|
|
4
|
+
import { pathExists } from './files.mjs';
|
|
5
|
+
import { execCmdFile, execFileRaw } from './shell.mjs';
|
|
6
|
+
import { runHostValidation, resolveNpmInvocation } from './orchestration/host-validation.mjs';
|
|
7
|
+
import {
|
|
8
|
+
runCodex, getDefaultCodexCmdPath, getDefaultCodexJsPath, writeCodexCompare,
|
|
9
|
+
getCodexComparePath, resolveCodexInvocationMode
|
|
10
|
+
} from './commands.mjs';
|
|
11
|
+
import { DEFAULT_CODEX_PROFILE_CONFIG, ensureCodexHome } from './codex-home.mjs';
|
|
12
|
+
import {
|
|
13
|
+
inspectSensitiveContent, copyDirectorySafe, restoreDirectoryFromBackup,
|
|
14
|
+
safeProbeName, timestampForPath, inspectHome, summarizeHomeIssues, classifyInventoryPath, sha256File,
|
|
15
|
+
readSafeStructure
|
|
16
|
+
} from './codex-home-inspect.mjs';
|
|
17
|
+
import { summarizeText, normalizeReadContent, detectField, doctorLine } from './format.mjs';
|
|
18
|
+
import { logMemory, memoryDecision } from './memory-log.mjs';
|
|
19
|
+
import { recordUsage } from './usage.mjs';
|
|
20
|
+
import { getCodexDebugInfo, formatEffectiveRuntimeLines } from './debug.mjs';
|
|
21
|
+
|
|
22
|
+
const SANDBOX_ACL_FAILURE_PATTERN = /helper_unknown_error|apply deny-read acls/i;
|
|
23
|
+
|
|
24
|
+
export async function codexHomeCommand() {
|
|
25
|
+
const result = await ensureCodexHome();
|
|
26
|
+
doctorLine('OK', 'Codex home', result.codexHome);
|
|
27
|
+
doctorLine('OK', 'Config path', result.configPath);
|
|
28
|
+
doctorLine((result.checks.hasProvider9router || result.checks.hasProfile9router) ? 'OK' : 'ERRO', 'Provider 9router', result.checks.hasProvider9router || result.checks.hasProfile9router);
|
|
29
|
+
doctorLine(result.checks.hasProfile9router ? 'OK' : 'ERRO', 'Profile 9router', result.checks.hasProfile9router);
|
|
30
|
+
doctorLine(result.checks.mixesDefaultPermissionsAndSandboxMode ? 'ERRO' : 'OK', 'default_permissions + sandbox_mode mix', result.checks.mixesDefaultPermissionsAndSandboxMode);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function codexHomeDiff() {
|
|
34
|
+
const normal = await inspectHome(CONFIG.normalCodexHome);
|
|
35
|
+
const maestro = await inspectHome(CONFIG.codexHome);
|
|
36
|
+
const normalPaths = new Set(normal.files.map(file => file.path));
|
|
37
|
+
const maestroPaths = new Set(maestro.files.map(file => file.path));
|
|
38
|
+
const payload = {
|
|
39
|
+
timestamp: new Date().toISOString(),
|
|
40
|
+
normalHome: CONFIG.normalCodexHome,
|
|
41
|
+
maestroHome: CONFIG.codexHome,
|
|
42
|
+
normal: { ...normal, summary: summarizeHomeIssues(normal) },
|
|
43
|
+
maestro: { ...maestro, summary: summarizeHomeIssues(maestro) },
|
|
44
|
+
onlyInNormal: [...normalPaths].filter(item => !maestroPaths.has(item)).sort(),
|
|
45
|
+
onlyInMaestro: [...maestroPaths].filter(item => !normalPaths.has(item)).sort(),
|
|
46
|
+
common: [...normalPaths].filter(item => maestroPaths.has(item)).sort()
|
|
47
|
+
};
|
|
48
|
+
await fs.mkdir(CONFIG.maestroPath, { recursive: true });
|
|
49
|
+
const diffPath = path.join(CONFIG.maestroPath, 'codex-home-diff.json');
|
|
50
|
+
await fs.writeFile(diffPath, JSON.stringify(payload, null, 2));
|
|
51
|
+
await logMemory('maestro codex-home-diff compared normal and Maestro Codex homes. Maestro summary: ' + JSON.stringify(payload.maestro.summary) + '.');
|
|
52
|
+
console.log('Codex home diff saved: ' + diffPath);
|
|
53
|
+
console.log('Normal files: ' + normal.files.length);
|
|
54
|
+
console.log('Maestro files: ' + maestro.files.length);
|
|
55
|
+
console.log('Maestro summary: ' + JSON.stringify(payload.maestro.summary));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function codexHomeInventory() {
|
|
59
|
+
const normal = await inspectHome(CONFIG.normalCodexHome);
|
|
60
|
+
const maestro = await inspectHome(CONFIG.codexHome);
|
|
61
|
+
const byPath = new Map();
|
|
62
|
+
for (const file of normal.files) {
|
|
63
|
+
byPath.set(file.path, { path: file.path, normal: file, maestro: null });
|
|
64
|
+
}
|
|
65
|
+
for (const file of maestro.files) {
|
|
66
|
+
const row = byPath.get(file.path) || { path: file.path, normal: null, maestro: null };
|
|
67
|
+
row.maestro = file;
|
|
68
|
+
byPath.set(file.path, row);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const entries = [];
|
|
72
|
+
for (const row of [...byPath.values()].sort((a, b) => a.path.localeCompare(b.path))) {
|
|
73
|
+
const source = row.normal || row.maestro || {};
|
|
74
|
+
const classified = classifyInventoryPath(row.path, source);
|
|
75
|
+
const normalPath = row.normal ? path.join(CONFIG.normalCodexHome, row.path) : null;
|
|
76
|
+
const maestroPath = row.maestro ? path.join(CONFIG.codexHome, row.path) : null;
|
|
77
|
+
const canHashNormal = row.normal && row.normal.type === 'file' && !/(sensitive|cache|unknown-sensitive)/.test(classified.classification);
|
|
78
|
+
const canHashMaestro = row.maestro && row.maestro.type === 'file' && !/(sensitive|cache|unknown-sensitive)/.test(classified.classification);
|
|
79
|
+
const existsOnlyNormal = !!row.normal && !row.maestro;
|
|
80
|
+
const differs = row.normal && row.maestro && row.normal.type === 'file' && row.maestro.type === 'file' && row.normal.size !== row.maestro.size;
|
|
81
|
+
const safeCopyCandidate = classified.safeCopyCandidate && !!row.normal && row.normal.type === 'file' && (existsOnlyNormal || differs);
|
|
82
|
+
const entry = {
|
|
83
|
+
path: row.path,
|
|
84
|
+
existsInNormal: !!row.normal,
|
|
85
|
+
existsInMaestro: !!row.maestro,
|
|
86
|
+
normalSize: row.normal && row.normal.type === 'file' ? row.normal.size : null,
|
|
87
|
+
maestroSize: row.maestro && row.maestro.type === 'file' ? row.maestro.size : null,
|
|
88
|
+
extension: path.extname(row.path).toLowerCase(),
|
|
89
|
+
classification: classified.classification,
|
|
90
|
+
safeCopyCandidate,
|
|
91
|
+
reason: safeCopyCandidate ? classified.reason : classified.reason + (row.normal && row.maestro && !differs ? '; already present with same size or not a missing candidate' : ''),
|
|
92
|
+
normalSha256: canHashNormal ? await sha256File(normalPath) : null,
|
|
93
|
+
maestroSha256: canHashMaestro ? await sha256File(maestroPath) : null,
|
|
94
|
+
normalStructure: safeCopyCandidate ? await readSafeStructure(CONFIG.normalCodexHome, row.path, classified.classification) : null
|
|
95
|
+
};
|
|
96
|
+
if (entry.normalSha256 && entry.maestroSha256 && entry.normalSha256 !== entry.maestroSha256) {
|
|
97
|
+
entry.differs = true;
|
|
98
|
+
if (classified.safeCopyCandidate && row.normal && row.maestro) {
|
|
99
|
+
entry.safeCopyCandidate = true;
|
|
100
|
+
entry.reason = classified.reason + '; hashes differ';
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
entry.differs = !!differs;
|
|
104
|
+
}
|
|
105
|
+
entries.push(entry);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const payload = {
|
|
109
|
+
timestamp: new Date().toISOString(),
|
|
110
|
+
normalHome: CONFIG.normalCodexHome,
|
|
111
|
+
maestroHome: CONFIG.codexHome,
|
|
112
|
+
entries,
|
|
113
|
+
candidates: entries.filter(entry => entry.safeCopyCandidate).map(entry => entry.path),
|
|
114
|
+
counts: entries.reduce((acc, entry) => {
|
|
115
|
+
acc.total++;
|
|
116
|
+
acc[entry.classification] = (acc[entry.classification] || 0) + 1;
|
|
117
|
+
if (entry.safeCopyCandidate) {
|
|
118
|
+
acc.safeCopyCandidates++;
|
|
119
|
+
}
|
|
120
|
+
return acc;
|
|
121
|
+
}, { total: 0, safeCopyCandidates: 0 })
|
|
122
|
+
};
|
|
123
|
+
await fs.mkdir(CONFIG.maestroPath, { recursive: true });
|
|
124
|
+
const inventoryPath = path.join(CONFIG.maestroPath, 'codex-home-inventory.json');
|
|
125
|
+
await fs.writeFile(inventoryPath, JSON.stringify(payload, null, 2));
|
|
126
|
+
await logMemory('maestro codex-home-inventory generated ' + payload.entries.length + ' entries and ' + payload.candidates.length + ' safe candidates.');
|
|
127
|
+
console.log('Codex home inventory saved: ' + inventoryPath);
|
|
128
|
+
console.log('Safe candidates: ' + payload.candidates.length);
|
|
129
|
+
console.log('Counts: ' + JSON.stringify(payload.counts));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function candidatePriority(pathName) {
|
|
133
|
+
const lower = pathName.toLowerCase();
|
|
134
|
+
if (lower.startsWith('.sandbox/') && !lower.includes('secret')) return 0;
|
|
135
|
+
if (/(trust|trusted|approval|permission|policy|workspace|project|settings)/i.test(lower)) return 1;
|
|
136
|
+
if (/(config|rules?)/i.test(lower)) return 2;
|
|
137
|
+
if (/(metadata|state)/i.test(lower)) return 3;
|
|
138
|
+
return 4;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function isolatedCompareEnv() {
|
|
142
|
+
const env = { ...process.env, CODEX_HOME: CONFIG.codexHome };
|
|
143
|
+
if (!env.NINEROUTER_API_KEY && env.MAESTRO_ALLOW_LOCAL_TEST_KEY === '1') {
|
|
144
|
+
env.NINEROUTER_API_KEY = 'local-test';
|
|
145
|
+
}
|
|
146
|
+
return env;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function runIsolatedProbeFile(fileName, expected) {
|
|
150
|
+
const expectedPath = path.join(process.cwd(), fileName);
|
|
151
|
+
try {
|
|
152
|
+
await fs.unlink(expectedPath);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (error.code !== 'ENOENT') {
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const jsPath = getDefaultCodexJsPath();
|
|
159
|
+
const args = [jsPath, 'exec', '--profile', CONFIG.codexProfile, '--sandbox', 'workspace-write', '--skip-git-repo-check', 'Crie ' + fileName + ' com exatamente: ' + expected];
|
|
160
|
+
const result = await execFileRaw(process.execPath, args, { env: isolatedCompareEnv(), quiet: true, timeoutMs: 60000 });
|
|
161
|
+
let fileCreated = false;
|
|
162
|
+
let contentOk = false;
|
|
163
|
+
let actualContent = '';
|
|
164
|
+
try {
|
|
165
|
+
actualContent = normalizeReadContent(await fs.readFile(expectedPath)).trim();
|
|
166
|
+
fileCreated = true;
|
|
167
|
+
contentOk = actualContent === expected;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
actualContent = '';
|
|
170
|
+
}
|
|
171
|
+
const output = result.stdout + '\n' + result.stderr;
|
|
172
|
+
return {
|
|
173
|
+
exitCode: result.code,
|
|
174
|
+
sandbox: detectField(output, 'sandbox'),
|
|
175
|
+
provider: detectField(output, 'provider'),
|
|
176
|
+
apiOpenAi: /api\.openai\.com/i.test(output),
|
|
177
|
+
blockedByPolicy: /blocked by policy/i.test(output),
|
|
178
|
+
readOnly: /read-only/i.test(output),
|
|
179
|
+
file: fileName,
|
|
180
|
+
fileCreated,
|
|
181
|
+
contentOk,
|
|
182
|
+
ok: result.code === 0 && fileCreated && contentOk,
|
|
183
|
+
stdout: summarizeText(result.stdout),
|
|
184
|
+
stderr: summarizeText(result.stderr)
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export async function codexHomeProbe() {
|
|
189
|
+
const inventoryPath = path.join(CONFIG.maestroPath, 'codex-home-inventory.json');
|
|
190
|
+
let inventory;
|
|
191
|
+
try {
|
|
192
|
+
inventory = JSON.parse(await fs.readFile(inventoryPath, 'utf-8'));
|
|
193
|
+
} catch (error) {
|
|
194
|
+
await codexHomeInventory();
|
|
195
|
+
inventory = JSON.parse(await fs.readFile(inventoryPath, 'utf-8'));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const baseline = await runIsolatedProbeFile('PROBE_BASELINE_ISOLATED.md', 'baseline codex-js-isolated probe.');
|
|
199
|
+
if (baseline.ok) {
|
|
200
|
+
const payload = {
|
|
201
|
+
timestamp: new Date().toISOString(),
|
|
202
|
+
baseline,
|
|
203
|
+
candidatesTested: 0,
|
|
204
|
+
attempts: [],
|
|
205
|
+
winner: { candidate: '<already-working>', result: baseline },
|
|
206
|
+
conclusion: 'codex-js-isolated already has workspace-write; no candidate copy needed.'
|
|
207
|
+
};
|
|
208
|
+
const probePath = path.join(CONFIG.maestroPath, 'codex-home-probe.json');
|
|
209
|
+
await fs.writeFile(probePath, JSON.stringify(payload, null, 2));
|
|
210
|
+
await logMemory('maestro codex-home-probe baseline already works; no candidates copied.');
|
|
211
|
+
console.log('Codex home probe saved: ' + probePath);
|
|
212
|
+
console.log('Winner: <already-working>');
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const candidates = inventory.entries
|
|
217
|
+
.filter(entry => entry.safeCopyCandidate)
|
|
218
|
+
.filter(entry => !/(auth|token|credential|secret|session|cookie|cache|key|login|account|\.tmp|\/tmp\/|sessions\/)/i.test(entry.path))
|
|
219
|
+
.sort((a, b) => candidatePriority(a.path) - candidatePriority(b.path) || a.path.localeCompare(b.path))
|
|
220
|
+
.slice(0, 80);
|
|
221
|
+
|
|
222
|
+
const attempts = [];
|
|
223
|
+
let winner = null;
|
|
224
|
+
for (const candidate of candidates) {
|
|
225
|
+
const backupPath = CONFIG.codexHome + '.probe-backup-' + timestampForPath() + '-' + safeProbeName(candidate.path);
|
|
226
|
+
await copyDirectorySafe(CONFIG.codexHome, backupPath);
|
|
227
|
+
const source = path.join(CONFIG.normalCodexHome, candidate.path);
|
|
228
|
+
const target = path.join(CONFIG.codexHome, candidate.path);
|
|
229
|
+
const sensitiveInspection = await inspectSensitiveContent(source, candidate.path);
|
|
230
|
+
if (!sensitiveInspection.ok) {
|
|
231
|
+
attempts.push({ candidate: candidate.path, backupPath, classification: candidate.classification, skipped: true, reason: sensitiveInspection.reason });
|
|
232
|
+
await restoreDirectoryFromBackup(backupPath, CONFIG.codexHome);
|
|
233
|
+
console.log(candidate.path + ': skipped=' + sensitiveInspection.reason);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
237
|
+
await fs.copyFile(source, target);
|
|
238
|
+
const probeName = 'PROBE_' + safeProbeName(candidate.path) + '.md';
|
|
239
|
+
const expected = 'probe ' + candidate.path + ' funcionou.';
|
|
240
|
+
const result = await runIsolatedProbeFile(probeName, expected);
|
|
241
|
+
const attempt = { candidate: candidate.path, backupPath, classification: candidate.classification, result };
|
|
242
|
+
attempts.push(attempt);
|
|
243
|
+
console.log(candidate.path + ': ok=' + result.ok + ', sandbox=' + (result.sandbox || 'unknown'));
|
|
244
|
+
if (result.ok) {
|
|
245
|
+
winner = attempt;
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
await restoreDirectoryFromBackup(backupPath, CONFIG.codexHome);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const payload = {
|
|
252
|
+
timestamp: new Date().toISOString(),
|
|
253
|
+
baseline,
|
|
254
|
+
candidatesTested: attempts.length,
|
|
255
|
+
attempts,
|
|
256
|
+
winner,
|
|
257
|
+
conclusion: winner ? 'Found safe candidate that unlocks isolated workspace-write.' : 'No safe candidate unlocked isolated workspace-write; keep normal-home fallback.'
|
|
258
|
+
};
|
|
259
|
+
const probePath = path.join(CONFIG.maestroPath, 'codex-home-probe.json');
|
|
260
|
+
await fs.writeFile(probePath, JSON.stringify(payload, null, 2));
|
|
261
|
+
await logMemory('maestro codex-home-probe tested ' + attempts.length + ' safe candidates. Winner: ' + (winner ? winner.candidate : 'none') + '.');
|
|
262
|
+
if (winner) {
|
|
263
|
+
await memoryDecision('Use safe Codex home candidate "' + winner.candidate + '" to unlock codex-js-isolated.');
|
|
264
|
+
} else {
|
|
265
|
+
await memoryDecision('No safe Codex home candidate unlocked codex-js-isolated; keep codex-js-normal-home fallback.');
|
|
266
|
+
}
|
|
267
|
+
console.log('Codex home probe saved: ' + probePath);
|
|
268
|
+
console.log('Winner: ' + (winner ? winner.candidate : 'none'));
|
|
269
|
+
if (!winner) {
|
|
270
|
+
process.exitCode = 1;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export async function codexHomeRepair() {
|
|
275
|
+
await fs.mkdir(CONFIG.codexHome, { recursive: true });
|
|
276
|
+
const backupPath = CONFIG.codexHome + '.backup-' + timestampForPath();
|
|
277
|
+
await copyDirectorySafe(CONFIG.codexHome, backupPath);
|
|
278
|
+
const profilePath = path.join(CONFIG.codexHome, '9router.config.toml');
|
|
279
|
+
await fs.writeFile(profilePath, DEFAULT_CODEX_PROFILE_CONFIG);
|
|
280
|
+
const copiedPolicyFiles = [];
|
|
281
|
+
const normalSandboxBin = path.join(CONFIG.normalCodexHome, '.sandbox-bin');
|
|
282
|
+
const maestroSandboxBin = path.join(CONFIG.codexHome, '.sandbox-bin');
|
|
283
|
+
if (await pathExists(normalSandboxBin)) {
|
|
284
|
+
await copyDirectorySafe(normalSandboxBin, maestroSandboxBin);
|
|
285
|
+
copiedPolicyFiles.push(maestroSandboxBin);
|
|
286
|
+
}
|
|
287
|
+
const normalSandboxMarker = path.join(CONFIG.normalCodexHome, '.sandbox', 'setup_marker.json');
|
|
288
|
+
if (await pathExists(normalSandboxMarker)) {
|
|
289
|
+
const markerInspection = await inspectSensitiveContent(normalSandboxMarker, '.sandbox/setup_marker.json');
|
|
290
|
+
if (markerInspection.ok) {
|
|
291
|
+
const maestroSandboxDir = path.join(CONFIG.codexHome, '.sandbox');
|
|
292
|
+
await fs.mkdir(maestroSandboxDir, { recursive: true });
|
|
293
|
+
const maestroSandboxMarker = path.join(maestroSandboxDir, 'setup_marker.json');
|
|
294
|
+
await fs.copyFile(normalSandboxMarker, maestroSandboxMarker);
|
|
295
|
+
copiedPolicyFiles.push(maestroSandboxMarker);
|
|
296
|
+
} else {
|
|
297
|
+
copiedPolicyFiles.push('skipped .sandbox/setup_marker.json: ' + markerInspection.reason);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
const repairPath = path.join(CONFIG.maestroPath, 'codex-home-repair.json');
|
|
301
|
+
const repair = {
|
|
302
|
+
timestamp: new Date().toISOString(),
|
|
303
|
+
maestroHome: CONFIG.codexHome,
|
|
304
|
+
backupPath,
|
|
305
|
+
changedFiles: [profilePath, ...copiedPolicyFiles],
|
|
306
|
+
copiedPolicyFiles,
|
|
307
|
+
skippedSensitiveCopy: true,
|
|
308
|
+
note: 'Rewrote 9router.config.toml and copied non-sensitive sandbox runtime files only. No auth/session/token/cookie/cache files copied.'
|
|
309
|
+
};
|
|
310
|
+
await fs.writeFile(repairPath, JSON.stringify(repair, null, 2));
|
|
311
|
+
await logMemory('maestro codex-home-repair backed up ' + CONFIG.codexHome + ' to ' + backupPath + ' and rewrote 9router.config.toml without copying secrets.');
|
|
312
|
+
console.log('Codex home backup: ' + backupPath);
|
|
313
|
+
console.log('Repair saved: ' + repairPath);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function withoutPrompt(args) {
|
|
317
|
+
return args.slice(0, -1).concat(['<prompt>']);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function normalHomeEnv() {
|
|
321
|
+
const env = { ...process.env };
|
|
322
|
+
delete env.CODEX_HOME;
|
|
323
|
+
return env;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function runCompareTest(test) {
|
|
327
|
+
const expectedPath = path.join(process.cwd(), test.file);
|
|
328
|
+
try {
|
|
329
|
+
await fs.unlink(expectedPath);
|
|
330
|
+
} catch (error) {
|
|
331
|
+
if (error.code !== 'ENOENT') {
|
|
332
|
+
throw error;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const result = test.kind === 'cmd'
|
|
337
|
+
? await execCmdFile(test.command, test.args, { env: test.env, quiet: true, timeoutMs: 60000 })
|
|
338
|
+
: await execFileRaw(test.command, test.args, { env: test.env, quiet: true, timeoutMs: 60000 });
|
|
339
|
+
|
|
340
|
+
let fileContent = '';
|
|
341
|
+
let fileCreated = false;
|
|
342
|
+
let contentOk = false;
|
|
343
|
+
try {
|
|
344
|
+
fileContent = normalizeReadContent(await fs.readFile(expectedPath));
|
|
345
|
+
fileCreated = true;
|
|
346
|
+
contentOk = fileContent.trim() === test.expected;
|
|
347
|
+
} catch (error) {
|
|
348
|
+
fileContent = error.message;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const output = (result.stdout || '') + '\n' + (result.stderr || '');
|
|
352
|
+
return {
|
|
353
|
+
id: test.id,
|
|
354
|
+
invocation: test.invocation,
|
|
355
|
+
command: test.command,
|
|
356
|
+
args: withoutPrompt(test.args),
|
|
357
|
+
codexHome: test.env && test.env.CODEX_HOME ? test.env.CODEX_HOME : null,
|
|
358
|
+
exitCode: result.code,
|
|
359
|
+
provider: detectField(output, 'provider'),
|
|
360
|
+
sandbox: detectField(output, 'sandbox'),
|
|
361
|
+
apiOpenAi: /api\.openai\.com/i.test(output),
|
|
362
|
+
blockedByPolicy: /blocked by policy/i.test(output),
|
|
363
|
+
readOnly: /read-only/i.test(output),
|
|
364
|
+
file: test.file,
|
|
365
|
+
fileCreated,
|
|
366
|
+
contentOk,
|
|
367
|
+
ok: result.code === 0 && fileCreated && contentOk,
|
|
368
|
+
stdout: summarizeText(result.stdout),
|
|
369
|
+
stderr: summarizeText(result.stderr),
|
|
370
|
+
actualContent: fileCreated ? fileContent.trim() : ''
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function compareRecommendation(tests) {
|
|
375
|
+
const byId = new Map(tests.map(test => [test.id, test]));
|
|
376
|
+
const a = byId.get('working-baseline-cmd');
|
|
377
|
+
const b = byId.get('cmd-with-codex-home');
|
|
378
|
+
const c = byId.get('node-js-with-normal-codex-home');
|
|
379
|
+
const d = byId.get('node-js-with-codex-home');
|
|
380
|
+
if (!a || !a.ok) {
|
|
381
|
+
return 'Baseline codex.cmd falhou; o ambiente externo mudou.';
|
|
382
|
+
}
|
|
383
|
+
if (a.ok && b && !b.ok) {
|
|
384
|
+
return 'Problema provável no CODEX_HOME isolado ou na config isolada.';
|
|
385
|
+
}
|
|
386
|
+
if (a.ok && c && !c.ok) {
|
|
387
|
+
return 'Problema provável em node.exe + codex.js; usar codex.cmd como compatibilidade.';
|
|
388
|
+
}
|
|
389
|
+
if (b && b.ok && d && !d.ok) {
|
|
390
|
+
return 'Problema provável em node.exe + codex.js com CODEX_HOME; usar codex.cmd com CODEX_HOME.';
|
|
391
|
+
}
|
|
392
|
+
if ([a, b, c, d].every(test => test && test.ok)) {
|
|
393
|
+
return 'Todas as invocações funcionaram; usar a invocação isolada mais segura.';
|
|
394
|
+
}
|
|
395
|
+
return 'Comparação inconclusiva; revisar stdout/stderr por teste.';
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function chooseCompareWinner(tests) {
|
|
399
|
+
const order = ['codex-js-isolated', 'codex-cmd-isolated', 'codex-js-normal-home', 'codex-cmd'];
|
|
400
|
+
for (const invocation of order) {
|
|
401
|
+
const test = tests.find(item => item.invocation === invocation && item.ok);
|
|
402
|
+
if (test) {
|
|
403
|
+
return invocation;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export async function codexCompare() {
|
|
410
|
+
await ensureCodexHome();
|
|
411
|
+
const cmdPath = getDefaultCodexCmdPath();
|
|
412
|
+
const jsPath = getDefaultCodexJsPath();
|
|
413
|
+
const cases = [
|
|
414
|
+
{
|
|
415
|
+
id: 'working-baseline-cmd',
|
|
416
|
+
invocation: 'codex-cmd',
|
|
417
|
+
kind: 'cmd',
|
|
418
|
+
command: cmdPath,
|
|
419
|
+
env: normalHomeEnv(),
|
|
420
|
+
file: 'WORKING_BASELINE_CMD.md',
|
|
421
|
+
expected: 'baseline codex.cmd workspace-write funcionou.'
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
id: 'cmd-with-codex-home',
|
|
425
|
+
invocation: 'codex-cmd-isolated',
|
|
426
|
+
kind: 'cmd',
|
|
427
|
+
command: cmdPath,
|
|
428
|
+
env: isolatedCompareEnv(),
|
|
429
|
+
file: 'WORKING_CMD_CODEX_HOME.md',
|
|
430
|
+
expected: 'codex.cmd com CODEX_HOME isolado funcionou.'
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
id: 'node-js-with-normal-codex-home',
|
|
434
|
+
invocation: 'codex-js-normal-home',
|
|
435
|
+
kind: 'direct',
|
|
436
|
+
command: process.execPath,
|
|
437
|
+
argsPrefix: [jsPath],
|
|
438
|
+
env: normalHomeEnv(),
|
|
439
|
+
file: 'WORKING_NODEJS_NORMAL_HOME.md',
|
|
440
|
+
expected: 'node codex.js com home normal funcionou.'
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
id: 'node-js-with-codex-home',
|
|
444
|
+
invocation: 'codex-js-isolated',
|
|
445
|
+
kind: 'direct',
|
|
446
|
+
command: process.execPath,
|
|
447
|
+
argsPrefix: [jsPath],
|
|
448
|
+
env: isolatedCompareEnv(),
|
|
449
|
+
file: 'WORKING_NODEJS_CODEX_HOME.md',
|
|
450
|
+
expected: 'node codex.js com CODEX_HOME isolado funcionou.'
|
|
451
|
+
}
|
|
452
|
+
];
|
|
453
|
+
|
|
454
|
+
for (const item of cases) {
|
|
455
|
+
const prompt = 'Crie ' + item.file + ' com exatamente: ' + item.expected;
|
|
456
|
+
item.args = [...(item.argsPrefix || []), 'exec', '--profile', CONFIG.codexProfile, '--sandbox', 'workspace-write', '--skip-git-repo-check', prompt];
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const tests = [];
|
|
460
|
+
for (const item of cases) {
|
|
461
|
+
console.log('Running compare test: ' + item.id);
|
|
462
|
+
tests.push(await runCompareTest(item));
|
|
463
|
+
const last = tests[tests.length - 1];
|
|
464
|
+
console.log(item.id + ': ok=' + last.ok + ', exit=' + last.exitCode + ', sandbox=' + (last.sandbox || 'unknown'));
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const winner = chooseCompareWinner(tests);
|
|
468
|
+
const payload = await writeCodexCompare({
|
|
469
|
+
winner,
|
|
470
|
+
recommendation: compareRecommendation(tests),
|
|
471
|
+
tests
|
|
472
|
+
});
|
|
473
|
+
await recordUsage({ command: 'codex-compare', strategy: winner || 'none', promptChars: 0, output: JSON.stringify(tests), exitCode: winner ? 0 : 1 });
|
|
474
|
+
await logMemory('maestro codex-compare result: ' + tests.map(test => test.id + '=' + (test.ok ? 'ok' : 'fail')).join(', ') + '. Winner: ' + (winner || 'none') + '.');
|
|
475
|
+
if (winner) {
|
|
476
|
+
await memoryDecision('Use Codex invocation "' + winner + '" from .maestro/codex-compare.json for MAESTRO_CODEX_INVOCATION=auto.');
|
|
477
|
+
}
|
|
478
|
+
console.log('Compare saved: ' + getCodexComparePath());
|
|
479
|
+
console.log('Winner: ' + (payload.winner || 'none'));
|
|
480
|
+
console.log('Recommendation: ' + payload.recommendation);
|
|
481
|
+
if (!winner) {
|
|
482
|
+
process.exitCode = 1;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Hotfix A.2: minimal real workspace test. Asks the worker ONLY to
|
|
487
|
+
// (1) read a probe file inside the project, (2) create a temp file inside
|
|
488
|
+
// .maestro, (3) read it back, (4) delete it, (5) reply success. The read
|
|
489
|
+
// nonce exists only inside the probe file (never in the prompt), so seeing
|
|
490
|
+
// it in the answer proves a real read; the success marker plus the temp
|
|
491
|
+
// file being gone proves write + delete.
|
|
492
|
+
export async function codexTestInvocation() {
|
|
493
|
+
await ensureCodexHome();
|
|
494
|
+
await fs.mkdir(CONFIG.maestroPath, { recursive: true });
|
|
495
|
+
const nonce = 'MAESTRO-READ-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 8);
|
|
496
|
+
const readProbePath = path.join(CONFIG.maestroPath, 'codex-test-read.txt');
|
|
497
|
+
const tmpPath = path.join(CONFIG.maestroPath, 'codex-test-tmp.txt');
|
|
498
|
+
await fs.writeFile(readProbePath, nonce + '\n');
|
|
499
|
+
await fs.rm(tmpPath, { force: true });
|
|
500
|
+
|
|
501
|
+
const prompt = [
|
|
502
|
+
'Minimal workspace access test. Execute exactly these steps, in order:',
|
|
503
|
+
'1. Read the file .maestro/codex-test-read.txt.',
|
|
504
|
+
'2. Create the file .maestro/codex-test-tmp.txt whose content is exactly the single line you just read from .maestro/codex-test-read.txt.',
|
|
505
|
+
'3. Read .maestro/codex-test-tmp.txt back and verify it matches.',
|
|
506
|
+
'4. Delete .maestro/codex-test-tmp.txt.',
|
|
507
|
+
'5. If and only if every step succeeded, end your final answer with this single line, replacing <content> with the exact line read in step 1:',
|
|
508
|
+
'MAESTRO-CODEX-TEST: SUCCESS <content>',
|
|
509
|
+
'If any step fails, say exactly which step failed and why.',
|
|
510
|
+
'Do not modify any other file. Do not touch .env files. Do not deploy.'
|
|
511
|
+
].join('\n');
|
|
512
|
+
|
|
513
|
+
const invocationMode = await resolveCodexInvocationMode();
|
|
514
|
+
// 300s: the unelevated Windows sandbox adds per-command setup overhead
|
|
515
|
+
// and the 5-step test issues several tool calls; 60-120s produced false
|
|
516
|
+
// exit-124 timeouts while the worker was still legitimately executing.
|
|
517
|
+
const result = await runCodex(['exec', '--profile', CONFIG.codexProfile, '--sandbox', 'workspace-write', '--skip-git-repo-check', prompt], { quiet: true, timeoutMs: 300000 });
|
|
518
|
+
|
|
519
|
+
const output = result.stdout + '\n' + result.stderr;
|
|
520
|
+
const readOk = output.includes(nonce);
|
|
521
|
+
const successMarkerOk = output.includes('MAESTRO-CODEX-TEST: SUCCESS ' + nonce);
|
|
522
|
+
const tmpLeftBehind = await pathExists(tmpPath);
|
|
523
|
+
const sandboxAclFailure = SANDBOX_ACL_FAILURE_PATTERN.test(output);
|
|
524
|
+
const invocation = result.invocation || {};
|
|
525
|
+
const attempt = {
|
|
526
|
+
invocation: invocationMode,
|
|
527
|
+
strategy: result.strategy,
|
|
528
|
+
exitCode: result.code,
|
|
529
|
+
readOk,
|
|
530
|
+
writeAndDeleteOk: successMarkerOk && !tmpLeftBehind,
|
|
531
|
+
tmpLeftBehind,
|
|
532
|
+
sandboxAclFailure,
|
|
533
|
+
codexHomeIsolated: invocation.usesIsolatedHome === true,
|
|
534
|
+
sandbox: detectField(output, 'sandbox'),
|
|
535
|
+
provider: detectField(output, 'provider'),
|
|
536
|
+
blockedByPolicy: /blocked by policy/i.test(output),
|
|
537
|
+
readOnly: /read-only/i.test(output),
|
|
538
|
+
apiOpenAi: /api\.openai\.com/i.test(output),
|
|
539
|
+
stdout: summarizeText(result.stdout),
|
|
540
|
+
stderr: summarizeText(result.stderr)
|
|
541
|
+
};
|
|
542
|
+
const ok = result.code === 0 && readOk && successMarkerOk && !tmpLeftBehind && !sandboxAclFailure && !attempt.apiOpenAi;
|
|
543
|
+
|
|
544
|
+
// Clean up the probe files regardless of outcome.
|
|
545
|
+
await fs.rm(readProbePath, { force: true });
|
|
546
|
+
await fs.rm(tmpPath, { force: true });
|
|
547
|
+
|
|
548
|
+
await recordUsage({ command: 'codex-test', strategy: invocationMode, prompt, output: JSON.stringify(attempt), exitCode: ok ? 0 : 1, codexTest: true });
|
|
549
|
+
await logMemory('maestro codex-test used invocation ' + invocationMode + ': readOk=' + readOk + ', writeAndDeleteOk=' + attempt.writeAndDeleteOk + ', sandboxAclFailure=' + sandboxAclFailure + ', exit=' + result.code + '.');
|
|
550
|
+
console.log(JSON.stringify(attempt, null, 2));
|
|
551
|
+
if (!ok) {
|
|
552
|
+
process.exitCode = 1;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
export async function hasRecentCodexTest() {
|
|
557
|
+
try {
|
|
558
|
+
const content = await fs.readFile(path.join(CONFIG.maestroPath, 'usage.jsonl'), 'utf-8');
|
|
559
|
+
const dayMs = 24 * 60 * 60 * 1000;
|
|
560
|
+
return content.split(/\r?\n/).filter(Boolean).map(line => JSON.parse(line)).some(row => {
|
|
561
|
+
return row.codexTest && row.exitCode === 0 && row.strategy === 'codex-js-isolated' && (Date.now() - new Date(row.timestamp).getTime()) < dayMs;
|
|
562
|
+
});
|
|
563
|
+
} catch (error) {
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
async function runDiagnosticStep(name, fn) {
|
|
569
|
+
try {
|
|
570
|
+
const result = await fn();
|
|
571
|
+
return { name, ok: result.ok !== false, ...result };
|
|
572
|
+
} catch (error) {
|
|
573
|
+
return { name, ok: false, error: error.message };
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
export async function codexSandboxTestCommand() {
|
|
578
|
+
await fs.mkdir(path.join(CONFIG.maestroPath, 'sandbox-test'), { recursive: true });
|
|
579
|
+
const root = process.cwd();
|
|
580
|
+
const probe = path.join(CONFIG.maestroPath, 'sandbox-test', 'probe.txt');
|
|
581
|
+
const checkFile = path.join(CONFIG.maestroPath, 'sandbox-test', 'check-ok.mjs');
|
|
582
|
+
const unitFile = path.join(CONFIG.maestroPath, 'sandbox-test', 'unit.test.mjs');
|
|
583
|
+
await fs.writeFile(checkFile, 'const value = 1;\n');
|
|
584
|
+
await fs.writeFile(unitFile, "import test from 'node:test';\nimport assert from 'node:assert/strict';\ntest('sandbox diagnostic unit', () => assert.equal(1, 1));\n");
|
|
585
|
+
const steps = [];
|
|
586
|
+
steps.push(await runDiagnosticStep('filesystem-write', async () => { await fs.writeFile(probe, 'ok\n'); return { ok: true }; }));
|
|
587
|
+
steps.push(await runDiagnosticStep('filesystem-read', async () => ({ ok: (await fs.readFile(probe, 'utf8')).trim() === 'ok' })));
|
|
588
|
+
steps.push(await runDiagnosticStep('node', async () => {
|
|
589
|
+
const result = await execFileRaw(process.execPath, ['--version'], { quiet: true, timeoutMs: 30000 });
|
|
590
|
+
return { ok: result.code === 0, exitCode: result.code, stdout: summarizeText(result.stdout), stderr: summarizeText(result.stderr) };
|
|
591
|
+
}));
|
|
592
|
+
steps.push(await runDiagnosticStep('npm', async () => {
|
|
593
|
+
const invocation = resolveNpmInvocation(['--version']);
|
|
594
|
+
const result = await execFileRaw(invocation.executable, invocation.args, { quiet: true, timeoutMs: 30000 });
|
|
595
|
+
return { ok: result.code === 0, exitCode: result.code, stdout: summarizeText(result.stdout), stderr: summarizeText(result.stderr) };
|
|
596
|
+
}));
|
|
597
|
+
steps.push(await runDiagnosticStep('child-process', async () => {
|
|
598
|
+
const result = await execFileRaw(process.execPath, ['-e', 'process.exit(0)'], { quiet: true, timeoutMs: 30000 });
|
|
599
|
+
return { ok: result.code === 0, exitCode: result.code, stderr: summarizeText(result.stderr) };
|
|
600
|
+
}));
|
|
601
|
+
steps.push(await runDiagnosticStep('node-check', async () => {
|
|
602
|
+
const result = await runHostValidation({ commandId: 'node-check', projectRoot: root, file: path.relative(root, checkFile) });
|
|
603
|
+
return { ok: result.ok, exitCode: result.exitCode, stdout: summarizeText(result.stdout), stderr: summarizeText(result.stderr), metadata: result.metadata };
|
|
604
|
+
}));
|
|
605
|
+
steps.push(await runDiagnosticStep('unit-test', async () => {
|
|
606
|
+
const result = await execFileRaw(process.execPath, ['--test', unitFile], { quiet: true, timeoutMs: 60000 });
|
|
607
|
+
return { ok: result.code === 0, exitCode: result.code, stdout: summarizeText(result.stdout), stderr: summarizeText(result.stderr) };
|
|
608
|
+
}));
|
|
609
|
+
steps.push(await runDiagnosticStep('npm-test', async () => {
|
|
610
|
+
const result = await runHostValidation({ commandId: 'npm-test', projectRoot: root });
|
|
611
|
+
return { ok: result.ok, exitCode: result.exitCode, stdout: summarizeText(result.stdout), stderr: summarizeText(result.stderr), metadata: result.metadata };
|
|
612
|
+
}));
|
|
613
|
+
const payload = { timestamp: new Date().toISOString(), steps, ok: steps.every(step => step.ok) };
|
|
614
|
+
const reportPath = path.join(CONFIG.maestroPath, 'codex-sandbox-test.json');
|
|
615
|
+
await fs.writeFile(reportPath, JSON.stringify(payload, null, 2));
|
|
616
|
+
await fs.rm(path.join(CONFIG.maestroPath, 'sandbox-test'), { recursive: true, force: true });
|
|
617
|
+
await recordUsage({ command: 'codex-sandbox-test', runKind: 'diagnostic', promptChars: 0, output: JSON.stringify(payload), exitCode: payload.ok ? 0 : 1 });
|
|
618
|
+
await logMemory('maestro codex-sandbox-test completed: ok=' + payload.ok + ', steps=' + steps.map(step => step.name + '=' + (step.ok ? 'ok' : 'fail')).join(', ') + '.');
|
|
619
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
620
|
+
if (!payload.ok) process.exitCode = 1;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
export async function codexDebugCommand() {
|
|
624
|
+
const info = await getCodexDebugInfo();
|
|
625
|
+
let repair = null;
|
|
626
|
+
let homeDiff = null;
|
|
627
|
+
let probe = null;
|
|
628
|
+
try {
|
|
629
|
+
repair = JSON.parse(await fs.readFile(path.join(CONFIG.maestroPath, 'codex-home-repair.json'), 'utf-8'));
|
|
630
|
+
} catch (error) {
|
|
631
|
+
repair = null;
|
|
632
|
+
}
|
|
633
|
+
try {
|
|
634
|
+
homeDiff = JSON.parse(await fs.readFile(path.join(CONFIG.maestroPath, 'codex-home-diff.json'), 'utf-8'));
|
|
635
|
+
} catch (error) {
|
|
636
|
+
homeDiff = null;
|
|
637
|
+
}
|
|
638
|
+
try {
|
|
639
|
+
probe = JSON.parse(await fs.readFile(path.join(CONFIG.maestroPath, 'codex-home-probe.json'), 'utf-8'));
|
|
640
|
+
} catch (error) {
|
|
641
|
+
probe = null;
|
|
642
|
+
}
|
|
643
|
+
const lines = [
|
|
644
|
+
'Codex Debug Information',
|
|
645
|
+
'- Command: ' + info.command,
|
|
646
|
+
'- Args prefix: ' + info.argsPrefix.join(' '),
|
|
647
|
+
'- Source: ' + info.source,
|
|
648
|
+
'- Selected invocation: ' + info.selectedInvocationMode,
|
|
649
|
+
'- Selected command: ' + info.selectedCommand,
|
|
650
|
+
'- Selected args prefix: ' + info.selectedArgsPrefix.join(' '),
|
|
651
|
+
'- Selected uses isolated CODEX_HOME: ' + info.selectedUsesIsolatedHome,
|
|
652
|
+
'- Normal CODEX_HOME: ' + CONFIG.normalCodexHome,
|
|
653
|
+
'- CODEX_HOME: ' + info.codexHome,
|
|
654
|
+
'- Last repair backup: ' + (repair ? repair.backupPath : 'none'),
|
|
655
|
+
'- Last home diff: ' + (homeDiff ? 'normalFiles=' + homeDiff.normal.files.length + ', maestroFiles=' + homeDiff.maestro.files.length : 'none'),
|
|
656
|
+
'- Last home probe: ' + (probe ? 'winner=' + (probe.winner ? probe.winner.candidate : 'none') + ', tested=' + probe.candidatesTested : 'none'),
|
|
657
|
+
'- Isolated probe blocked by policy: ' + (probe && probe.baseline ? probe.baseline.blockedByPolicy : 'unknown'),
|
|
658
|
+
'- Profile: ' + info.profile,
|
|
659
|
+
'- Current strategy: ' + info.currentStrategy,
|
|
660
|
+
'- Last validated strategy: ' + (info.validatedStrategy ? info.validatedStrategy.strategy : 'none'),
|
|
661
|
+
'- Config file: ' + info.codexConfigPath,
|
|
662
|
+
'- Profile config file: ' + info.codexProfileConfigPath,
|
|
663
|
+
'- Legacy workspace-permissions config (unused): ' + info.legacyWorkspacePermissionsConfigPath + ' (exists=' + info.legacyWorkspacePermissionsConfigExists + ')',
|
|
664
|
+
...formatEffectiveRuntimeLines(info.effectiveRuntime),
|
|
665
|
+
'- --ask-for-approval global: ' + !!info.helpMatrix.hasGlobalAskForApproval,
|
|
666
|
+
'- -a global: ' + !!info.helpMatrix.hasGlobalShortApproval,
|
|
667
|
+
'- --sandbox exec: ' + !!info.helpMatrix.hasExecSandbox,
|
|
668
|
+
'- --config exec: ' + !!info.helpMatrix.hasExecConfig,
|
|
669
|
+
'- -c exec: ' + !!info.helpMatrix.hasExecShortConfig,
|
|
670
|
+
'- --ignore-rules exec: ' + !!info.helpMatrix.hasExecIgnoreRules,
|
|
671
|
+
'- dangerous bypass flag present: ' + !!info.helpMatrix.hasDangerousBypass,
|
|
672
|
+
'- default_permissions used: ' + info.checks.usesDefaultPermissions,
|
|
673
|
+
'- default_permissions + sandbox_mode mix: ' + info.checks.mixesDefaultPermissionsAndSandboxMode,
|
|
674
|
+
'- 9Router env active: ' + info.is9RouterActive,
|
|
675
|
+
'- Last compare winner: ' + (info.lastCompare ? (info.lastCompare.winner || 'none') : 'none'),
|
|
676
|
+
'- Last compare recommendation: ' + (info.lastCompare ? (info.lastCompare.recommendation || 'none') : 'none'),
|
|
677
|
+
'- Last compare tests: ' + (info.lastCompare && info.lastCompare.tests ? info.lastCompare.tests.map(test => test.id + '=' + (test.ok ? 'ok' : 'fail')).join(', ') : 'none'),
|
|
678
|
+
'- Normal-home fallback active: ' + (String(info.selectedInvocationMode).includes('normal-home') || info.selectedInvocationMode === 'codex-cmd')
|
|
679
|
+
];
|
|
680
|
+
await fs.writeFile('codex-debug-output.txt', lines.join('\n'));
|
|
681
|
+
console.log(lines.join('\n'));
|
|
682
|
+
}
|