oxe-cc 1.12.0 → 1.14.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 +25 -0
- package/README.md +1 -1
- package/bin/lib/oxe-project-health.cjs +75 -0
- package/bin/oxe-cc.js +203 -41
- package/docs/INTEGRATION.md +152 -0
- package/docs/oxe-artifact-map.html +1172 -0
- package/lib/sdk/index.cjs +2 -0
- package/lib/sdk/index.d.ts +185 -159
- package/package.json +2 -2
- package/packages/runtime/package.json +1 -1
- package/vscode-extension/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,31 @@ Todas as versões seguem [Semantic Versioning](https://semver.org/). As mudança
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [1.14.0] — 2026-05-30
|
|
8
|
+
|
|
9
|
+
### Contratos de integração para hosts — reatividade + dashboard embutível
|
|
10
|
+
|
|
11
|
+
Aditivo — nenhum contrato existente muda. Complementa o 1.13.0 (`status --json --summary`, `agentSkills`) para hosts como o OXESpace embutirem o oxe-cc de forma reativa e visual.
|
|
12
|
+
|
|
13
|
+
- **`oxe events --tail [N] --json [--since <evt_id>] [--session <s>]`** — projeção read-only e versionada (`oxeEventsSchema: 1`) do log append-only `.oxe/OXE-EVENTS.ndjson`: `{ summary: { total, byType, lastEvent }, events: [...] }`. `--since` devolve só os eventos novos desde um `event_id` conhecido (leitura incremental). Reusa `operational.readEvents`/`summarizeEvents`. Um host normalmente observa o arquivo e chama `status --json --summary`; este comando é a forma documentada de ler o tail sem reparsear tudo.
|
|
14
|
+
- **`oxe dashboard --json`** — emite **uma linha** estável e versionada (`oxeDashboardSchema: 1`) com `{ url, port, readOnly, projectRoot }` assim que o servidor sobe, e **continua servindo**. Permite a um host capturar URL/porta de forma robusta e embutir o dashboard num webview, em vez de raspar o banner humano. Combine com `--no-open --port 0` para porta efêmera.
|
|
15
|
+
- **`dashboard --port 0`** agora seleciona uma porta efêmera de verdade (antes caía no default `4173`).
|
|
16
|
+
- **`docs/INTEGRATION.md`** — contrato estável para hosts: `status --json --summary`, `agentSkills`, schema do `OXE-EVENTS.ndjson`, `events --json` e `dashboard --json`; marca o que é estável vs experimental.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## [1.13.0] — 2026-05-29
|
|
21
|
+
|
|
22
|
+
### Contratos de integração para hosts (IDEs, OXESpace)
|
|
23
|
+
|
|
24
|
+
Aditivo — nenhum contrato existente muda. Facilita hosts consumirem o oxe-cc.
|
|
25
|
+
|
|
26
|
+
- **`oxe status --json --summary`** — projeção compacta e versionada (`oxeSummarySchema: 1`) com `workspaceMode, phase, healthStatus, activeSession, nextStep, cursorCmd, reason, eventsCount, warningsCount` + um `agentSkills` compacto. Troca ~150KB do status completo por <1KB para o "glance" que um host precisa.
|
|
27
|
+
- **`agentSkills` no `status --json`** e **`agentSkillsReport(target)` no SDK** (`health.agentSkillsReport`) — status das skills `/oxe-*` por agente no workspace (`copilot-vscode`, `codex`, `copilot-cli`): `detected/skillsInstalled/skillsPath/status/issues`. Permite a um host detectar skills ausentes e oferecer instalação **antes** de lançar o agente (resolve o "Failed to load N skills").
|
|
28
|
+
- **SDK**: `health.buildStatusSummary(report)` e `health.agentSkillsReport(target)` exportados + tipados em `lib/sdk/index.d.ts`.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
7
32
|
## [1.12.0] — 2026-05-12
|
|
8
33
|
|
|
9
34
|
### Agent Mode, Swarm Mode, Memory Kernel & Learning Kernel
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://www.npmjs.com/package/oxe-cc)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
|
|
10
|
-
**Versão:** `1.
|
|
10
|
+
**Versão:** `1.14.0` · [package.json](package.json)
|
|
11
11
|
|
|
12
12
|
**Framework OXE — Orchestrated eXperience Engineering**
|
|
13
13
|
|
|
@@ -2730,6 +2730,79 @@ function buildHealthReport(target) {
|
|
|
2730
2730
|
};
|
|
2731
2731
|
}
|
|
2732
2732
|
|
|
2733
|
+
/**
|
|
2734
|
+
* Compact, stable projection of `buildHealthReport` for host integrations
|
|
2735
|
+
* (IDEs, OXESpace). Versioned independently via `oxeSummarySchema` so a host
|
|
2736
|
+
* can depend on a small, cheap payload instead of parsing the full ~100KB
|
|
2737
|
+
* status. Pure — pass in a report from `buildHealthReport`.
|
|
2738
|
+
* @param {ReturnType<typeof buildHealthReport>} report
|
|
2739
|
+
*/
|
|
2740
|
+
function buildStatusSummary(report) {
|
|
2741
|
+
const next = (report && report.next) || {};
|
|
2742
|
+
const gaps = Array.isArray(report && report.criticalExecutionGaps) ? report.criticalExecutionGaps.length : 0;
|
|
2743
|
+
const planWarn = report && report.planSelfEvaluation && Array.isArray(report.planSelfEvaluation.warnings)
|
|
2744
|
+
? report.planSelfEvaluation.warnings.length
|
|
2745
|
+
: 0;
|
|
2746
|
+
return {
|
|
2747
|
+
oxeSummarySchema: 1,
|
|
2748
|
+
workspaceMode: (report && report.workspaceMode) || 'oxe_project',
|
|
2749
|
+
phase: (report && report.phase) || null,
|
|
2750
|
+
healthStatus: (report && report.healthStatus) || null,
|
|
2751
|
+
activeSession: (report && report.activeSession) || null,
|
|
2752
|
+
nextStep: next.step || null,
|
|
2753
|
+
cursorCmd: next.cursorCmd || null,
|
|
2754
|
+
reason: next.reason || null,
|
|
2755
|
+
eventsCount: report && report.eventsSummary && typeof report.eventsSummary.total === 'number' ? report.eventsSummary.total : 0,
|
|
2756
|
+
warningsCount: gaps + planWarn,
|
|
2757
|
+
};
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
/**
|
|
2761
|
+
* Per-agent OXE skills/integration status for a workspace. Lets a host detect
|
|
2762
|
+
* when an agent lacks the `/oxe-*` skills BEFORE launching it — directly
|
|
2763
|
+
* addresses the "Failed to load N skills" failure seen in agent CLIs. Reuses
|
|
2764
|
+
* the tested copilot/codex integration reports plus a filesystem check of the
|
|
2765
|
+
* Copilot CLI skills home (~/.copilot/skills).
|
|
2766
|
+
* @param {string} target
|
|
2767
|
+
*/
|
|
2768
|
+
function agentSkillsReport(target) {
|
|
2769
|
+
const agents = [];
|
|
2770
|
+
|
|
2771
|
+
const cp = copilotIntegrationReport(target);
|
|
2772
|
+
agents.push({
|
|
2773
|
+
agent: 'copilot-vscode',
|
|
2774
|
+
detected: cp.detected,
|
|
2775
|
+
skillsInstalled: cp.promptSource === 'workspace',
|
|
2776
|
+
skillsPath: cp.workspace.promptsDir,
|
|
2777
|
+
status: cp.status,
|
|
2778
|
+
issues: cp.warnings || [],
|
|
2779
|
+
});
|
|
2780
|
+
|
|
2781
|
+
const cx = codexIntegrationReport(target);
|
|
2782
|
+
agents.push({
|
|
2783
|
+
agent: 'codex',
|
|
2784
|
+
detected: cx.detected,
|
|
2785
|
+
skillsInstalled: Boolean(cx.skillsReady),
|
|
2786
|
+
skillsPath: cx.skillsRoot,
|
|
2787
|
+
status: cx.status,
|
|
2788
|
+
issues: cx.warnings || [],
|
|
2789
|
+
});
|
|
2790
|
+
|
|
2791
|
+
const cliSkillsRoot = path.join(copilotLegacyHome(), 'skills');
|
|
2792
|
+
const cliSkillDirs = listOxeSkillDirs(cliSkillsRoot);
|
|
2793
|
+
const cliInstalled = cliSkillDirs.length > 0;
|
|
2794
|
+
agents.push({
|
|
2795
|
+
agent: 'copilot-cli',
|
|
2796
|
+
detected: cliInstalled,
|
|
2797
|
+
skillsInstalled: cliInstalled,
|
|
2798
|
+
skillsPath: cliSkillsRoot,
|
|
2799
|
+
status: cliInstalled ? 'healthy' : 'not_installed',
|
|
2800
|
+
issues: cliInstalled ? [] : ['Skills OXE ausentes em ~/.copilot/skills — rode `oxe install --copilot-cli` e depois `/skills reload`'],
|
|
2801
|
+
});
|
|
2802
|
+
|
|
2803
|
+
return agents;
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2733
2806
|
module.exports = {
|
|
2734
2807
|
ALLOWED_CONFIG_KEYS,
|
|
2735
2808
|
EXECUTION_PROFILES,
|
|
@@ -2775,6 +2848,8 @@ module.exports = {
|
|
|
2775
2848
|
shouldSuppressExecutionWorkspaceGates,
|
|
2776
2849
|
suggestNextStep,
|
|
2777
2850
|
buildHealthReport,
|
|
2851
|
+
buildStatusSummary,
|
|
2852
|
+
agentSkillsReport,
|
|
2778
2853
|
buildExecutionRationality: rationality.buildExecutionRationality,
|
|
2779
2854
|
oxePaths,
|
|
2780
2855
|
scopedOxePaths,
|
package/bin/oxe-cc.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* OXE — CLI em pt-BR: instala workflows no projeto, bootstrap `.oxe/`, doctor, uninstall, update.
|
|
4
4
|
* Uso: npx oxe-cc, doctor, status, init-oxe, uninstall, update (ver --help).
|
|
@@ -203,26 +203,26 @@ function buildInstallSummary(opts, fullLayout) {
|
|
|
203
203
|
cmd: 'npx oxe-cc status',
|
|
204
204
|
});
|
|
205
205
|
|
|
206
|
-
const agentHint = [];
|
|
207
|
-
if (opts.cursor) agentHint.push('Cursor');
|
|
208
|
-
if (opts.copilot) agentHint.push('Copilot no VS Code');
|
|
209
|
-
if (opts.copilotCli || opts.allAgents || anyGranularAgent(opts)) agentHint.push('CLIs / multi-agente');
|
|
210
|
-
const onlyCodexAgent =
|
|
211
|
-
opts.agentCodex &&
|
|
212
|
-
!opts.allAgents &&
|
|
213
|
-
!opts.copilotCli &&
|
|
214
|
-
!opts.cursor &&
|
|
215
|
-
!opts.copilot &&
|
|
216
|
-
!opts.agentOpenCode &&
|
|
217
|
-
!opts.agentGemini &&
|
|
218
|
-
!opts.agentWindsurf &&
|
|
219
|
-
!opts.agentAntigravity;
|
|
220
|
-
if (agentHint.length) {
|
|
221
|
-
nextSteps.push({
|
|
222
|
-
desc: `Entrar no fluxo OXE no agente (${agentHint.join(', ')}) e deixar o router indicar o primeiro passo:`,
|
|
223
|
-
cmd: onlyCodexAgent ? '/prompts:oxe' : '/oxe',
|
|
224
|
-
});
|
|
225
|
-
} else if (opts.oxeOnly) {
|
|
206
|
+
const agentHint = [];
|
|
207
|
+
if (opts.cursor) agentHint.push('Cursor');
|
|
208
|
+
if (opts.copilot) agentHint.push('Copilot no VS Code');
|
|
209
|
+
if (opts.copilotCli || opts.allAgents || anyGranularAgent(opts)) agentHint.push('CLIs / multi-agente');
|
|
210
|
+
const onlyCodexAgent =
|
|
211
|
+
opts.agentCodex &&
|
|
212
|
+
!opts.allAgents &&
|
|
213
|
+
!opts.copilotCli &&
|
|
214
|
+
!opts.cursor &&
|
|
215
|
+
!opts.copilot &&
|
|
216
|
+
!opts.agentOpenCode &&
|
|
217
|
+
!opts.agentGemini &&
|
|
218
|
+
!opts.agentWindsurf &&
|
|
219
|
+
!opts.agentAntigravity;
|
|
220
|
+
if (agentHint.length) {
|
|
221
|
+
nextSteps.push({
|
|
222
|
+
desc: `Entrar no fluxo OXE no agente (${agentHint.join(', ')}) e deixar o router indicar o primeiro passo:`,
|
|
223
|
+
cmd: onlyCodexAgent ? '/prompts:oxe' : '/oxe',
|
|
224
|
+
});
|
|
225
|
+
} else if (opts.oxeOnly) {
|
|
226
226
|
nextSteps.push({
|
|
227
227
|
desc: 'Para ativar integrações IDE/CLI neste repo, instale de novo sem --oxe-only:',
|
|
228
228
|
cmd: 'npx oxe-cc@latest',
|
|
@@ -234,19 +234,19 @@ function buildInstallSummary(opts, fullLayout) {
|
|
|
234
234
|
});
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
if (opts.copilotCli || opts.allAgents) {
|
|
238
|
-
nextSteps.push({
|
|
239
|
-
desc: 'No Copilot CLI: após instalar, rode /skills reload (ou reinicie o copilot) e use /oxe ou /oxe-scan:',
|
|
240
|
-
cmd: '/skills list',
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
if (opts.agentCodex || opts.allAgents) {
|
|
244
|
-
nextSteps.push({
|
|
245
|
-
desc: 'No Codex Desktop: reinicie o app ou abra uma nova conversa para recarregar prompts/skills; use a superfície de prompts do Codex:',
|
|
246
|
-
cmd: '/prompts:oxe ou /prompts:oxe-spec',
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
if (opts.allAgents || opts.agentGemini) {
|
|
237
|
+
if (opts.copilotCli || opts.allAgents) {
|
|
238
|
+
nextSteps.push({
|
|
239
|
+
desc: 'No Copilot CLI: após instalar, rode /skills reload (ou reinicie o copilot) e use /oxe ou /oxe-scan:',
|
|
240
|
+
cmd: '/skills list',
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
if (opts.agentCodex || opts.allAgents) {
|
|
244
|
+
nextSteps.push({
|
|
245
|
+
desc: 'No Codex Desktop: reinicie o app ou abra uma nova conversa para recarregar prompts/skills; use a superfície de prompts do Codex:',
|
|
246
|
+
cmd: '/prompts:oxe ou /prompts:oxe-spec',
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
if (opts.allAgents || opts.agentGemini) {
|
|
250
250
|
nextSteps.push({
|
|
251
251
|
desc: 'No Gemini CLI: recarregar comandos personalizados (/oxe, /oxe:scan, …):',
|
|
252
252
|
cmd: '/commands reload',
|
|
@@ -390,6 +390,8 @@ function parseInstallArgs(argv) {
|
|
|
390
390
|
statusHints: false,
|
|
391
391
|
/** Visão extendida CLI-first: coverage matrix + readiness gate no terminal. */
|
|
392
392
|
statusFull: false,
|
|
393
|
+
/** Saída JSON compacta/versionada para hosts (status --json --summary). */
|
|
394
|
+
statusSummary: false,
|
|
393
395
|
restPositional: [],
|
|
394
396
|
};
|
|
395
397
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -435,6 +437,7 @@ function parseInstallArgs(argv) {
|
|
|
435
437
|
} else if (a === '--json') out.jsonOutput = true;
|
|
436
438
|
else if (a === '--hints') out.statusHints = true;
|
|
437
439
|
else if (a === '--full') out.statusFull = true;
|
|
440
|
+
else if (a === '--summary') out.statusSummary = true;
|
|
438
441
|
else if (a === '--release') out.releaseDoctor = true;
|
|
439
442
|
else if (a === '--write-manifest') out.writeManifest = true;
|
|
440
443
|
else if (!a.startsWith('-')) out.restPositional.push(a);
|
|
@@ -1892,6 +1895,18 @@ function runStatus(target, opts = {}) {
|
|
|
1892
1895
|
const routineHints = collectOxeRoutineHints(target, report, config);
|
|
1893
1896
|
const next = report.next;
|
|
1894
1897
|
|
|
1898
|
+
// Lightweight, versioned summary for host integrations (IDEs, OXESpace) —
|
|
1899
|
+
// a small stable payload instead of the full ~100KB status. Includes a
|
|
1900
|
+
// compact per-agent skills view so a host can decide whether to offer
|
|
1901
|
+
// "install OXE skills" before launching an agent.
|
|
1902
|
+
if (opts.json && opts.summary) {
|
|
1903
|
+
const summary = oxeHealth.buildStatusSummary(report);
|
|
1904
|
+
summary.projectRoot = path.resolve(target);
|
|
1905
|
+
summary.agentSkills = oxeHealth.agentSkillsReport(target).map((a) => ({ agent: a.agent, skillsInstalled: a.skillsInstalled }));
|
|
1906
|
+
console.log(JSON.stringify(summary));
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1895
1910
|
if (opts.json) {
|
|
1896
1911
|
/** @type {Record<string, unknown>} */
|
|
1897
1912
|
const payload = {
|
|
@@ -1947,6 +1962,7 @@ function runStatus(target, opts = {}) {
|
|
|
1947
1962
|
azure: report.azure,
|
|
1948
1963
|
copilot: report.copilot,
|
|
1949
1964
|
codex: report.codex,
|
|
1965
|
+
agentSkills: oxeHealth.agentSkillsReport(target),
|
|
1950
1966
|
contextPacks: report.contextPacks,
|
|
1951
1967
|
contextQuality: report.contextQuality,
|
|
1952
1968
|
semanticsDrift: report.semanticsDrift,
|
|
@@ -2553,6 +2569,7 @@ ${cyan}oxe-cc${reset} — instala workflows OXE (núcleo .oxe/ + integrações:
|
|
|
2553
2569
|
npx oxe-cc init-oxe [opções] [pasta-do-projeto]
|
|
2554
2570
|
npx oxe-cc context <build|inspect> [opções] [pasta-do-projeto]
|
|
2555
2571
|
npx oxe-cc dashboard [opções] [pasta-do-projeto]
|
|
2572
|
+
npx oxe-cc events [--tail N] [--since <evt_id>] [--json] [opções] [pasta-do-projeto]
|
|
2556
2573
|
npx oxe-cc runtime <status|start|pause|resume|replay|compile|verify|project|ci|promote|recover|gates|agents|execute> [opções] [pasta-do-projeto]
|
|
2557
2574
|
npx oxe-cc azure <status|doctor|auth|sync|find|servicebus|eventgrid|sql|operations> [opções] [pasta-do-projeto]
|
|
2558
2575
|
npx oxe-cc capabilities <list|install|remove|update> [opções] [id]
|
|
@@ -2587,12 +2604,21 @@ ${green}update${reset} (executa npx oxe-cc@latest --force na pasta do projeto)
|
|
|
2587
2604
|
${dim}CI / sem rede:${reset} OXE_UPDATE_SKIP_REGISTRY=1 desativa consultas (--check sai 2; --if-newer sai 2 sem npx)
|
|
2588
2605
|
|
|
2589
2606
|
${green}dashboard${reset} (interface web local para revisão e aprovação do plano)
|
|
2590
|
-
--port <número> porta local (padrão: 4173)
|
|
2607
|
+
--port <número> porta local (padrão: 4173; use 0 para porta efêmera)
|
|
2591
2608
|
--no-open não abre o browser automaticamente
|
|
2609
|
+
--json emite {url,port,...} (oxeDashboardSchema:1) e segue servindo — para embutir num host
|
|
2610
|
+
--read-only UI visual sem persistência (embed seguro)
|
|
2592
2611
|
--session <sessions/sNNN-slug> força visualização de uma sessão específica
|
|
2593
2612
|
--dump-context imprime JSON consolidado e sai
|
|
2594
2613
|
--dir <pasta> raiz do projeto (padrão: diretório atual)
|
|
2595
2614
|
|
|
2615
|
+
${green}events${reset} (projeção read-only do log .oxe/OXE-EVENTS.ndjson — para hosts)
|
|
2616
|
+
--tail <N> só os últimos N eventos (padrão: 50)
|
|
2617
|
+
--since <evt_id> só eventos posteriores a um event_id conhecido (leitura incremental)
|
|
2618
|
+
--json saída estruturada (oxeEventsSchema:1) com summary + events
|
|
2619
|
+
--session <sessions/sNNN-slug> força sessão específica (padrão: sessão ativa do STATE.md)
|
|
2620
|
+
--dir <pasta> raiz do projeto (padrão: diretório atual)
|
|
2621
|
+
|
|
2596
2622
|
${green}context${reset} (Context Engine V2: seleção, compressão e inspeção determinística)
|
|
2597
2623
|
build [--workflow <slug>] gera pack(s) em .oxe/context/packs/
|
|
2598
2624
|
inspect [--workflow <slug>] lê um pack existente ou resolve sob demanda
|
|
@@ -3924,7 +3950,7 @@ function parseCapabilitiesArgs(argv) {
|
|
|
3924
3950
|
}
|
|
3925
3951
|
|
|
3926
3952
|
/**
|
|
3927
|
-
* @typedef {{ help: boolean, dir: string, port: number, noOpen: boolean, readOnly: boolean, dumpContext: boolean, activeSession: string|null, parseError: boolean, unknownFlag: string }} DashboardOpts
|
|
3953
|
+
* @typedef {{ help: boolean, dir: string, port: number, noOpen: boolean, readOnly: boolean, dumpContext: boolean, json: boolean, activeSession: string|null, parseError: boolean, unknownFlag: string }} DashboardOpts
|
|
3928
3954
|
*/
|
|
3929
3955
|
|
|
3930
3956
|
/**
|
|
@@ -3985,6 +4011,7 @@ function parseDashboardArgs(argv) {
|
|
|
3985
4011
|
noOpen: false,
|
|
3986
4012
|
readOnly: false,
|
|
3987
4013
|
dumpContext: false,
|
|
4014
|
+
json: false,
|
|
3988
4015
|
activeSession: null,
|
|
3989
4016
|
parseError: false,
|
|
3990
4017
|
unknownFlag: '',
|
|
@@ -3993,10 +4020,15 @@ function parseDashboardArgs(argv) {
|
|
|
3993
4020
|
const a = argv[i];
|
|
3994
4021
|
if (a === '-h' || a === '--help') out.help = true;
|
|
3995
4022
|
else if (a === '--dir' && argv[i + 1]) out.dir = path.resolve(argv[++i]);
|
|
3996
|
-
else if (a === '--port' && argv[i + 1]
|
|
3997
|
-
|
|
4023
|
+
else if (a === '--port' && argv[i + 1] !== undefined) {
|
|
4024
|
+
// Allow `--port 0` (OS picks an ephemeral port — used by hosts that embed
|
|
4025
|
+
// the dashboard and read the real port back from `--json`).
|
|
4026
|
+
const n = Number(argv[++i]);
|
|
4027
|
+
out.port = Number.isFinite(n) && n >= 0 ? n : 4173;
|
|
4028
|
+
} else if (a === '--no-open') out.noOpen = true;
|
|
3998
4029
|
else if (a === '--read-only') out.readOnly = true;
|
|
3999
4030
|
else if (a === '--dump-context') out.dumpContext = true;
|
|
4031
|
+
else if (a === '--json') out.json = true;
|
|
4000
4032
|
else if (a === '--session' && argv[i + 1]) out.activeSession = String(argv[++i]).replace(/\\/g, '/');
|
|
4001
4033
|
else if (!a.startsWith('-') && i === 0) out.dir = path.resolve(a);
|
|
4002
4034
|
else {
|
|
@@ -4357,7 +4389,7 @@ async function runDashboard(opts) {
|
|
|
4357
4389
|
return;
|
|
4358
4390
|
}
|
|
4359
4391
|
const c = useAnsiColors();
|
|
4360
|
-
printSection('OXE ▸ dashboard');
|
|
4392
|
+
if (!opts.json) printSection('OXE ▸ dashboard');
|
|
4361
4393
|
const server = oxeDashboard.createDashboardServer(target, { activeSession: opts.activeSession });
|
|
4362
4394
|
await new Promise((resolve, reject) => {
|
|
4363
4395
|
server.once('error', reject);
|
|
@@ -4366,6 +4398,24 @@ async function runDashboard(opts) {
|
|
|
4366
4398
|
const address = server.address();
|
|
4367
4399
|
const port = address && typeof address === 'object' ? address.port : opts.port;
|
|
4368
4400
|
const url = `http://127.0.0.1:${port}/`;
|
|
4401
|
+
// Host-integration mode: emit one stable, versioned line with the live URL/port
|
|
4402
|
+
// as soon as the server is listening, then keep serving until killed. Lets a
|
|
4403
|
+
// host (OXESpace) embed the dashboard in a webview without scraping the
|
|
4404
|
+
// human-readable banner. Pair with `--no-open --port 0` for an ephemeral port.
|
|
4405
|
+
if (opts.json) {
|
|
4406
|
+
console.log(JSON.stringify({
|
|
4407
|
+
oxeDashboardSchema: 1,
|
|
4408
|
+
projectRoot: path.resolve(target),
|
|
4409
|
+
url,
|
|
4410
|
+
port,
|
|
4411
|
+
readOnly: Boolean(opts.readOnly),
|
|
4412
|
+
}));
|
|
4413
|
+
if (!opts.noOpen) {
|
|
4414
|
+
try { openUrlInBrowser(url); } catch { /* host owns presentation */ }
|
|
4415
|
+
}
|
|
4416
|
+
await new Promise(() => {});
|
|
4417
|
+
return;
|
|
4418
|
+
}
|
|
4369
4419
|
console.log(` ${c ? green : ''}Projeto:${c ? reset : ''} ${c ? cyan : ''}${target}${c ? reset : ''}`);
|
|
4370
4420
|
console.log(` ${c ? green : ''}URL:${c ? reset : ''} ${c ? cyan : ''}${url}${c ? reset : ''}`);
|
|
4371
4421
|
if (opts.readOnly) {
|
|
@@ -4383,6 +4433,99 @@ async function runDashboard(opts) {
|
|
|
4383
4433
|
await new Promise(() => {});
|
|
4384
4434
|
}
|
|
4385
4435
|
|
|
4436
|
+
/**
|
|
4437
|
+
* @typedef {{ help: boolean, dir: string, tail: number, since: string, activeSession: string|null, json: boolean, parseError: boolean, unknownFlag: string }} EventsOpts
|
|
4438
|
+
*/
|
|
4439
|
+
|
|
4440
|
+
/**
|
|
4441
|
+
* @param {string[]} argv
|
|
4442
|
+
* @returns {EventsOpts}
|
|
4443
|
+
*/
|
|
4444
|
+
function parseEventsArgs(argv) {
|
|
4445
|
+
/** @type {EventsOpts} */
|
|
4446
|
+
const out = {
|
|
4447
|
+
help: false,
|
|
4448
|
+
dir: process.cwd(),
|
|
4449
|
+
tail: 50,
|
|
4450
|
+
since: '',
|
|
4451
|
+
activeSession: null,
|
|
4452
|
+
json: false,
|
|
4453
|
+
parseError: false,
|
|
4454
|
+
unknownFlag: '',
|
|
4455
|
+
};
|
|
4456
|
+
for (let i = 0; i < argv.length; i++) {
|
|
4457
|
+
const a = argv[i];
|
|
4458
|
+
if (a === '-h' || a === '--help') out.help = true;
|
|
4459
|
+
else if (a === '--dir' && argv[i + 1]) out.dir = path.resolve(argv[++i]);
|
|
4460
|
+
else if (a === '--tail') {
|
|
4461
|
+
// `--tail` alone keeps the default; `--tail N` overrides the count.
|
|
4462
|
+
const next = argv[i + 1];
|
|
4463
|
+
if (next && /^\d+$/.test(next)) out.tail = Number(argv[++i]);
|
|
4464
|
+
} else if (a === '--since' && argv[i + 1]) out.since = String(argv[++i]);
|
|
4465
|
+
else if (a === '--json') out.json = true;
|
|
4466
|
+
else if (a === '--session' && argv[i + 1]) out.activeSession = String(argv[++i]).replace(/\\/g, '/');
|
|
4467
|
+
else if (!a.startsWith('-') && i === 0) out.dir = path.resolve(a);
|
|
4468
|
+
else {
|
|
4469
|
+
out.parseError = true;
|
|
4470
|
+
out.unknownFlag = a;
|
|
4471
|
+
break;
|
|
4472
|
+
}
|
|
4473
|
+
}
|
|
4474
|
+
return out;
|
|
4475
|
+
}
|
|
4476
|
+
|
|
4477
|
+
/**
|
|
4478
|
+
* Read-only projection of the append-only event log (.oxe/OXE-EVENTS.ndjson)
|
|
4479
|
+
* for host integrations. A host typically watches that file directly and then
|
|
4480
|
+
* calls `status --json --summary`; this command is the documented way to read
|
|
4481
|
+
* the tail of the log (optionally since a known event_id) without parsing the
|
|
4482
|
+
* whole file. Reuses the same SDK helpers the runtime uses.
|
|
4483
|
+
* @param {EventsOpts} opts
|
|
4484
|
+
*/
|
|
4485
|
+
function runEvents(opts) {
|
|
4486
|
+
const target = opts.dir;
|
|
4487
|
+
if (!fs.existsSync(target)) {
|
|
4488
|
+
if (opts.json) console.log(JSON.stringify({ oxeEventsSchema: 1, projectRoot: path.resolve(target), error: 'dir-not-found', events: [] }));
|
|
4489
|
+
else console.error(`${yellow}Diretório não encontrado: ${target}${reset}`);
|
|
4490
|
+
process.exit(1);
|
|
4491
|
+
}
|
|
4492
|
+
const stateText = fs.existsSync(oxeHealth.oxePaths(target).state)
|
|
4493
|
+
? fs.readFileSync(oxeHealth.oxePaths(target).state, 'utf8')
|
|
4494
|
+
: '';
|
|
4495
|
+
const activeSession = opts.activeSession || oxeHealth.parseActiveSession(stateText) || null;
|
|
4496
|
+
let events = oxeOperational.readEvents(target, activeSession);
|
|
4497
|
+
if (opts.since) {
|
|
4498
|
+
const idx = events.findIndex((e) => e && e.event_id === opts.since);
|
|
4499
|
+
if (idx >= 0) events = events.slice(idx + 1);
|
|
4500
|
+
}
|
|
4501
|
+
const tail = opts.tail > 0 ? events.slice(-opts.tail) : events;
|
|
4502
|
+
|
|
4503
|
+
if (opts.json) {
|
|
4504
|
+
console.log(JSON.stringify({
|
|
4505
|
+
oxeEventsSchema: 1,
|
|
4506
|
+
projectRoot: path.resolve(target),
|
|
4507
|
+
activeSession,
|
|
4508
|
+
summary: oxeOperational.summarizeEvents(events),
|
|
4509
|
+
events: tail,
|
|
4510
|
+
}));
|
|
4511
|
+
return;
|
|
4512
|
+
}
|
|
4513
|
+
|
|
4514
|
+
const c = useAnsiColors();
|
|
4515
|
+
printSection('OXE ▸ events');
|
|
4516
|
+
const summary = oxeOperational.summarizeEvents(events);
|
|
4517
|
+
console.log(` ${c ? green : ''}Total:${c ? reset : ''} ${summary.total}${activeSession ? ` ${c ? dim : ''}(sessão: ${activeSession})${c ? reset : ''}` : ''}`);
|
|
4518
|
+
if (!tail.length) {
|
|
4519
|
+
console.log(` ${c ? dim : ''}Sem eventos.${c ? reset : ''}\n`);
|
|
4520
|
+
return;
|
|
4521
|
+
}
|
|
4522
|
+
for (const e of tail) {
|
|
4523
|
+
const ts = e.timestamp ? String(e.timestamp).replace('T', ' ').replace(/\..*$/, '') : '';
|
|
4524
|
+
console.log(` ${c ? dim : ''}${ts}${c ? reset : ''} ${c ? cyan : ''}${e.type}${c ? reset : ''}${e.run_id ? ` ${c ? dim : ''}run=${e.run_id}${c ? reset : ''}` : ''}`);
|
|
4525
|
+
}
|
|
4526
|
+
console.log('');
|
|
4527
|
+
}
|
|
4528
|
+
|
|
4386
4529
|
/**
|
|
4387
4530
|
* @param {RuntimeOpts} opts
|
|
4388
4531
|
*/
|
|
@@ -5473,6 +5616,7 @@ async function main() {
|
|
|
5473
5616
|
argv[0] === 'init-oxe' ||
|
|
5474
5617
|
argv[0] === 'context' ||
|
|
5475
5618
|
argv[0] === 'dashboard' ||
|
|
5619
|
+
argv[0] === 'events' ||
|
|
5476
5620
|
argv[0] === 'runtime' ||
|
|
5477
5621
|
argv[0] === 'azure' ||
|
|
5478
5622
|
argv[0] === 'uninstall' ||
|
|
@@ -5582,11 +5726,29 @@ async function main() {
|
|
|
5582
5726
|
usage();
|
|
5583
5727
|
process.exit(1);
|
|
5584
5728
|
}
|
|
5585
|
-
printBanner();
|
|
5729
|
+
if (!d.json) printBanner();
|
|
5586
5730
|
await runDashboard(d);
|
|
5587
5731
|
return;
|
|
5588
5732
|
}
|
|
5589
5733
|
|
|
5734
|
+
if (command === 'events') {
|
|
5735
|
+
const e = parseEventsArgs(argv);
|
|
5736
|
+
if (e.help) {
|
|
5737
|
+
printBanner();
|
|
5738
|
+
usage();
|
|
5739
|
+
process.exit(0);
|
|
5740
|
+
}
|
|
5741
|
+
if (e.parseError) {
|
|
5742
|
+
printBanner();
|
|
5743
|
+
console.error(`${red}Opção desconhecida:${reset} ${e.unknownFlag}`);
|
|
5744
|
+
usage();
|
|
5745
|
+
process.exit(1);
|
|
5746
|
+
}
|
|
5747
|
+
if (!e.json) printBanner();
|
|
5748
|
+
runEvents(e);
|
|
5749
|
+
return;
|
|
5750
|
+
}
|
|
5751
|
+
|
|
5590
5752
|
if (command === 'context') {
|
|
5591
5753
|
const contextOpts = parseContextArgs(argv);
|
|
5592
5754
|
if (contextOpts.help) {
|
|
@@ -5768,7 +5930,7 @@ async function main() {
|
|
|
5768
5930
|
if (opts.statusFull) {
|
|
5769
5931
|
runStatusFull(target);
|
|
5770
5932
|
} else {
|
|
5771
|
-
runStatus(target, { json: opts.jsonOutput, hints: opts.statusHints });
|
|
5933
|
+
runStatus(target, { json: opts.jsonOutput, hints: opts.statusHints, summary: opts.statusSummary });
|
|
5772
5934
|
}
|
|
5773
5935
|
return;
|
|
5774
5936
|
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Integração com hosts (IDEs, OXESpace, …)
|
|
2
|
+
|
|
3
|
+
Este documento descreve o **contrato estável** que o `oxe-cc` expõe para um *host* — um app que embute o oxe-cc e mostra o estado de um projeto OXE (ex.: o [OXESpace](https://github.com/propagno) embute o painel OXE).
|
|
4
|
+
|
|
5
|
+
O `oxe-cc` e o host têm **releases independentes**. Para que evoluam sem se quebrar:
|
|
6
|
+
|
|
7
|
+
- Toda saída de máquina é **versionada** com um campo `oxe*Schema` próprio (independente da versão do pacote).
|
|
8
|
+
- Os campos são **aditivos**: campos novos podem aparecer; campos existentes não mudam de tipo nem somem dentro da mesma `*Schema`.
|
|
9
|
+
- O host deve **degradar graciosamente**: detectar a versão / o schema e usar o caminho antigo quando um recurso novo não existir.
|
|
10
|
+
|
|
11
|
+
Detecte a versão com `oxe --version` (imprime `oxe-cc vX.Y.Z`). Tabela de disponibilidade:
|
|
12
|
+
|
|
13
|
+
| Recurso | Desde | Schema |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| `status --json` (completo) | 1.0 | `oxeStatusSchema: 5` |
|
|
16
|
+
| `status --json --summary` | 1.13.0 | `oxeSummarySchema: 1` |
|
|
17
|
+
| `agentSkills[]` no `status --json` + `agentSkills` no summary | 1.13.0 | — |
|
|
18
|
+
| `events --tail --json` | 1.14.0 | `oxeEventsSchema: 1` |
|
|
19
|
+
| `dashboard --json` + `--port 0` efêmero | 1.14.0 | `oxeDashboardSchema: 1` |
|
|
20
|
+
|
|
21
|
+
> **Estável:** `status --json --summary`, `agentSkills`, `events --json`, `dashboard --json`.
|
|
22
|
+
> **Experimental (pode mudar):** o conteúdo de `payload` dentro de cada evento; o HTML servido pelo dashboard; o corpo de `status --json` completo (`oxeStatusSchema`) além dos campos listados abaixo.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 1. Glance barato — `status --json --summary`
|
|
27
|
+
|
|
28
|
+
Em vez do `status --json` completo (~100KB), use a projeção compacta no *hot path* (atualizações frequentes):
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
oxe status --json --summary --dir <projeto>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```jsonc
|
|
35
|
+
{
|
|
36
|
+
"oxeSummarySchema": 1,
|
|
37
|
+
"projectRoot": "/abs/path",
|
|
38
|
+
"workspaceMode": "oxe_project",
|
|
39
|
+
"phase": "execute",
|
|
40
|
+
"healthStatus": "warning",
|
|
41
|
+
"activeSession": "sessions/s003-foo",
|
|
42
|
+
"nextStep": "execute",
|
|
43
|
+
"cursorCmd": "/oxe-execute",
|
|
44
|
+
"reason": "…",
|
|
45
|
+
"eventsCount": 42,
|
|
46
|
+
"warningsCount": 7,
|
|
47
|
+
"agentSkills": [ { "agent": "copilot-cli", "skillsInstalled": false } ]
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
O `status --json` completo (`oxeStatusSchema: 5`) continua disponível para a vista detalhada (diagnósticos, `criticalExecutionGaps`, `planSelfEvaluation`, artefatos, `agentSkills[]` detalhado).
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 2. Skills por-agente — `agentSkills`
|
|
56
|
+
|
|
57
|
+
O summary traz um `agentSkills` compacto (`{ agent, skillsInstalled }`). O `status --json` completo traz a forma detalhada:
|
|
58
|
+
|
|
59
|
+
```jsonc
|
|
60
|
+
{
|
|
61
|
+
"agent": "copilot-cli", // copilot-vscode | codex | copilot-cli | cursor | …
|
|
62
|
+
"detected": true, // o agente está configurado neste ambiente
|
|
63
|
+
"skillsInstalled": false, // as skills /oxe-* estão instaladas neste workspace
|
|
64
|
+
"skillsPath": "/abs/.../skills",
|
|
65
|
+
"status": "no_skills",
|
|
66
|
+
"issues": ["…"] // por que falhou (caminho ausente, frontmatter, versão)
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Via SDK, sem shell: `require('oxe-cc').health.agentSkillsReport(target)`.
|
|
71
|
+
|
|
72
|
+
**Uso típico do host:** se algum agente relevante tem `skillsInstalled:false`, ofereça instalar as skills daquele agente **antes** de lançá-lo (resolve o "Failed to load N skills"). Hoje o host dispara a instalação via os comandos `install` do oxe-cc (no terminal); uma saída `install --json` silenciosa está planejada (ver §5).
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 3. Reatividade — observar eventos
|
|
77
|
+
|
|
78
|
+
O `oxe-cc` mantém um log **append-only** em:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
.oxe/OXE-EVENTS.ndjson # projeto sem sessão ativa
|
|
82
|
+
.oxe/<session>/execution/OXE-EVENTS.ndjson # quando há sessão ativa (ex.: sessions/s003-foo)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Cada linha é um JSON (NDJSON). Campos estáveis por evento:
|
|
86
|
+
|
|
87
|
+
```jsonc
|
|
88
|
+
{
|
|
89
|
+
"event_id": "evt-...", // único; use como cursor em --since
|
|
90
|
+
"type": "RunStarted", // RunStarted, WorkItemReady, ToolInvoked, GateRequested, RunCompleted, …
|
|
91
|
+
"timestamp": "2026-05-30T10:00:00.000Z",
|
|
92
|
+
"run_id": "run-1" | null,
|
|
93
|
+
"session_id": "sessions/s003-foo" | null,
|
|
94
|
+
"wave_id": 1 | null,
|
|
95
|
+
"task_id": "T1" | null,
|
|
96
|
+
"payload": { /* experimental — específico do tipo */ }
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Padrão recomendado (reativo + barato):**
|
|
101
|
+
|
|
102
|
+
1. O host faz `fs.watch` do `OXE-EVENTS.ndjson` (com debounce ~400ms).
|
|
103
|
+
2. A cada mudança, re-chama `status --json --summary` para atualizar a UI.
|
|
104
|
+
3. Um poll lento de segurança (~15s) cobre o caso de o watch falhar (rede/symlink).
|
|
105
|
+
|
|
106
|
+
Para **ler os eventos** sem reparsear o arquivo inteiro, use o comando read-only:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
oxe events --tail 50 --json --dir <projeto>
|
|
110
|
+
oxe events --since evt-abc123 --json --dir <projeto> # só os novos desde um event_id
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```jsonc
|
|
114
|
+
{
|
|
115
|
+
"oxeEventsSchema": 1,
|
|
116
|
+
"projectRoot": "/abs/path",
|
|
117
|
+
"activeSession": "sessions/s003-foo" | null,
|
|
118
|
+
"summary": { "total": 3, "byType": { "RunStarted": 1, "…": 1 }, "lastEvent": { /* … */ } },
|
|
119
|
+
"events": [ /* … */ ]
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Via SDK: `oxe.operational.readEvents(root, session)` e `oxe.operational.summarizeEvents(events)`.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 4. Dashboard embutível — `dashboard --json`
|
|
128
|
+
|
|
129
|
+
O dashboard é um servidor HTTP local (serve `/` HTML + `/api/context` + endpoints de review/runtime). Para embutir num webview/iframe, suba-o em modo host:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
oxe dashboard --no-open --port 0 --json --dir <projeto>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
A **primeira linha** do stdout é, assim que o servidor está ouvindo, e o processo **continua servindo** até ser morto:
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{"oxeDashboardSchema":1,"projectRoot":"/abs/path","url":"http://127.0.0.1:52555/","port":52555,"readOnly":false}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
- `--port 0` → o SO escolhe uma porta efêmera; leia a porta real do `port`/`url`.
|
|
142
|
+
- `--read-only` → UI visual sem persistir mudanças (embed seguro).
|
|
143
|
+
- O servidor é localhost-only (`127.0.0.1`), sem autenticação — adequado a embed local.
|
|
144
|
+
|
|
145
|
+
**Ciclo de vida (host):** faça spawn do processo, leia a primeira linha JSON, carregue `url` no webview, e **mate o processo** ao fechar o workspace/app. Fallback para versões < 1.14: rode `dashboard` sem `--json` e raspe a linha `URL: …`, ou abra no navegador externo.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## 5. Roadmap de integração (ainda não estável)
|
|
150
|
+
|
|
151
|
+
- **`install … --json`** — saída idempotente `{ ok, agents:[{agent, installedPaths[]}], skipped[], errors[] }` para instalar skills sem terminal. Hoje a instalação é via os comandos `install` (no terminal) + reconferir com `agentSkills`.
|
|
152
|
+
- **`events --follow` / streaming (SSE)** — hoje a reatividade é via `fs.watch` + `events --tail`/`--since`.
|