oxe-cc 1.7.0 → 1.8.3

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 (44) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/README.md +37 -37
  3. package/bin/lib/oxe-agent-install.cjs +24 -8
  4. package/bin/lib/oxe-manifest.cjs +20 -13
  5. package/bin/lib/oxe-operational.cjs +234 -41
  6. package/bin/lib/oxe-project-health.cjs +219 -52
  7. package/bin/lib/oxe-rationality.cjs +9 -7
  8. package/bin/oxe-cc.js +443 -236
  9. package/lib/runtime/compiler/graph-compiler.js +1 -1
  10. package/lib/runtime/executor/action-tool-map.js +4 -0
  11. package/lib/runtime/executor/built-in-tools.js +27 -0
  12. package/lib/runtime/executor/llm-task-executor.d.ts +4 -1
  13. package/lib/runtime/executor/llm-task-executor.js +41 -5
  14. package/lib/runtime/executor/node-prompt-builder.d.ts +4 -1
  15. package/lib/runtime/executor/node-prompt-builder.js +13 -2
  16. package/lib/runtime/models/failure.d.ts +1 -1
  17. package/lib/runtime/scheduler/scheduler.d.ts +5 -1
  18. package/lib/runtime/scheduler/scheduler.js +82 -14
  19. package/lib/runtime/verification/verification-compiler.js +7 -5
  20. package/lib/sdk/index.cjs +48 -44
  21. package/oxe/templates/PLAN.template.md +23 -9
  22. package/oxe/templates/SPEC.template.md +55 -22
  23. package/oxe/workflows/plan.md +18 -6
  24. package/oxe/workflows/spec.md +31 -9
  25. package/package.json +103 -100
  26. package/packages/runtime/package.json +14 -14
  27. package/packages/runtime/src/compiler/graph-compiler.ts +1 -1
  28. package/packages/runtime/src/evidence/evidence-store.ts +2 -2
  29. package/packages/runtime/src/executor/action-tool-map.ts +4 -0
  30. package/packages/runtime/src/executor/built-in-tools.ts +29 -0
  31. package/packages/runtime/src/executor/llm-task-executor.ts +46 -4
  32. package/packages/runtime/src/executor/node-prompt-builder.ts +18 -1
  33. package/packages/runtime/src/models/failure.ts +2 -0
  34. package/packages/runtime/src/scheduler/scheduler.ts +93 -15
  35. package/packages/runtime/src/verification/verification-compiler.ts +7 -5
  36. package/vscode-extension/package.json +184 -184
  37. package/vscode-extension/oxe-agents-0.9.1.vsix +0 -0
  38. package/vscode-extension/oxe-agents-0.9.2.vsix +0 -0
  39. package/vscode-extension/oxe-agents-1.0.0.vsix +0 -0
  40. package/vscode-extension/oxe-agents-1.4.0.vsix +0 -0
  41. package/vscode-extension/oxe-agents-1.5.0.vsix +0 -0
  42. package/vscode-extension/oxe-agents-1.5.1.vsix +0 -0
  43. package/vscode-extension/oxe-agents-1.6.0.vsix +0 -0
  44. package/vscode-extension/oxe-agents-1.7.0.vsix +0 -0
@@ -79,6 +79,31 @@ function loadRuntimeModule() {
79
79
  }
80
80
  }
81
81
 
