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,121 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { CONFIG } from '../config.mjs';
|
|
3
|
+
import { readJsonSafe } from '../files.mjs';
|
|
4
|
+
import { validateBudget } from '../schema.mjs';
|
|
5
|
+
import { DEFAULT_HOST_VALIDATION_POLICY, normalizeHostValidationPolicy } from './host-validation.mjs';
|
|
6
|
+
|
|
7
|
+
const budgetPath = path.join(CONFIG.maestroPath, 'budget.json');
|
|
8
|
+
|
|
9
|
+
export const DEFAULT_BUDGET = {
|
|
10
|
+
schemaVersion: 1,
|
|
11
|
+
currencyBudget: 0,
|
|
12
|
+
estimatedTokenBudget: 100000,
|
|
13
|
+
maxRunsPerTask: 3,
|
|
14
|
+
maxSubagentsPerTask: 4,
|
|
15
|
+
maxDepth: 2,
|
|
16
|
+
preferFree: true,
|
|
17
|
+
allowPaid: false,
|
|
18
|
+
allowClaudeReview: false,
|
|
19
|
+
allowDirectOpenAI: false,
|
|
20
|
+
...DEFAULT_HOST_VALIDATION_POLICY
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function getBudgetPath() {
|
|
24
|
+
return budgetPath;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function loadBudget() {
|
|
28
|
+
const budget = await readJsonSafe(budgetPath, DEFAULT_BUDGET);
|
|
29
|
+
const result = validateBudget(budget);
|
|
30
|
+
if (!result.ok) {
|
|
31
|
+
console.warn('.maestro/budget.json failed schema validation (' + result.errors.join(', ') + '); missing/invalid fields fall back to safe defaults.');
|
|
32
|
+
return { ...DEFAULT_BUDGET, ...budget };
|
|
33
|
+
}
|
|
34
|
+
return budget;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getHostValidationPolicy(budget = {}) {
|
|
38
|
+
return normalizeHostValidationPolicy({ ...DEFAULT_HOST_VALIDATION_POLICY, ...(budget || {}) });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function checkRunLimit(budget, runsSoFar) {
|
|
42
|
+
if (runsSoFar >= budget.maxRunsPerTask) {
|
|
43
|
+
return { allowed: false, reason: 'maxRunsPerTask (' + budget.maxRunsPerTask + ') reached for this task (runsSoFar=' + runsSoFar + ').' };
|
|
44
|
+
}
|
|
45
|
+
return { allowed: true };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function checkSubagentLimit(budget, subagentsSoFar) {
|
|
49
|
+
if (subagentsSoFar >= budget.maxSubagentsPerTask) {
|
|
50
|
+
return { allowed: false, reason: 'maxSubagentsPerTask (' + budget.maxSubagentsPerTask + ') reached (subagentsSoFar=' + subagentsSoFar + ').' };
|
|
51
|
+
}
|
|
52
|
+
return { allowed: true };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function checkDepthLimit(budget, depth) {
|
|
56
|
+
if (depth >= budget.maxDepth) {
|
|
57
|
+
return { allowed: false, reason: 'maxDepth (' + budget.maxDepth + ') reached at depth=' + depth + '.' };
|
|
58
|
+
}
|
|
59
|
+
return { allowed: true };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function checkTokenBudget(budget, estimatedTokensUsedSoFar, estimatedTokensForThisRun = 0) {
|
|
63
|
+
const projected = estimatedTokensUsedSoFar + estimatedTokensForThisRun;
|
|
64
|
+
if (budget.estimatedTokenBudget > 0 && projected > budget.estimatedTokenBudget) {
|
|
65
|
+
return {
|
|
66
|
+
allowed: false,
|
|
67
|
+
reason: 'estimatedTokenBudget (' + budget.estimatedTokenBudget + ') would be exceeded: ' +
|
|
68
|
+
estimatedTokensUsedSoFar + ' already used + ' + estimatedTokensForThisRun + ' projected = ' + projected + '.'
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return { allowed: true };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Env vars are an ADDITIONAL way to satisfy a gate already open in
|
|
75
|
+
// budget.json, never a way to bypass one that's closed there — this mirrors
|
|
76
|
+
// the spec's "OpenAI direto bloqueado sem MAESTRO_ALLOW_DIRECT_OPENAI=1" /
|
|
77
|
+
// "Claude review somente com MAESTRO_ALLOW_CLAUDE_REVIEW=1" language, which
|
|
78
|
+
// names env vars as the opt-in mechanism operators actually use day to day,
|
|
79
|
+
// while budget.json remains the durable, inspectable source of policy.
|
|
80
|
+
function envAllows(name) {
|
|
81
|
+
return process.env[name] === '1';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function checkEnginePolicy(budget, engineRecord, { isClaudeReview = false, isDirectOpenAI = false } = {}) {
|
|
85
|
+
const costClass = engineRecord && engineRecord.cost ? engineRecord.cost.class : 'unknown';
|
|
86
|
+
if (costClass === 'paid' && budget.allowPaid !== true) {
|
|
87
|
+
return { allowed: false, reason: 'Engine cost class is "paid" and allowPaid is false in .maestro/budget.json.' };
|
|
88
|
+
}
|
|
89
|
+
if (isClaudeReview && budget.allowClaudeReview !== true && !envAllows('MAESTRO_ALLOW_CLAUDE_REVIEW')) {
|
|
90
|
+
return { allowed: false, reason: 'Claude/premium review requires budget.allowClaudeReview=true or MAESTRO_ALLOW_CLAUDE_REVIEW=1.' };
|
|
91
|
+
}
|
|
92
|
+
if (isDirectOpenAI && budget.allowDirectOpenAI !== true && !envAllows('MAESTRO_ALLOW_DIRECT_OPENAI')) {
|
|
93
|
+
return { allowed: false, reason: 'Direct OpenAI requires budget.allowDirectOpenAI=true or MAESTRO_ALLOW_DIRECT_OPENAI=1.' };
|
|
94
|
+
}
|
|
95
|
+
return { allowed: true };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function evaluateBudget({
|
|
99
|
+
runsSoFar = 0,
|
|
100
|
+
subagentsSoFar = 0,
|
|
101
|
+
depth = 0,
|
|
102
|
+
estimatedTokensUsedSoFar = 0,
|
|
103
|
+
estimatedTokensForThisRun = 0,
|
|
104
|
+
engineRecord = null,
|
|
105
|
+
isClaudeReview = false,
|
|
106
|
+
isDirectOpenAI = false
|
|
107
|
+
} = {}) {
|
|
108
|
+
const budget = await loadBudget();
|
|
109
|
+
const checks = [
|
|
110
|
+
checkRunLimit(budget, runsSoFar),
|
|
111
|
+
checkSubagentLimit(budget, subagentsSoFar),
|
|
112
|
+
checkDepthLimit(budget, depth),
|
|
113
|
+
checkTokenBudget(budget, estimatedTokensUsedSoFar, estimatedTokensForThisRun),
|
|
114
|
+
checkEnginePolicy(budget, engineRecord, { isClaudeReview, isDirectOpenAI })
|
|
115
|
+
];
|
|
116
|
+
const failed = checks.find(check => check.allowed === false);
|
|
117
|
+
if (failed) {
|
|
118
|
+
return { allowed: false, reason: failed.reason, budget };
|
|
119
|
+
}
|
|
120
|
+
return { allowed: true, reason: 'Within budget.', budget };
|
|
121
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { CONFIG } from '../config.mjs';
|
|
3
|
+
import { readJsonSafe } from '../files.mjs';
|
|
4
|
+
|
|
5
|
+
const enginesFilePath = path.join(CONFIG.maestroPath, 'engines.json');
|
|
6
|
+
|
|
7
|
+
const EMPTY_REGISTRY = { schemaVersion: 1, generatedBy: 'none', note: '', engines: [] };
|
|
8
|
+
|
|
9
|
+
const TRUTHY_UNKNOWN = new Set(['unknown', null, undefined]);
|
|
10
|
+
|
|
11
|
+
export function getEnginesFilePath() {
|
|
12
|
+
return enginesFilePath;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function loadEngineRegistry() {
|
|
16
|
+
return readJsonSafe(enginesFilePath, EMPTY_REGISTRY);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function listEngineRecords({ enabledOnly = false } = {}) {
|
|
20
|
+
const registry = await loadEngineRegistry();
|
|
21
|
+
const engines = registry.engines || [];
|
|
22
|
+
return enabledOnly ? engines.filter(engine => engine.enabled === true) : engines;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function getEngineRecord(id) {
|
|
26
|
+
const engines = await listEngineRecords();
|
|
27
|
+
return engines.find(engine => engine.id === id) || null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// `field` is unknown if it's the literal string 'unknown', null, or undefined.
|
|
31
|
+
// Booleans (true/false) are treated as known even when false — a declared
|
|
32
|
+
// `false` is a real, confirmed answer ("this engine cannot do X"), not a gap.
|
|
33
|
+
export function isUnknownField(value) {
|
|
34
|
+
return TRUTHY_UNKNOWN.has(value);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function hasTool(engineRecord, toolName) {
|
|
38
|
+
if (!engineRecord || !engineRecord.tools) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
const value = engineRecord.tools[toolName];
|
|
42
|
+
return value === true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function hasCapabilityAtLeast(engineRecord, capabilityName, minLevel) {
|
|
46
|
+
if (!engineRecord || !engineRecord.capabilities) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
const value = engineRecord.capabilities[capabilityName];
|
|
50
|
+
if (isUnknownField(value) || typeof value !== 'number') {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
return value >= minLevel;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 'paid' is the only class that hard-requires the allowPaid opt-in (project
|
|
57
|
+
// rule: never call a paid engine without explicit opt-in). 'unknown' is
|
|
58
|
+
// deliberately NOT treated as equivalent to 'paid': this project's only real
|
|
59
|
+
// worker (codex9, via 9Router's own documented subscription->cheap->free
|
|
60
|
+
// auto-fallback) has honestly unknown per-call cost, and hard-blocking every
|
|
61
|
+
// unknown-cost engine by default would make the orchestrator unable to
|
|
62
|
+
// select the one engine that actually works. Unknown-cost engines remain
|
|
63
|
+
// selectable by default but score worse than confirmed free/local/free-tier
|
|
64
|
+
// ones (see engine-selector.mjs), and callers that want strict "confirmed
|
|
65
|
+
// non-paid only" behavior can pass allowUnknownCost:false explicitly.
|
|
66
|
+
export function isCostAcceptable(engineRecord, { allowPaid = false, allowUnknownCost = true } = {}) {
|
|
67
|
+
const costClass = engineRecord && engineRecord.cost ? engineRecord.cost.class : 'unknown';
|
|
68
|
+
if (costClass === 'free' || costClass === 'free-tier' || costClass === 'local') {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
if (costClass === 'paid') {
|
|
72
|
+
return allowPaid === true;
|
|
73
|
+
}
|
|
74
|
+
return allowUnknownCost === true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function summarizeConfidence(engineRecord) {
|
|
78
|
+
const confidence = (engineRecord && engineRecord.confidence) || {};
|
|
79
|
+
const values = Object.values(confidence);
|
|
80
|
+
if (values.length === 0) {
|
|
81
|
+
return 'unknown';
|
|
82
|
+
}
|
|
83
|
+
if (values.every(value => value === 'confirmed')) {
|
|
84
|
+
return 'confirmed';
|
|
85
|
+
}
|
|
86
|
+
if (values.some(value => value === 'unknown')) {
|
|
87
|
+
return 'unknown';
|
|
88
|
+
}
|
|
89
|
+
return 'probable';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function describeEngine(id) {
|
|
93
|
+
const engineRecord = await getEngineRecord(id);
|
|
94
|
+
if (!engineRecord) {
|
|
95
|
+
return { id, found: false };
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
id: engineRecord.id,
|
|
99
|
+
found: true,
|
|
100
|
+
displayName: engineRecord.displayName,
|
|
101
|
+
enabled: engineRecord.enabled === true,
|
|
102
|
+
validated: engineRecord.validated === true,
|
|
103
|
+
costClass: engineRecord.cost ? engineRecord.cost.class : 'unknown',
|
|
104
|
+
overallConfidence: summarizeConfidence(engineRecord),
|
|
105
|
+
roles: engineRecord.roles || [],
|
|
106
|
+
engineRecord
|
|
107
|
+
};
|
|
108
|
+
}
|