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.
Files changed (81) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +15 -0
  3. package/README.md +727 -0
  4. package/bin/maestro.mjs +246 -0
  5. package/package.json +29 -0
  6. package/src/checkpoint.mjs +102 -0
  7. package/src/codex-diagnostics.mjs +682 -0
  8. package/src/codex-home-inspect.mjs +252 -0
  9. package/src/codex-home.mjs +258 -0
  10. package/src/commands.mjs +809 -0
  11. package/src/config.mjs +11 -0
  12. package/src/debug.mjs +164 -0
  13. package/src/encoding-guard.mjs +125 -0
  14. package/src/engines/ai-router-engine.mjs +37 -0
  15. package/src/engines/codex-engine.mjs +21 -0
  16. package/src/engines/engine.mjs +21 -0
  17. package/src/engines/registry.mjs +29 -0
  18. package/src/files.mjs +65 -0
  19. package/src/format.mjs +132 -0
  20. package/src/lock.mjs +439 -0
  21. package/src/memory-log.mjs +18 -0
  22. package/src/memory.mjs +1 -0
  23. package/src/orchestration/attempt-chain-runtime.mjs +627 -0
  24. package/src/orchestration/budget-manager.mjs +121 -0
  25. package/src/orchestration/capability-registry.mjs +108 -0
  26. package/src/orchestration/consolidator.mjs +772 -0
  27. package/src/orchestration/delegation-executor.mjs +262 -0
  28. package/src/orchestration/delegation-manager.mjs +116 -0
  29. package/src/orchestration/deployment-intent.mjs +36 -0
  30. package/src/orchestration/engine-history.mjs +110 -0
  31. package/src/orchestration/engine-policy.mjs +73 -0
  32. package/src/orchestration/engine-selector.mjs +187 -0
  33. package/src/orchestration/execution-context.mjs +45 -0
  34. package/src/orchestration/failure-classifier.mjs +136 -0
  35. package/src/orchestration/failure-evidence.mjs +237 -0
  36. package/src/orchestration/fallback-chain-lock.mjs +217 -0
  37. package/src/orchestration/fallback-chain-trail.mjs +218 -0
  38. package/src/orchestration/fallback-executor.mjs +146 -0
  39. package/src/orchestration/fallback-graph.mjs +106 -0
  40. package/src/orchestration/fallback-recommendation.mjs +73 -0
  41. package/src/orchestration/file-conflict-detector.mjs +126 -0
  42. package/src/orchestration/host-validation.mjs +241 -0
  43. package/src/orchestration/orchestration-loop.mjs +1971 -0
  44. package/src/orchestration/orchestration-runtime.mjs +1019 -0
  45. package/src/orchestration/orchestration-scheduler.mjs +135 -0
  46. package/src/orchestration/orchestration-trail.mjs +192 -0
  47. package/src/orchestration/preflight.mjs +212 -0
  48. package/src/orchestration/provider-health.mjs +566 -0
  49. package/src/orchestration/provider-router.mjs +352 -0
  50. package/src/orchestration/rc-check-adapters.mjs +817 -0
  51. package/src/orchestration/rc-check.mjs +544 -0
  52. package/src/orchestration/rc-policy.mjs +200 -0
  53. package/src/orchestration/runtime-gate.mjs +146 -0
  54. package/src/orchestration/sector-managers.mjs +215 -0
  55. package/src/orchestration/self-hosting-canary.mjs +231 -0
  56. package/src/orchestration/self-hosting-readiness.mjs +244 -0
  57. package/src/orchestration/spec-planner.mjs +877 -0
  58. package/src/orchestration/subtask-planner.mjs +176 -0
  59. package/src/orchestration/task-classifier.mjs +241 -0
  60. package/src/orchestration/verifier.mjs +1608 -0
  61. package/src/orchestration-commands.mjs +279 -0
  62. package/src/planner.mjs +38 -0
  63. package/src/project.mjs +1 -0
  64. package/src/run-diagnostics.mjs +641 -0
  65. package/src/runner.mjs +243 -0
  66. package/src/safety.mjs +116 -0
  67. package/src/schema.mjs +371 -0
  68. package/src/session-commands.mjs +521 -0
  69. package/src/shell.mjs +93 -0
  70. package/src/smart-planner.mjs +249 -0
  71. package/src/task-commands.mjs +1182 -0
  72. package/src/tasks.mjs +134 -0
  73. package/src/ui/commands.mjs +76 -0
  74. package/src/ui/events.mjs +45 -0
  75. package/src/ui/public/app.js +600 -0
  76. package/src/ui/public/index.html +88 -0
  77. package/src/ui/public/style.css +460 -0
  78. package/src/ui/server.mjs +112 -0
  79. package/src/ui/state.mjs +504 -0
  80. package/src/usage.mjs +178 -0
  81. 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
+ }