82
+ // Gap 1: factory that always wires GateManager into ctx
83
+ function createExecutionContext(projectRoot, activeSession, options = {}) {
84
+ const runtime = loadRuntimeModule();
85
+ const runId = options.runId || makeRunId();
86
+ const gateManager = (runtime && typeof runtime.GateManager === 'function')
87
+ ? new runtime.GateManager(projectRoot, activeSession || null, runId)
88
+ : null;
89
+ return {
90
+ projectRoot,
91
+ sessionId: activeSession || null,
92
+ runId,
93
+ executor: options.executor || null,
94
+ workspaceManager: options.workspaceManager || null,
95
+ gateManager,
96
+ policyEngine: options.policyEngine || null,
97
+ policyActor: options.policyActor || 'runtime',
98
+ quota: options.quota || null,
99
+ pluginRegistry: options.pluginRegistry || null,
100
+ auditTrail: options.auditTrail || null,
101
+ evidenceStore: options.evidenceStore || null,
102
+ onEvent: options.onEvent || null,
103
+ options: options.schedulerOptions || {},
104
+ };
105
+ }
106
+
82
107
  function buildRuntimePluginRegistry(projectRoot) {
83
108
  const runtime = loadRuntimeModule();
84
109
  if (!runtime || typeof runtime.PluginRegistry !== 'function') return null;
@@ -431,6 +456,61 @@ function reduceCanonicalRunState(projectRoot, activeSession, options = {}) {
431
456
  return serializeCanonicalState(reduceCanonicalRunStateLive(projectRoot, activeSession, options));
432
457
  }
433
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
+
434
514
  function compileExecutionGraphFromArtifacts(projectRoot, activeSession, options = {}) {
435
515
  const runtime = loadRuntimeModule();
436
516
  const parsers = loadSdkParsers();
@@ -443,12 +523,17 @@ function compileExecutionGraphFromArtifacts(projectRoot, activeSession, options
443
523
  const artifactPaths = resolveRuntimeArtifactPaths(projectRoot, activeSession);
444
524
  const specText = readTextIfExists(artifactPaths.spec);
445
525
  const planText = readTextIfExists(artifactPaths.plan);
446
- if (!specText) throw new Error(`SPEC.md ausente em ${artifactPaths.spec}`);
447
- 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`);
448
528
  const parsedSpec = parsers.parseSpec(specText);
449
529
  const parsedPlan = parsers.parsePlan(planText);
450
530
  const graph = runtime.compile(parsedPlan, parsedSpec, options.compilerOptions || {});
451
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
+
452
537
  const compiledGraph = runtime.toSerializable(graph);
453
538
  const current = options.runState || readRunState(projectRoot, activeSession) || {};
454
539
  const runId = current.run_id || makeRunId();
@@ -637,11 +722,108 @@ async function resolveRuntimeGate(projectRoot, activeSession, options = {}) {
637
722
  };
638
723
  }
639
724
 
640
- function readRuntimeMultiAgentStatus(projectRoot, activeSession, options = {}) {
641
- const runtime = loadRuntimeModule();
642
- const current = readRunState(projectRoot, activeSession);
643
- const runId = options.runId || (current && current.run_id) || null;
644
- if (!runId) {
725
+ // Gap 5: route execution to MultiAgentCoordinator when plan-agents.json exists
726
+ async function runRuntimeExecute(projectRoot, activeSession, options = {}) {
727
+ const runtime = loadRuntimeModule();
728
+ if (!runtime) throw new Error('Runtime package não está disponível. Rode npm run build:runtime.');
729
+ const parsers = loadSdkParsers();
730
+ if (!parsers) throw new Error('SDK parsers não disponíveis.');
731
+
732
+ // Auto-wire LlmTaskExecutor if providerConfig is supplied
733
+ let executor = options.executor || null;
734
+ if (!executor && options.providerConfig) {
735
+ if (typeof runtime.LlmTaskExecutor !== 'function') throw new Error('Runtime não exporta LlmTaskExecutor.');
736
+ executor = new runtime.LlmTaskExecutor(options.providerConfig, null, options.onProgress || null);
737
+ }
738
+ // Auto-wire InplaceWorkspaceManager as default
739
+ let workspaceManager = options.workspaceManager || null;
740
+ if (!workspaceManager) {
741
+ if (typeof runtime.InplaceWorkspaceManager === 'function') {
742
+ workspaceManager = new runtime.InplaceWorkspaceManager(projectRoot);
743
+ }
744
+ }
745
+
746
+ // Resolve compiled graph from run state or compile on demand
747
+ let current = options.runState || readRunState(projectRoot, activeSession);
748
+ if (!current || !current.compiled_graph) {
749
+ current = compileExecutionGraphFromArtifacts(projectRoot, activeSession, { runState: current }).run;
750
+ }
751
+ if (!current || !current.compiled_graph) {
752
+ throw new Error('Nenhum grafo compilado encontrado. Execute oxe-cc runtime compile primeiro.');
753
+ }
754
+ const graph = runtime.fromSerializable
755
+ ? runtime.fromSerializable(current.compiled_graph)
756
+ : current.compiled_graph;
757
+
758
+ // Detect plan-agents.json (session path takes priority over root)
759
+ const rootAgentPlan = path.join(projectRoot, '.oxe', 'plan-agents.json');
760
+ const sessAgentPlan = activeSession
761
+ ? path.join(projectRoot, '.oxe', activeSession, 'plan', 'plan-agents.json')
762
+ : null;
763
+ const agentPlanPath = (sessAgentPlan && fs.existsSync(sessAgentPlan))
764
+ ? sessAgentPlan
765
+ : (fs.existsSync(rootAgentPlan) ? rootAgentPlan : null);
766
+
767
+ // Build ctx with GateManager (Gap 1)
768
+ const ctx = createExecutionContext(projectRoot, activeSession, {
769
+ runId: current.run_id,
770
+ executor,
771
+ workspaceManager,
772
+ pluginRegistry: options.pluginRegistry || buildRuntimePluginRegistry(projectRoot),
773
+ schedulerOptions: options.schedulerOptions || {},
774
+ onEvent: (event) => {
775
+ appendEvent(projectRoot, activeSession, event);
776
+ options.onProgress?.(event);
777
+ },
778
+ });
779
+
780
+ // Gap 5: multi-agent path if plan-agents.json exists
781
+ if (agentPlanPath) {
782
+ let agentPlan;
783
+ try {
784
+ agentPlan = JSON.parse(fs.readFileSync(agentPlanPath, 'utf8'));
785
+ } catch (err) {
786
+ throw new Error(`plan-agents.json inválido: ${err.message}`);
787
+ }
788
+ if (!Array.isArray(agentPlan.agents) || agentPlan.agents.length === 0) {
789
+ throw new Error('plan-agents.json não contém agentes válidos (campo "agents" vazio ou ausente).');
790
+ }
791
+ if (typeof runtime.MultiAgentCoordinator !== 'function') {
792
+ throw new Error('Runtime não exporta MultiAgentCoordinator. Verifique a versão do runtime.');
793
+ }
794
+ const agents = agentPlan.agents.map((spec) => ({
795
+ id: spec.id,
796
+ executor: options.executorFactory ? options.executorFactory(spec) : (options.executor || null),
797
+ workspaceManager: options.workspaceManager || null,
798
+ assignedTaskIds: Array.isArray(spec.tasks) ? spec.tasks : [],
799
+ }));
800
+ const coordinator = new runtime.MultiAgentCoordinator();
801
+ const result = await coordinator.run(graph, {
802
+ mode: agentPlan.mode || 'parallel',
803
+ agents,
804
+ projectRoot,
805
+ sessionId: activeSession || null,
806
+ runId: current.run_id,
807
+ heartbeatTimeoutMs: options.heartbeatTimeoutMs ?? 120000,
808
+ onEvent: ctx.onEvent,
809
+ });
810
+ return { mode: agentPlan.mode || 'parallel', agentPlan, result, run: current };
811
+ }
812
+
813
+ // Single-agent fallback
814
+ if (typeof runtime.Scheduler !== 'function') {
815
+ throw new Error('Runtime não exporta Scheduler. Verifique a versão do runtime.');
816
+ }
817
+ const scheduler = new runtime.Scheduler();
818
+ const result = await scheduler.run(graph, ctx);
819
+ return { mode: 'single', agentPlan: null, result, run: current };
820
+ }
821
+
822
+ function readRuntimeMultiAgentStatus(projectRoot, activeSession, options = {}) {
823
+ const runtime = loadRuntimeModule();
824
+ const current = readRunState(projectRoot, activeSession);
825
+ const runId = options.runId || (current && current.run_id) || null;
826
+ if (!runId) {
645
827
  return {
646
828
  path: null,
647
829
  enabled: false,
@@ -649,40 +831,40 @@ function readRuntimeMultiAgentStatus(projectRoot, activeSession, options = {}) {
649
831
  mode: null,
650
832
  workspaceIsolationEnforced: false,
651
833
  agents: [],
652
- ownership: [],
653
- orphanReassignments: [],
654
- handoffs: [],
655
- arbitrationResults: [],
656
- summary: null,
657
- };
658
- }
659
- const runDir = path.join(projectRoot, '.oxe', 'runs', runId);
660
- const statePath = path.join(runDir, 'multi-agent-state.json');
661
- const summaryPath = path.join(runDir, 'multi-agent-summary.json');
662
- const handoffsPath = path.join(runDir, 'handoffs.json');
663
- const arbitrationPath = path.join(runDir, 'arbitration-results.json');
664
- const state = runtime && typeof runtime.loadMultiAgentState === 'function'
665
- ? runtime.loadMultiAgentState(projectRoot, runId)
666
- : readJsonIfExists(statePath);
667
- const summary = runtime && typeof runtime.loadMultiAgentSummary === 'function'
668
- ? runtime.loadMultiAgentSummary(projectRoot, runId)
669
- : readJsonIfExists(summaryPath);
670
- const handoffs = readJsonIfExists(handoffsPath);
671
- const arbitrationResults = readJsonIfExists(arbitrationPath);
672
- return {
673
- path: statePath,
674
- enabled: Boolean(state),
834
+ ownership: [],
835
+ orphanReassignments: [],
836
+ handoffs: [],
837
+ arbitrationResults: [],
838
+ summary: null,
839
+ };
840
+ }
841
+ const runDir = path.join(projectRoot, '.oxe', 'runs', runId);
842
+ const statePath = path.join(runDir, 'multi-agent-state.json');
843
+ const summaryPath = path.join(runDir, 'multi-agent-summary.json');
844
+ const handoffsPath = path.join(runDir, 'handoffs.json');
845
+ const arbitrationPath = path.join(runDir, 'arbitration-results.json');
846
+ const state = runtime && typeof runtime.loadMultiAgentState === 'function'
847
+ ? runtime.loadMultiAgentState(projectRoot, runId)
848
+ : readJsonIfExists(statePath);
849
+ const summary = runtime && typeof runtime.loadMultiAgentSummary === 'function'
850
+ ? runtime.loadMultiAgentSummary(projectRoot, runId)
851
+ : readJsonIfExists(summaryPath);
852
+ const handoffs = readJsonIfExists(handoffsPath);
853
+ const arbitrationResults = readJsonIfExists(arbitrationPath);
854
+ return {
855
+ path: statePath,
856
+ enabled: Boolean(state),
675
857
  runId,
676
858
  mode: state && state.mode ? state.mode : null,
677
859
  workspaceIsolationEnforced: Boolean(state && state.workspace_isolation_enforced),
678
860
  agents: state && Array.isArray(state.agent_results) ? state.agent_results : [],
679
861
  ownership: state && Array.isArray(state.ownership) ? state.ownership : [],
680
- orphanReassignments: state && Array.isArray(state.orphan_reassignments) ? state.orphan_reassignments : [],
681
- handoffs: Array.isArray(handoffs) ? handoffs : [],
682
- arbitrationResults: Array.isArray(arbitrationResults) ? arbitrationResults : [],
683
- summary: summary || null,
684
- };
685
- }
862
+ orphanReassignments: state && Array.isArray(state.orphan_reassignments) ? state.orphan_reassignments : [],
863
+ handoffs: Array.isArray(handoffs) ? handoffs : [],
864
+ arbitrationResults: Array.isArray(arbitrationResults) ? arbitrationResults : [],
865
+ summary: summary || null,
866
+ };
867
+ }
686
868
 
687
869
  function loadRuntimeVerificationArtifacts(projectRoot, runState) {
688
870
  const runtime = loadRuntimeModule();
@@ -801,6 +983,11 @@ function buildRecoveryConsistency(projectRoot, activeSession, runState, journal,
801
983
  const runDir = runState && runState.run_id ? path.join(projectRoot, '.oxe', 'runs', runState.run_id) : null;
802
984
  const allEvents = readEvents(projectRoot, activeSession);
803
985
  const runEvents = runState && runState.run_id ? allEvents.filter((event) => event.run_id === runState.run_id) : [];
986
+ // Detect if execution has ever started (at least one attempt recorded)
987
+ const attemptCount = runState && runState.canonical_state && runState.canonical_state.summary
988
+ ? (runState.canonical_state.summary.attempt_count || 0)
989
+ : 0;
990
+ const executionStarted = attemptCount > 0;
804
991
  const issues = [];
805
992
  if (!activeRunRef || activeRunRef.run_id !== (runState && runState.run_id)) {
806
993
  issues.push('ACTIVE-RUN.json não referencia o mesmo run persistido em .oxe/runs/.');
@@ -808,10 +995,12 @@ function buildRecoveryConsistency(projectRoot, activeSession, runState, journal,
808
995
  if (!runFile || !fs.existsSync(runFile)) {
809
996
  issues.push('Arquivo canónico da run ausente em .oxe/runs/<run>.json.');
810
997
  }
811
- if (!journal) {
998
+ // Journal is only created after execution starts — skip this check pre-execution
999
+ if (!journal && executionStarted) {
812
1000
  issues.push('Journal ausente para recover/replay.');
813
1001
  }
814
- if (runEvents.length === 0) {
1002
+ // Events for this run only exist after execution — skip pre-execution
1003
+ if (runEvents.length === 0 && executionStarted) {
815
1004
  issues.push('Nenhum evento NDJSON encontrado para a run ativa.');
816
1005
  }
817
1006
  if (!runState || !runState.canonical_state) {
@@ -1041,7 +1230,7 @@ function projectRuntimeArtifacts(projectRoot, activeSession, options = {}) {
1041
1230
  const paths = resolveRuntimeArtifactPaths(projectRoot, activeSession);
1042
1231
  const op = operationalPaths(projectRoot, activeSession);
1043
1232
  const projectionRefs = {
1044
- plan_ref: path.relative(projectRoot, paths.plan).replace(/\\/g, '/'),
1233
+ plan_ref: path.relative(projectRoot, paths.plan.replace(/PLAN\.md$/, 'PLAN-STATUS.md')).replace(/\\/g, '/'),
1045
1234
  verify_ref: path.relative(projectRoot, paths.verify).replace(/\\/g, '/'),
1046
1235
  state_ref: path.relative(projectRoot, paths.state).replace(/\\/g, '/'),
1047
1236
  run_summary_ref: path.relative(projectRoot, path.join(op.executionRoot, 'RUN-SUMMARY.md')).replace(/\\/g, '/'),
@@ -1051,10 +1240,12 @@ function projectRuntimeArtifacts(projectRoot, activeSession, options = {}) {
1051
1240
  generated_at: new Date().toISOString(),
1052
1241
  };
1053
1242
  if (options.write !== false) {
1054
- ensureDirForFile(paths.plan);
1243
+ // Write plan projection to PLAN-STATUS.md — never overwrite the source PLAN.md
1244
+ const planStatusPath = paths.plan.replace(/PLAN\.md$/, 'PLAN-STATUS.md');
1245
+ ensureDirForFile(planStatusPath);
1055
1246
  ensureDirForFile(paths.verify);
1056
1247
  ensureDirForFile(paths.state);
1057
- fs.writeFileSync(paths.plan, projections.plan + '\n', 'utf8');
1248
+ fs.writeFileSync(planStatusPath, projections.plan + '\n', 'utf8');
1058
1249
  fs.writeFileSync(paths.verify, projections.verify + '\n', 'utf8');
1059
1250
  fs.writeFileSync(paths.state, projections.state + '\n', 'utf8');
1060
1251
  fs.writeFileSync(path.join(op.executionRoot, 'RUN-SUMMARY.md'), projections.runSummary + '\n', 'utf8');
@@ -2138,6 +2329,8 @@ module.exports = {
2138
2329
  buildRecoveryConsistency,
2139
2330
  readRuntimeGates,
2140
2331
  resolveRuntimeGate,
2332
+ createExecutionContext,
2333
+ runRuntimeExecute,
2141
2334
  runRuntimeVerify,
2142
2335
  projectRuntimeArtifacts,
2143
2336
  runRuntimeCiChecks,