oxe-cc 1.8.0 → 1.9.1
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/AGENTS.md +1 -1
- package/CHANGELOG.md +94 -0
- package/README.md +15 -12
- package/bin/lib/oxe-manifest.cjs +20 -13
- package/bin/lib/oxe-operational.cjs +305 -79
- package/bin/lib/oxe-project-health.cjs +79 -18
- package/bin/lib/oxe-rationality.cjs +9 -7
- package/bin/lib/oxe-release.cjs +26 -0
- package/bin/oxe-cc.js +224 -52
- package/docs/RELEASE-READINESS.md +6 -1
- package/lib/runtime/compiler/graph-compiler.js +1 -1
- package/lib/runtime/executor/action-tool-map.js +4 -0
- package/lib/runtime/executor/built-in-tools.js +27 -0
- package/lib/runtime/executor/llm-task-executor.d.ts +4 -1
- package/lib/runtime/executor/llm-task-executor.js +41 -5
- package/lib/runtime/executor/node-prompt-builder.d.ts +4 -1
- package/lib/runtime/executor/node-prompt-builder.js +13 -2
- package/lib/runtime/models/failure.d.ts +1 -1
- package/lib/runtime/scheduler/multi-agent-coordinator.d.ts +39 -0
- package/lib/runtime/scheduler/multi-agent-coordinator.js +222 -13
- package/lib/runtime/scheduler/scheduler.d.ts +5 -1
- package/lib/runtime/scheduler/scheduler.js +82 -14
- package/lib/runtime/verification/verification-compiler.js +7 -5
- package/lib/runtime/workspace/strategies/git-worktree.js +18 -9
- package/lib/sdk/index.cjs +48 -44
- package/oxe/templates/PLAN.template.md +23 -9
- package/oxe/templates/SPEC.template.md +55 -22
- package/oxe/workflows/plan.md +18 -6
- package/oxe/workflows/spec.md +31 -9
- package/package.json +106 -100
- package/packages/runtime/package.json +18 -18
- package/packages/runtime/src/compiler/graph-compiler.ts +1 -1
- package/packages/runtime/src/evidence/evidence-store.ts +2 -2
- package/packages/runtime/src/executor/action-tool-map.ts +4 -0
- package/packages/runtime/src/executor/built-in-tools.ts +29 -0
- package/packages/runtime/src/executor/llm-task-executor.ts +46 -4
- package/packages/runtime/src/executor/node-prompt-builder.ts +18 -1
- package/packages/runtime/src/models/failure.ts +2 -0
- package/packages/runtime/src/scheduler/multi-agent-coordinator.ts +320 -46
- package/packages/runtime/src/scheduler/scheduler.ts +93 -15
- package/packages/runtime/src/verification/verification-compiler.ts +7 -5
- package/packages/runtime/src/workspace/strategies/git-worktree.ts +24 -16
- package/vscode-extension/package.json +185 -185
- package/vscode-extension/oxe-agents-0.9.1.vsix +0 -0
- package/vscode-extension/oxe-agents-0.9.2.vsix +0 -0
- package/vscode-extension/oxe-agents-1.0.0.vsix +0 -0
- package/vscode-extension/oxe-agents-1.4.0.vsix +0 -0
- package/vscode-extension/oxe-agents-1.5.0.vsix +0 -0
- package/vscode-extension/oxe-agents-1.5.1.vsix +0 -0
- package/vscode-extension/oxe-agents-1.6.0.vsix +0 -0
- package/vscode-extension/oxe-agents-1.7.0.vsix +0 -0
- package/vscode-extension/oxe-agents-1.8.0.vsix +0 -0
|
@@ -456,6 +456,61 @@ function reduceCanonicalRunState(projectRoot, activeSession, options = {}) {
|
|
|
456
456
|
return serializeCanonicalState(reduceCanonicalRunStateLive(projectRoot, activeSession, options));
|
|
457
457
|
}
|
|
458
458
|
|
|
459
|
+
/**
|
|
460
|
+
* Static-analysis lints for common pitfalls detectable before execution.
|
|
461
|
+
* Returns hint strings to be appended to validationErrors (shown at compile time).
|
|
462
|
+
*/
|
|
463
|
+
function lintPlanForCommonPitfalls(parsedPlan, parsedSpec, projectRoot, rawSpecText = '') {
|
|
464
|
+
const hints = [];
|
|
465
|
+
const specText = (parsedSpec && parsedSpec.objective ? parsedSpec.objective : '') +
|
|
466
|
+
JSON.stringify(parsedSpec && parsedSpec.criteria ? parsedSpec.criteria : []) +
|
|
467
|
+
(rawSpecText || '');
|
|
468
|
+
const planTasks = parsedPlan && Array.isArray(parsedPlan.tasks) ? parsedPlan.tasks : [];
|
|
469
|
+
|
|
470
|
+
// ── Lint 1: HTML/JS SPA sem restrição de file:// ──────────────────────────
|
|
471
|
+
// Detects: spec mentions HTML/SPA + no files restriction, but no verify command
|
|
472
|
+
// checks for fetch() absence. Warns to add a fetch-detection verify.
|
|
473
|
+
const isHtmlApp = /html|spa|browser|page|frontend|estático|static|aplicação web|web app|web page|interface web|\.html/i.test(specText);
|
|
474
|
+
if (isHtmlApp) {
|
|
475
|
+
const hasFetchGuard = planTasks.some(t =>
|
|
476
|
+
t.verifyCommand && /fetch|XMLHttpRequest|file:\/\//i.test(t.verifyCommand)
|
|
477
|
+
);
|
|
478
|
+
const specMentionsServer = /servidor|server|http-server|localhost|npx serve|vite|webpack/i.test(specText);
|
|
479
|
+
const specMentionsFileProtocol = /file:\/\/|sem servidor|without server|no.server/i.test(specText);
|
|
480
|
+
if (!hasFetchGuard && !specMentionsServer && !specMentionsFileProtocol) {
|
|
481
|
+
hints.push(
|
|
482
|
+
'HINT(html-fetch): SPEC não declara se o app precisa de servidor HTTP. ' +
|
|
483
|
+
'Se abrir em file://, adicione à SPEC: "sem servidor HTTP" e um verify que detecte fetch(): ' +
|
|
484
|
+
'`node -e "if(require(\'fs\').readFileSync(\'app.js\',\'utf8\').includes(\'fetch(\'))throw new Error(\'fetch not allowed in file://\')"`'
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// ── Lint 2: Verify commands que só verificam existência de função, não comportamento ──
|
|
490
|
+
const existenceOnlyVerify = planTasks.filter(t =>
|
|
491
|
+
t.verifyCommand &&
|
|
492
|
+
/s\.includes\(/.test(t.verifyCommand) &&
|
|
493
|
+
!/existsSync|readFileSync.*utf8|require\(/.test(t.verifyCommand)
|
|
494
|
+
);
|
|
495
|
+
if (existenceOnlyVerify.length > 2) {
|
|
496
|
+
hints.push(
|
|
497
|
+
`HINT(verify-depth): ${existenceOnlyVerify.length} tarefa(s) verificam apenas presença de string no código ` +
|
|
498
|
+
`(s.includes). Considere adicionar verificações de comportamento: executar o código, não só inspecioná-lo.`
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// ── Lint 3: Tarefas sem verify command (T-level) ──────────────────────────
|
|
503
|
+
const noVerify = planTasks.filter(t => !t.verifyCommand && !t.done);
|
|
504
|
+
if (noVerify.length > 0) {
|
|
505
|
+
hints.push(
|
|
506
|
+
`HINT(no-verify): ${noVerify.length} tarefa(s) sem Comando de verificação: ${noVerify.map(t => t.id).join(', ')}. ` +
|
|
507
|
+
`Tarefas sem verify não serão testadas inline pelo scheduler.`
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return hints;
|
|
512
|
+
}
|
|
513
|
+
|
|
459
514
|
function compileExecutionGraphFromArtifacts(projectRoot, activeSession, options = {}) {
|
|
460
515
|
const runtime = loadRuntimeModule();
|
|
461
516
|
const parsers = loadSdkParsers();
|
|
@@ -468,12 +523,17 @@ function compileExecutionGraphFromArtifacts(projectRoot, activeSession, options
|
|
|
468
523
|
const artifactPaths = resolveRuntimeArtifactPaths(projectRoot, activeSession);
|
|
469
524
|
const specText = readTextIfExists(artifactPaths.spec);
|
|
470
525
|
const planText = readTextIfExists(artifactPaths.plan);
|
|
471
|
-
if (!specText) throw new Error(`SPEC.md ausente em ${artifactPaths.spec}`);
|
|
472
|
-
if (!planText) throw new Error(`PLAN.md ausente em ${artifactPaths.plan}`);
|
|
526
|
+
if (!specText) throw new Error(`SPEC.md ausente em ${artifactPaths.spec}\n Crie .oxe/SPEC.md com /oxe-spec no seu agente, ou use o template em oxe/templates/SPEC.template.md`);
|
|
527
|
+
if (!planText) throw new Error(`PLAN.md ausente em ${artifactPaths.plan}\n Crie .oxe/PLAN.md com /oxe-plan no seu agente (requer SPEC.md), ou use o template em oxe/templates/PLAN.template.md`);
|
|
473
528
|
const parsedSpec = parsers.parseSpec(specText);
|
|
474
529
|
const parsedPlan = parsers.parsePlan(planText);
|
|
475
530
|
const graph = runtime.compile(parsedPlan, parsedSpec, options.compilerOptions || {});
|
|
476
531
|
const validationErrors = typeof runtime.validateGraph === 'function' ? runtime.validateGraph(graph) : [];
|
|
532
|
+
|
|
533
|
+
// Static-analysis lints: detect common patterns that cause runtime failures
|
|
534
|
+
const lintHints = lintPlanForCommonPitfalls(parsedPlan, parsedSpec, projectRoot, specText);
|
|
535
|
+
if (lintHints.length) validationErrors.push(...lintHints);
|
|
536
|
+
|
|
477
537
|
const compiledGraph = runtime.toSerializable(graph);
|
|
478
538
|
const current = options.runState || readRunState(projectRoot, activeSession) || {};
|
|
479
539
|
const runId = current.run_id || makeRunId();
|
|
@@ -662,24 +722,106 @@ async function resolveRuntimeGate(projectRoot, activeSession, options = {}) {
|
|
|
662
722
|
};
|
|
663
723
|
}
|
|
664
724
|
|
|
665
|
-
// Gap 5: route execution to MultiAgentCoordinator when plan-agents.json exists
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
725
|
+
// Gap 5: route execution to MultiAgentCoordinator when plan-agents.json exists
|
|
726
|
+
function buildRuntimeExecutePreflight(projectRoot, activeSession, runState) {
|
|
727
|
+
const health = loadProjectHealth();
|
|
728
|
+
const blockers = [];
|
|
729
|
+
const warnings = [];
|
|
730
|
+
let report = null;
|
|
731
|
+
if (health && typeof health.buildHealthReport === 'function') {
|
|
732
|
+
report = health.buildHealthReport(projectRoot);
|
|
733
|
+
if (report.planSelfEvaluation && report.planSelfEvaluation.executable === false) {
|
|
734
|
+
const confidence = report.planSelfEvaluation.confidence;
|
|
735
|
+
const threshold = report.planConfidenceThreshold || 90;
|
|
736
|
+
blockers.push(`plan_confidence:${confidence == null ? 'missing' : `${confidence}%`}<=${threshold}%`);
|
|
737
|
+
}
|
|
738
|
+
if (report.executionRationality && report.executionRationality.applicable && !report.executionRationalityReady) {
|
|
739
|
+
const gaps = Array.isArray(report.criticalExecutionGaps) ? report.criticalExecutionGaps : [];
|
|
740
|
+
blockers.push(`execution_rationality:${gaps[0] || 'not_ready'}`);
|
|
741
|
+
}
|
|
742
|
+
if (report.fallbackMode && report.fallbackMode !== 'none') {
|
|
743
|
+
warnings.push(`fallback_mode:${report.fallbackMode}`);
|
|
744
|
+
}
|
|
745
|
+
} else {
|
|
746
|
+
warnings.push('health_report_unavailable');
|
|
747
|
+
}
|
|
748
|
+
const runId = runState && runState.run_id ? runState.run_id : null;
|
|
749
|
+
const queue = readRuntimeGates(projectRoot, activeSession, { runId });
|
|
750
|
+
if (queue.pending.length > 0) {
|
|
751
|
+
blockers.push(`pending_gates:${queue.pending.length}`);
|
|
752
|
+
}
|
|
753
|
+
return {
|
|
754
|
+
ok: blockers.length === 0,
|
|
755
|
+
blockers,
|
|
756
|
+
warnings,
|
|
757
|
+
runId,
|
|
758
|
+
gateQueue: {
|
|
759
|
+
pending: queue.pending.length,
|
|
760
|
+
stale: queue.staleCount || 0,
|
|
761
|
+
},
|
|
762
|
+
confidence: report && report.planSelfEvaluation ? report.planSelfEvaluation.confidence : null,
|
|
763
|
+
confidenceThreshold: report ? report.planConfidenceThreshold || 90 : 90,
|
|
764
|
+
executionRationalityReady: report ? Boolean(report.executionRationalityReady) : false,
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
async function runRuntimeExecute(projectRoot, activeSession, options = {}) {
|
|
769
|
+
const runtime = loadRuntimeModule();
|
|
770
|
+
if (!runtime) throw new Error('Runtime package não está disponível. Rode npm run build:runtime.');
|
|
669
771
|
const parsers = loadSdkParsers();
|
|
670
772
|
if (!parsers) throw new Error('SDK parsers não disponíveis.');
|
|
671
773
|
|
|
774
|
+
// Auto-wire LlmTaskExecutor if providerConfig is supplied
|
|
775
|
+
let executor = options.executor || null;
|
|
776
|
+
if (!executor && options.providerConfig) {
|
|
777
|
+
if (typeof runtime.LlmTaskExecutor !== 'function') throw new Error('Runtime não exporta LlmTaskExecutor.');
|
|
778
|
+
executor = new runtime.LlmTaskExecutor(options.providerConfig, null, options.onProgress || null);
|
|
779
|
+
}
|
|
780
|
+
// Auto-wire InplaceWorkspaceManager as default
|
|
781
|
+
let workspaceManager = options.workspaceManager || null;
|
|
782
|
+
if (!workspaceManager) {
|
|
783
|
+
if (typeof runtime.InplaceWorkspaceManager === 'function') {
|
|
784
|
+
workspaceManager = new runtime.InplaceWorkspaceManager(projectRoot);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
672
788
|
// Resolve compiled graph from run state or compile on demand
|
|
673
789
|
let current = options.runState || readRunState(projectRoot, activeSession);
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
790
|
+
if (!current || !current.compiled_graph) {
|
|
791
|
+
current = compileExecutionGraphFromArtifacts(projectRoot, activeSession, { runState: current }).run;
|
|
792
|
+
}
|
|
793
|
+
if (!current || !current.compiled_graph) {
|
|
794
|
+
throw new Error('Nenhum grafo compilado encontrado. Execute oxe-cc runtime compile primeiro.');
|
|
795
|
+
}
|
|
796
|
+
const preflight = buildRuntimeExecutePreflight(projectRoot, activeSession, current);
|
|
797
|
+
if (!preflight.ok && !options.skipPreflight) {
|
|
798
|
+
const reason = preflight.blockers[0] || 'runtime_execute_preflight_failed';
|
|
799
|
+
appendEvent(projectRoot, activeSession, {
|
|
800
|
+
type: 'WorkItemBlocked',
|
|
801
|
+
run_id: current.run_id,
|
|
802
|
+
payload: {
|
|
803
|
+
reason: 'runtime_execute_preflight_failed',
|
|
804
|
+
blockers: preflight.blockers,
|
|
805
|
+
},
|
|
806
|
+
});
|
|
807
|
+
return {
|
|
808
|
+
mode: 'preflight',
|
|
809
|
+
agentPlan: null,
|
|
810
|
+
result: {
|
|
811
|
+
run_id: current.run_id,
|
|
812
|
+
status: 'blocked',
|
|
813
|
+
completed: [],
|
|
814
|
+
failed: [],
|
|
815
|
+
blocked: ['runtime_execute_preflight'],
|
|
816
|
+
reason,
|
|
817
|
+
},
|
|
818
|
+
run: current,
|
|
819
|
+
preflight,
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
const graph = runtime.fromSerializable
|
|
823
|
+
? runtime.fromSerializable(current.compiled_graph)
|
|
824
|
+
: current.compiled_graph;
|
|
683
825
|
|
|
684
826
|
// Detect plan-agents.json (session path takes priority over root)
|
|
685
827
|
const rootAgentPlan = path.join(projectRoot, '.oxe', 'plan-agents.json');
|
|
@@ -693,45 +835,53 @@ async function runRuntimeExecute(projectRoot, activeSession, options = {}) {
|
|
|
693
835
|
// Build ctx with GateManager (Gap 1)
|
|
694
836
|
const ctx = createExecutionContext(projectRoot, activeSession, {
|
|
695
837
|
runId: current.run_id,
|
|
696
|
-
executor
|
|
697
|
-
workspaceManager
|
|
838
|
+
executor,
|
|
839
|
+
workspaceManager,
|
|
698
840
|
pluginRegistry: options.pluginRegistry || buildRuntimePluginRegistry(projectRoot),
|
|
699
841
|
schedulerOptions: options.schedulerOptions || {},
|
|
700
|
-
onEvent: (event) =>
|
|
842
|
+
onEvent: (event) => {
|
|
843
|
+
appendEvent(projectRoot, activeSession, event);
|
|
844
|
+
options.onProgress?.(event);
|
|
845
|
+
},
|
|
701
846
|
});
|
|
702
847
|
|
|
703
848
|
// Gap 5: multi-agent path if plan-agents.json exists
|
|
704
849
|
if (agentPlanPath) {
|
|
705
850
|
let agentPlan;
|
|
706
|
-
try {
|
|
707
|
-
agentPlan = JSON.parse(fs.readFileSync(agentPlanPath, 'utf8'));
|
|
708
|
-
} catch (err) {
|
|
709
|
-
throw new Error(`plan-agents.json inválido: ${err.message}`);
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
851
|
+
try {
|
|
852
|
+
agentPlan = JSON.parse(fs.readFileSync(agentPlanPath, 'utf8'));
|
|
853
|
+
} catch (err) {
|
|
854
|
+
throw new Error(`plan-agents.json inválido: ${err.message}`);
|
|
855
|
+
}
|
|
856
|
+
const planErrors = validateMultiAgentPlan(agentPlan);
|
|
857
|
+
if (planErrors.length > 0) {
|
|
858
|
+
throw new Error(`plan-agents.json inválido: ${planErrors.join('; ')}`);
|
|
859
|
+
}
|
|
860
|
+
if (typeof runtime.MultiAgentCoordinator !== 'function') {
|
|
861
|
+
throw new Error('Runtime não exporta MultiAgentCoordinator. Verifique a versão do runtime.');
|
|
862
|
+
}
|
|
863
|
+
if (typeof runtime.GitWorktreeManager !== 'function' && !options.workspaceManager) {
|
|
864
|
+
throw new Error('Runtime não exporta GitWorktreeManager. Multi-agent real exige backend git_worktree.');
|
|
865
|
+
}
|
|
866
|
+
const agents = agentPlan.agents.map((spec) => ({
|
|
867
|
+
id: spec.id,
|
|
868
|
+
executor: options.executorFactory ? options.executorFactory(spec) : (options.executor || executor),
|
|
869
|
+
workspaceManager: options.workspaceManager || new runtime.GitWorktreeManager(projectRoot),
|
|
870
|
+
assignedTaskIds: Array.isArray(spec.tasks) ? spec.tasks : [],
|
|
871
|
+
}));
|
|
723
872
|
const coordinator = new runtime.MultiAgentCoordinator();
|
|
724
873
|
const result = await coordinator.run(graph, {
|
|
725
874
|
mode: agentPlan.mode || 'parallel',
|
|
726
875
|
agents,
|
|
727
876
|
projectRoot,
|
|
728
877
|
sessionId: activeSession || null,
|
|
729
|
-
runId: current.run_id,
|
|
730
|
-
heartbeatTimeoutMs: options.heartbeatTimeoutMs ?? 120000,
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
878
|
+
runId: current.run_id,
|
|
879
|
+
heartbeatTimeoutMs: options.heartbeatTimeoutMs ?? 120000,
|
|
880
|
+
applyWorkspaceMerges: true,
|
|
881
|
+
onEvent: ctx.onEvent,
|
|
882
|
+
});
|
|
883
|
+
return { mode: agentPlan.mode || 'parallel', agentPlan, result, run: current, preflight };
|
|
884
|
+
}
|
|
735
885
|
|
|
736
886
|
// Single-agent fallback
|
|
737
887
|
if (typeof runtime.Scheduler !== 'function') {
|
|
@@ -739,8 +889,8 @@ async function runRuntimeExecute(projectRoot, activeSession, options = {}) {
|
|
|
739
889
|
}
|
|
740
890
|
const scheduler = new runtime.Scheduler();
|
|
741
891
|
const result = await scheduler.run(graph, ctx);
|
|
742
|
-
|
|
743
|
-
}
|
|
892
|
+
return { mode: 'single', agentPlan: null, result, run: current, preflight };
|
|
893
|
+
}
|
|
744
894
|
|
|
745
895
|
function readRuntimeMultiAgentStatus(projectRoot, activeSession, options = {}) {
|
|
746
896
|
const runtime = loadRuntimeModule();
|
|
@@ -755,23 +905,32 @@ function readRuntimeMultiAgentStatus(projectRoot, activeSession, options = {}) {
|
|
|
755
905
|
workspaceIsolationEnforced: false,
|
|
756
906
|
agents: [],
|
|
757
907
|
ownership: [],
|
|
758
|
-
orphanReassignments: [],
|
|
759
|
-
handoffs: [],
|
|
760
|
-
arbitrationResults: [],
|
|
761
|
-
|
|
762
|
-
|
|
908
|
+
orphanReassignments: [],
|
|
909
|
+
handoffs: [],
|
|
910
|
+
arbitrationResults: [],
|
|
911
|
+
workspaceMergeReport: null,
|
|
912
|
+
worktrees: [],
|
|
913
|
+
mergeBlockers: [],
|
|
914
|
+
mergeReadiness: null,
|
|
915
|
+
arbitrationRequired: false,
|
|
916
|
+
summary: null,
|
|
917
|
+
};
|
|
763
918
|
}
|
|
764
919
|
const runDir = path.join(projectRoot, '.oxe', 'runs', runId);
|
|
765
920
|
const statePath = path.join(runDir, 'multi-agent-state.json');
|
|
766
|
-
const summaryPath = path.join(runDir, 'multi-agent-summary.json');
|
|
767
|
-
const handoffsPath = path.join(runDir, 'handoffs.json');
|
|
768
|
-
const arbitrationPath = path.join(runDir, 'arbitration-results.json');
|
|
921
|
+
const summaryPath = path.join(runDir, 'multi-agent-summary.json');
|
|
922
|
+
const handoffsPath = path.join(runDir, 'handoffs.json');
|
|
923
|
+
const arbitrationPath = path.join(runDir, 'arbitration-results.json');
|
|
924
|
+
const workspaceMergePath = path.join(runDir, 'workspace-merge-report.json');
|
|
769
925
|
const state = runtime && typeof runtime.loadMultiAgentState === 'function'
|
|
770
926
|
? runtime.loadMultiAgentState(projectRoot, runId)
|
|
771
927
|
: readJsonIfExists(statePath);
|
|
772
|
-
const summary = runtime && typeof runtime.loadMultiAgentSummary === 'function'
|
|
773
|
-
? runtime.loadMultiAgentSummary(projectRoot, runId)
|
|
774
|
-
: readJsonIfExists(summaryPath);
|
|
928
|
+
const summary = runtime && typeof runtime.loadMultiAgentSummary === 'function'
|
|
929
|
+
? runtime.loadMultiAgentSummary(projectRoot, runId)
|
|
930
|
+
: readJsonIfExists(summaryPath);
|
|
931
|
+
const workspaceMergeReport = runtime && typeof runtime.loadWorkspaceMergeReport === 'function'
|
|
932
|
+
? runtime.loadWorkspaceMergeReport(projectRoot, runId)
|
|
933
|
+
: readJsonIfExists(workspaceMergePath);
|
|
775
934
|
const handoffs = readJsonIfExists(handoffsPath);
|
|
776
935
|
const arbitrationResults = readJsonIfExists(arbitrationPath);
|
|
777
936
|
return {
|
|
@@ -784,10 +943,41 @@ function readRuntimeMultiAgentStatus(projectRoot, activeSession, options = {}) {
|
|
|
784
943
|
ownership: state && Array.isArray(state.ownership) ? state.ownership : [],
|
|
785
944
|
orphanReassignments: state && Array.isArray(state.orphan_reassignments) ? state.orphan_reassignments : [],
|
|
786
945
|
handoffs: Array.isArray(handoffs) ? handoffs : [],
|
|
787
|
-
arbitrationResults: Array.isArray(arbitrationResults) ? arbitrationResults : [],
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
946
|
+
arbitrationResults: Array.isArray(arbitrationResults) ? arbitrationResults : [],
|
|
947
|
+
workspaceMergeReport: workspaceMergeReport || null,
|
|
948
|
+
worktrees: workspaceMergeReport && Array.isArray(workspaceMergeReport.records) ? workspaceMergeReport.records : [],
|
|
949
|
+
mergeBlockers: workspaceMergeReport && Array.isArray(workspaceMergeReport.blockers) ? workspaceMergeReport.blockers : [],
|
|
950
|
+
mergeReadiness: workspaceMergeReport && workspaceMergeReport.merge_readiness ? workspaceMergeReport.merge_readiness : null,
|
|
951
|
+
arbitrationRequired: Boolean(workspaceMergeReport && workspaceMergeReport.arbitration_required),
|
|
952
|
+
summary: summary || null,
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
function validateMultiAgentPlan(agentPlan) {
|
|
957
|
+
const errors = [];
|
|
958
|
+
const allowedModes = new Set(['parallel', 'competitive', 'cooperative']);
|
|
959
|
+
const mode = agentPlan && agentPlan.mode ? String(agentPlan.mode) : 'parallel';
|
|
960
|
+
if (!allowedModes.has(mode)) errors.push(`mode inválido: ${mode}`);
|
|
961
|
+
if (!Array.isArray(agentPlan && agentPlan.agents) || agentPlan.agents.length === 0) {
|
|
962
|
+
errors.push('campo "agents" vazio ou ausente');
|
|
963
|
+
return errors;
|
|
964
|
+
}
|
|
965
|
+
const schemaVersion = Number(agentPlan.schema_version || agentPlan.schema || 0);
|
|
966
|
+
const seen = new Set();
|
|
967
|
+
for (const [idx, spec] of agentPlan.agents.entries()) {
|
|
968
|
+
const id = spec && spec.id ? String(spec.id) : '';
|
|
969
|
+
if (!id) errors.push(`agents[${idx}].id ausente`);
|
|
970
|
+
if (id && seen.has(id)) errors.push(`agent duplicado: ${id}`);
|
|
971
|
+
if (id) seen.add(id);
|
|
972
|
+
if (schemaVersion >= 3 && !spec.persona) errors.push(`${id || `agents[${idx}]`}.persona ausente`);
|
|
973
|
+
if (schemaVersion >= 3 && !spec.model_hint) errors.push(`${id || `agents[${idx}]`}.model_hint ausente`);
|
|
974
|
+
if (spec.tasks != null && !Array.isArray(spec.tasks)) errors.push(`${id || `agents[${idx}]`}.tasks deve ser array`);
|
|
975
|
+
}
|
|
976
|
+
if ((mode === 'competitive' || mode === 'cooperative') && agentPlan.agents.length < 2) {
|
|
977
|
+
errors.push(`${mode} exige pelo menos 2 agentes`);
|
|
978
|
+
}
|
|
979
|
+
return errors;
|
|
980
|
+
}
|
|
791
981
|
|
|
792
982
|
function loadRuntimeVerificationArtifacts(projectRoot, runState) {
|
|
793
983
|
const runtime = loadRuntimeModule();
|
|
@@ -813,7 +1003,7 @@ function loadRuntimeVerificationArtifacts(projectRoot, runState) {
|
|
|
813
1003
|
return { manifest, residualRisks, evidenceCoverage };
|
|
814
1004
|
}
|
|
815
1005
|
|
|
816
|
-
function countVerificationEvidenceRefs(runState, verificationArtifacts) {
|
|
1006
|
+
function countVerificationEvidenceRefs(runState, verificationArtifacts) {
|
|
817
1007
|
if (verificationArtifacts && verificationArtifacts.manifest && Array.isArray(verificationArtifacts.manifest.checks)) {
|
|
818
1008
|
return verificationArtifacts.manifest.checks.reduce((sum, check) => {
|
|
819
1009
|
return sum + (Array.isArray(check.evidence_refs) ? check.evidence_refs.length : 0);
|
|
@@ -825,7 +1015,26 @@ function countVerificationEvidenceRefs(runState, verificationArtifacts) {
|
|
|
825
1015
|
}, 0);
|
|
826
1016
|
}
|
|
827
1017
|
return 0;
|
|
828
|
-
}
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
function detectOrphanWorktrees(projectRoot, runId) {
|
|
1021
|
+
const workspacesDir = path.join(projectRoot, '.oxe', 'workspaces');
|
|
1022
|
+
if (!fs.existsSync(workspacesDir)) return [];
|
|
1023
|
+
const active = new Set();
|
|
1024
|
+
const mergeReport = readJsonIfExists(path.join(projectRoot, '.oxe', 'runs', runId, 'workspace-merge-report.json'));
|
|
1025
|
+
for (const record of (mergeReport && Array.isArray(mergeReport.records) ? mergeReport.records : [])) {
|
|
1026
|
+
if (record && record.workspace_id) active.add(String(record.workspace_id));
|
|
1027
|
+
}
|
|
1028
|
+
return fs.readdirSync(workspacesDir, { withFileTypes: true })
|
|
1029
|
+
.filter((entry) => entry.isDirectory())
|
|
1030
|
+
.map((entry) => entry.name)
|
|
1031
|
+
.filter((name) => !active.has(name))
|
|
1032
|
+
.map((name) => ({
|
|
1033
|
+
workspace_id: name,
|
|
1034
|
+
path: path.join(workspacesDir, name),
|
|
1035
|
+
next_action: 'inspecione o worktree órfão; depois remova com git worktree remove --force se não houver diffs úteis',
|
|
1036
|
+
}));
|
|
1037
|
+
}
|
|
829
1038
|
|
|
830
1039
|
function buildRuntimeModeStatus(runState) {
|
|
831
1040
|
if (!runState) {
|
|
@@ -906,6 +1115,11 @@ function buildRecoveryConsistency(projectRoot, activeSession, runState, journal,
|
|
|
906
1115
|
const runDir = runState && runState.run_id ? path.join(projectRoot, '.oxe', 'runs', runState.run_id) : null;
|
|
907
1116
|
const allEvents = readEvents(projectRoot, activeSession);
|
|
908
1117
|
const runEvents = runState && runState.run_id ? allEvents.filter((event) => event.run_id === runState.run_id) : [];
|
|
1118
|
+
// Detect if execution has ever started (at least one attempt recorded)
|
|
1119
|
+
const attemptCount = runState && runState.canonical_state && runState.canonical_state.summary
|
|
1120
|
+
? (runState.canonical_state.summary.attempt_count || 0)
|
|
1121
|
+
: 0;
|
|
1122
|
+
const executionStarted = attemptCount > 0;
|
|
909
1123
|
const issues = [];
|
|
910
1124
|
if (!activeRunRef || activeRunRef.run_id !== (runState && runState.run_id)) {
|
|
911
1125
|
issues.push('ACTIVE-RUN.json não referencia o mesmo run persistido em .oxe/runs/.');
|
|
@@ -913,10 +1127,12 @@ function buildRecoveryConsistency(projectRoot, activeSession, runState, journal,
|
|
|
913
1127
|
if (!runFile || !fs.existsSync(runFile)) {
|
|
914
1128
|
issues.push('Arquivo canónico da run ausente em .oxe/runs/<run>.json.');
|
|
915
1129
|
}
|
|
916
|
-
|
|
1130
|
+
// Journal is only created after execution starts — skip this check pre-execution
|
|
1131
|
+
if (!journal && executionStarted) {
|
|
917
1132
|
issues.push('Journal ausente para recover/replay.');
|
|
918
1133
|
}
|
|
919
|
-
|
|
1134
|
+
// Events for this run only exist after execution — skip pre-execution
|
|
1135
|
+
if (runEvents.length === 0 && executionStarted) {
|
|
920
1136
|
issues.push('Nenhum evento NDJSON encontrado para a run ativa.');
|
|
921
1137
|
}
|
|
922
1138
|
if (!runState || !runState.canonical_state) {
|
|
@@ -990,11 +1206,17 @@ function writeRecoverySummaryMarkdown(projectRoot, activeSession, runState, reco
|
|
|
990
1206
|
'',
|
|
991
1207
|
'## Work items órfãos',
|
|
992
1208
|
'',
|
|
993
|
-
...(Array.isArray(recoverySummary.orphan_work_items) && recoverySummary.orphan_work_items.length
|
|
994
|
-
? recoverySummary.orphan_work_items.map((item) => `- ${item}`)
|
|
995
|
-
: ['- Nenhum']),
|
|
996
|
-
'',
|
|
997
|
-
'##
|
|
1209
|
+
...(Array.isArray(recoverySummary.orphan_work_items) && recoverySummary.orphan_work_items.length
|
|
1210
|
+
? recoverySummary.orphan_work_items.map((item) => `- ${item}`)
|
|
1211
|
+
: ['- Nenhum']),
|
|
1212
|
+
'',
|
|
1213
|
+
'## Worktrees órfãos',
|
|
1214
|
+
'',
|
|
1215
|
+
...(Array.isArray(recoverySummary.orphan_worktrees) && recoverySummary.orphan_worktrees.length
|
|
1216
|
+
? recoverySummary.orphan_worktrees.map((item) => `- ${item.workspace_id} · ${item.next_action}`)
|
|
1217
|
+
: ['- Nenhum']),
|
|
1218
|
+
'',
|
|
1219
|
+
'## Tentativas incompletas',
|
|
998
1220
|
'',
|
|
999
1221
|
...(recoverySummary.consistency && Array.isArray(recoverySummary.consistency.incomplete_attempts) && recoverySummary.consistency.incomplete_attempts.length
|
|
1000
1222
|
? recoverySummary.consistency.incomplete_attempts.map((item) => `- ${item.work_item_id} · ${item.attempt_id || 'attempt'} · ${item.outcome || 'unknown'}`)
|
|
@@ -1146,7 +1368,7 @@ function projectRuntimeArtifacts(projectRoot, activeSession, options = {}) {
|
|
|
1146
1368
|
const paths = resolveRuntimeArtifactPaths(projectRoot, activeSession);
|
|
1147
1369
|
const op = operationalPaths(projectRoot, activeSession);
|
|
1148
1370
|
const projectionRefs = {
|
|
1149
|
-
plan_ref: path.relative(projectRoot, paths.plan).replace(/\\/g, '/'),
|
|
1371
|
+
plan_ref: path.relative(projectRoot, paths.plan.replace(/PLAN\.md$/, 'PLAN-STATUS.md')).replace(/\\/g, '/'),
|
|
1150
1372
|
verify_ref: path.relative(projectRoot, paths.verify).replace(/\\/g, '/'),
|
|
1151
1373
|
state_ref: path.relative(projectRoot, paths.state).replace(/\\/g, '/'),
|
|
1152
1374
|
run_summary_ref: path.relative(projectRoot, path.join(op.executionRoot, 'RUN-SUMMARY.md')).replace(/\\/g, '/'),
|
|
@@ -1156,10 +1378,12 @@ function projectRuntimeArtifacts(projectRoot, activeSession, options = {}) {
|
|
|
1156
1378
|
generated_at: new Date().toISOString(),
|
|
1157
1379
|
};
|
|
1158
1380
|
if (options.write !== false) {
|
|
1159
|
-
|
|
1381
|
+
// Write plan projection to PLAN-STATUS.md — never overwrite the source PLAN.md
|
|
1382
|
+
const planStatusPath = paths.plan.replace(/PLAN\.md$/, 'PLAN-STATUS.md');
|
|
1383
|
+
ensureDirForFile(planStatusPath);
|
|
1160
1384
|
ensureDirForFile(paths.verify);
|
|
1161
1385
|
ensureDirForFile(paths.state);
|
|
1162
|
-
fs.writeFileSync(
|
|
1386
|
+
fs.writeFileSync(planStatusPath, projections.plan + '\n', 'utf8');
|
|
1163
1387
|
fs.writeFileSync(paths.verify, projections.verify + '\n', 'utf8');
|
|
1164
1388
|
fs.writeFileSync(paths.state, projections.state + '\n', 'utf8');
|
|
1165
1389
|
fs.writeFileSync(path.join(op.executionRoot, 'RUN-SUMMARY.md'), projections.runSummary + '\n', 'utf8');
|
|
@@ -1355,13 +1579,14 @@ function recoverRuntimeState(projectRoot, activeSession, options = {}) {
|
|
|
1355
1579
|
journal,
|
|
1356
1580
|
verificationArtifacts
|
|
1357
1581
|
);
|
|
1358
|
-
const recoverySummary = {
|
|
1359
|
-
recovered_at: new Date().toISOString(),
|
|
1360
|
-
journal_state: journal.scheduler_state,
|
|
1361
|
-
orphan_work_items: orphanWorkItems,
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1582
|
+
const recoverySummary = {
|
|
1583
|
+
recovered_at: new Date().toISOString(),
|
|
1584
|
+
journal_state: journal.scheduler_state,
|
|
1585
|
+
orphan_work_items: orphanWorkItems,
|
|
1586
|
+
orphan_worktrees: detectOrphanWorktrees(projectRoot, current.run_id),
|
|
1587
|
+
pending_gates: readRuntimeGates(projectRoot, activeSession, { runId: current.run_id }).pending.map((gate) => gate.gate_id),
|
|
1588
|
+
consistency,
|
|
1589
|
+
};
|
|
1365
1590
|
const runDir = path.join(projectRoot, '.oxe', 'runs', current.run_id);
|
|
1366
1591
|
ensureDir(runDir);
|
|
1367
1592
|
const recoverySummaryPath = path.join(runDir, 'recovery-summary.json');
|
|
@@ -2243,8 +2468,9 @@ module.exports = {
|
|
|
2243
2468
|
buildRecoveryConsistency,
|
|
2244
2469
|
readRuntimeGates,
|
|
2245
2470
|
resolveRuntimeGate,
|
|
2246
|
-
createExecutionContext,
|
|
2247
|
-
|
|
2471
|
+
createExecutionContext,
|
|
2472
|
+
buildRuntimeExecutePreflight,
|
|
2473
|
+
runRuntimeExecute,
|
|
2248
2474
|
runRuntimeVerify,
|
|
2249
2475
|
projectRuntimeArtifacts,
|
|
2250
2476
|
runRuntimeCiChecks,
|