@jmanuelcorral/openteam 0.1.16 → 0.1.18
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/README.md +35 -0
- package/dist/cli.js +60 -3
- package/dist/commands/dashboard.d.ts +9 -0
- package/dist/commands/dashboard.d.ts.map +1 -0
- package/dist/commands/dispatch.d.ts.map +1 -1
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/config/schema.d.ts +29 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +703 -4
- package/dist/plugin/commandTool.d.ts +1 -1
- package/dist/plugin/commandTool.d.ts.map +1 -1
- package/dist/web/activity.d.ts +12 -0
- package/dist/web/activity.d.ts.map +1 -0
- package/dist/web/paths.d.ts +4 -0
- package/dist/web/paths.d.ts.map +1 -0
- package/dist/web/server.d.ts +26 -0
- package/dist/web/server.d.ts.map +1 -0
- package/dist/web/snapshot.d.ts +34 -0
- package/dist/web/snapshot.d.ts.map +1 -0
- package/dist/web/start.d.ts +36 -0
- package/dist/web/start.d.ts.map +1 -0
- package/dist/web/watch.d.ts +19 -0
- package/dist/web/watch.d.ts.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -365,6 +365,7 @@ openteam baseline show
|
|
|
365
365
|
openteam baseline set openai/gpt-5-mini
|
|
366
366
|
openteam doctor --config .opencode/openteam.json
|
|
367
367
|
openteam agents
|
|
368
|
+
openteam dashboard
|
|
368
369
|
openteam report --telemetry .opencode/openteam-telemetry.jsonl
|
|
369
370
|
openteam yolo status
|
|
370
371
|
openteam yolo on
|
|
@@ -414,6 +415,40 @@ También puedes activarlo durante `openteam init` (te lo pregunta), o al vuelo c
|
|
|
414
415
|
|
|
415
416
|
Segundo modo de operación: un **bucle externo** que invoca `opencode run` contra un backlog persistente hasta vaciarlo, con contexto fresco por iteración y topes de seguridad (presupuesto, máx. iteraciones, sin-progreso). Complementa al orquestador interactivo, no lo sustituye. Consulta el diseño y los ejemplos en **[docs/loop.md](docs/loop.md)**.
|
|
416
417
|
|
|
418
|
+
### Dashboard web (mientras la sesión está viva)
|
|
419
|
+
|
|
420
|
+
openteam puede exponer un **panel web local** que se sirve **dentro del proceso de opencode** (hook `server()`), mientras la sesión está abierta. Muestra en vivo: ruteo reciente (coste/ahorro y modelo elegido, **solo hashes de prompt, nunca el texto**), progreso del backlog del loop, el equipo de agentes (LLM local/frontier de cada uno), últimos commits y actividad. Se actualiza por **SSE** (con *polling* de respaldo).
|
|
421
|
+
|
|
422
|
+
`openteam init` **te pregunta si quieres activarlo** (puerto 4599 por defecto). También puedes activarlo a mano en `.opencode/openteam.json`:
|
|
423
|
+
|
|
424
|
+
```json
|
|
425
|
+
{
|
|
426
|
+
"dashboard": {
|
|
427
|
+
"enabled": true,
|
|
428
|
+
"host": "127.0.0.1",
|
|
429
|
+
"port": 4599,
|
|
430
|
+
"autoPortFallback": true,
|
|
431
|
+
"refreshMs": 2000,
|
|
432
|
+
"recentRoutes": 50,
|
|
433
|
+
"openBrowser": false
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Recarga opencode. Al arrancar, el plugin registra la URL en el log:
|
|
439
|
+
|
|
440
|
+
```text
|
|
441
|
+
[openteam] dashboard en http://127.0.0.1:4599
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
Ábrela en el navegador. Para consultar el estado (URL, puerto, si está activo) desde la CLI o desde dentro de opencode:
|
|
445
|
+
|
|
446
|
+
```powershell
|
|
447
|
+
openteam dashboard
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
> **Seguridad**: el servidor escucha **solo en loopback** (`127.0.0.1`/`localhost`, validado por Zod) y **nunca expone prompts** (solo `promptHash`). Se cierra al terminar la sesión de opencode. Si el puerto está ocupado y `autoPortFallback` es `true`, prueba el siguiente libre.
|
|
451
|
+
|
|
417
452
|
|
|
418
453
|
|
|
419
454
|
### Ollama
|
package/dist/cli.js
CHANGED
|
@@ -956,6 +956,24 @@ var PrivacyModeSchema = z2.enum([
|
|
|
956
956
|
"off"
|
|
957
957
|
]);
|
|
958
958
|
var BaselineModeSchema = z2.enum(["auto", "pinned"]);
|
|
959
|
+
var DashboardHostSchema = z2.enum(["127.0.0.1", "localhost"]);
|
|
960
|
+
var DashboardConfigSchema = z2.object({
|
|
961
|
+
enabled: z2.boolean().default(false),
|
|
962
|
+
host: DashboardHostSchema.default("127.0.0.1"),
|
|
963
|
+
port: z2.number().int().min(1024).max(65535).default(4599),
|
|
964
|
+
autoPortFallback: z2.boolean().default(true),
|
|
965
|
+
refreshMs: z2.number().int().min(250).default(2000),
|
|
966
|
+
recentRoutes: z2.number().int().positive().default(50),
|
|
967
|
+
openBrowser: z2.boolean().default(false)
|
|
968
|
+
}).default({
|
|
969
|
+
enabled: false,
|
|
970
|
+
host: "127.0.0.1",
|
|
971
|
+
port: 4599,
|
|
972
|
+
autoPortFallback: true,
|
|
973
|
+
refreshMs: 2000,
|
|
974
|
+
recentRoutes: 50,
|
|
975
|
+
openBrowser: false
|
|
976
|
+
});
|
|
959
977
|
var defaultLocalModel = {
|
|
960
978
|
providerID: "ollama",
|
|
961
979
|
modelID: "qwen3:8b"
|
|
@@ -1017,7 +1035,8 @@ var OpenTeamConfigSchema = z2.object({
|
|
|
1017
1035
|
frontierTokensPerSession: z2.number().int().positive().optional(),
|
|
1018
1036
|
hardStopOnBudgetExhaustion: z2.boolean().default(false)
|
|
1019
1037
|
}).default({ hardStopOnBudgetExhaustion: false }),
|
|
1020
|
-
privacyMode: PrivacyModeSchema.default("forceLocalOnSensitive")
|
|
1038
|
+
privacyMode: PrivacyModeSchema.default("forceLocalOnSensitive"),
|
|
1039
|
+
dashboard: DashboardConfigSchema
|
|
1021
1040
|
});
|
|
1022
1041
|
|
|
1023
1042
|
// src/commands/baseline.ts
|
|
@@ -1081,6 +1100,32 @@ function autoBaseline(config) {
|
|
|
1081
1100
|
};
|
|
1082
1101
|
}
|
|
1083
1102
|
|
|
1103
|
+
// src/commands/dashboard.ts
|
|
1104
|
+
function renderDashboardStatus(dashboard) {
|
|
1105
|
+
const url = `http://${dashboard.host}:${dashboard.port}`;
|
|
1106
|
+
if (!dashboard.enabled) {
|
|
1107
|
+
return [
|
|
1108
|
+
"Dashboard: deshabilitado.",
|
|
1109
|
+
"",
|
|
1110
|
+
'Para activarlo, pon "dashboard": { "enabled": true } en .opencode/openteam.json',
|
|
1111
|
+
"y recarga opencode. Se servirá (loopback) en:",
|
|
1112
|
+
` ${url}`
|
|
1113
|
+
].join(`
|
|
1114
|
+
`);
|
|
1115
|
+
}
|
|
1116
|
+
return [
|
|
1117
|
+
"Dashboard: habilitado.",
|
|
1118
|
+
` URL: ${url}`,
|
|
1119
|
+
` Refresco: ${dashboard.refreshMs} ms (SSE + polling)`,
|
|
1120
|
+
` Rutas: últimas ${dashboard.recentRoutes}`,
|
|
1121
|
+
dashboard.autoPortFallback ? " Puerto: con fallback automático si está ocupado" : " Puerto: fijo (sin fallback)",
|
|
1122
|
+
"",
|
|
1123
|
+
"Nota: solo escucha en loopback y nunca expone prompts (solo hashes).",
|
|
1124
|
+
"El servidor corre dentro de la sesión de opencode; se cierra al salir."
|
|
1125
|
+
].join(`
|
|
1126
|
+
`);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1084
1129
|
// src/commands/doctor.ts
|
|
1085
1130
|
function runtimeLine(snapshot) {
|
|
1086
1131
|
const mark = snapshot.reachable ? "✓" : "✗";
|
|
@@ -1415,6 +1460,7 @@ var HELP = [
|
|
|
1415
1460
|
" openteam baseline auto Baseline cheapest-capable (modo auto)",
|
|
1416
1461
|
" openteam doctor Diagnóstico de runtimes y config",
|
|
1417
1462
|
" openteam agents Lista los agentes y el LLM (local/frontier) de cada uno",
|
|
1463
|
+
" openteam dashboard Estado y URL del dashboard web (loopback)",
|
|
1418
1464
|
" openteam report Resumen de coste/ahorro (telemetría)",
|
|
1419
1465
|
" openteam yolo status Muestra si el modo YOLO está activo",
|
|
1420
1466
|
" openteam yolo on Activa YOLO (opencode auto-aprueba todo)",
|
|
@@ -1604,6 +1650,10 @@ async function runCli(argv, deps) {
|
|
|
1604
1650
|
if (command === "agents") {
|
|
1605
1651
|
return await runAgents(deps, configPath, opencodeConfigPath);
|
|
1606
1652
|
}
|
|
1653
|
+
if (command === "dashboard") {
|
|
1654
|
+
const config = await deps.loadConfig(configPath);
|
|
1655
|
+
return { exitCode: 0, stdout: renderDashboardStatus(config.dashboard) };
|
|
1656
|
+
}
|
|
1607
1657
|
if (command === undefined || command === "help" || command === "--help") {
|
|
1608
1658
|
return { exitCode: 0, stdout: HELP };
|
|
1609
1659
|
}
|
|
@@ -1725,7 +1775,8 @@ function buildOpenTeamConfig(answers) {
|
|
|
1725
1775
|
localDefault
|
|
1726
1776
|
},
|
|
1727
1777
|
local: { runtimes },
|
|
1728
|
-
privacyMode: answers.privacyMode
|
|
1778
|
+
privacyMode: answers.privacyMode,
|
|
1779
|
+
dashboard: { enabled: answers.dashboard }
|
|
1729
1780
|
});
|
|
1730
1781
|
}
|
|
1731
1782
|
function buildOpencodeConfig(answers) {
|
|
@@ -1969,12 +2020,17 @@ async function runInit(deps) {
|
|
|
1969
2020
|
message: "¿Activar modo YOLO? (opencode auto-aprueba todos los permisos; no afecta a la privacidad de openteam)",
|
|
1970
2021
|
initial: false
|
|
1971
2022
|
});
|
|
2023
|
+
const dashboard = await prompt.confirm({
|
|
2024
|
+
message: "¿Exponer el dashboard web local mientras opencode está abierto? (solo loopback en el puerto 4599, sin prompts; ajustable luego en .opencode/openteam.json)",
|
|
2025
|
+
initial: false
|
|
2026
|
+
});
|
|
1972
2027
|
const answers = {
|
|
1973
2028
|
runtimes,
|
|
1974
2029
|
frontier,
|
|
1975
2030
|
routerMode,
|
|
1976
2031
|
privacyMode,
|
|
1977
|
-
yolo
|
|
2032
|
+
yolo,
|
|
2033
|
+
dashboard
|
|
1978
2034
|
};
|
|
1979
2035
|
const openTeamConfig = buildOpenTeamConfig(answers);
|
|
1980
2036
|
const opencodeConfig = buildOpencodeConfig(answers);
|
|
@@ -2010,6 +2066,7 @@ async function runInit(deps) {
|
|
|
2010
2066
|
`Baseline frontier: ${frontier.providerID}/${frontier.modelID}`,
|
|
2011
2067
|
`Runtimes locales: ${enabledSummary || "ninguno habilitado"}`,
|
|
2012
2068
|
`Modo YOLO: ${yolo ? "activado (auto-aprueba permisos)" : "desactivado"}`,
|
|
2069
|
+
`Dashboard web: ${dashboard ? "activado (http://127.0.0.1:4599 mientras opencode esté abierto)" : "desactivado"}`,
|
|
2013
2070
|
`Escrito: ${OPENCODE_CONFIG_PATH}, ${DEFAULT_CONFIG_PATH}, ${ORCHESTRATOR_AGENT_PATH}`,
|
|
2014
2071
|
"",
|
|
2015
2072
|
"Siguientes pasos:",
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DashboardConfig } from "../config/schema";
|
|
2
|
+
/**
|
|
3
|
+
* `openteam dashboard`: informa del estado del dashboard web (habilitado o no)
|
|
4
|
+
* y la URL en la que se sirve mientras la sesión de opencode está activa. El
|
|
5
|
+
* servidor vive dentro del proceso del plugin (`server()`), no en la CLI, así
|
|
6
|
+
* que este comando es de solo lectura: reporta config y cómo activarlo.
|
|
7
|
+
*/
|
|
8
|
+
export declare function renderDashboardStatus(dashboard: DashboardConfig): string;
|
|
9
|
+
//# sourceMappingURL=dashboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../src/commands/dashboard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,eAAe,GAAG,MAAM,CAwBxE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/commands/dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EACL,KAAK,SAAS,EAIf,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/commands/dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EACL,KAAK,SAAS,EAIf,MAAM,UAAU,CAAC;AAalB,MAAM,MAAM,OAAO,GAAG;IACpB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IACtD,UAAU,EAAE,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,KAAK,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAAC;IACnE,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IACvD,kBAAkB,EAAE,CAClB,IAAI,EAAE,MAAM,KACT,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IAClD,mBAAmB,EAAE,CACnB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,EAAE,MAAM,KACT,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,sBAAsB,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IACtD,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAwNF,wBAAsB,MAAM,CAC1B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,SAAS,CAAC,CA+DpB"}
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAKpE,OAAO,EAEL,KAAK,cAAc,EAEnB,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAM5C,iFAAiF;AACjF,eAAO,MAAM,oBAAoB,4BAA4B,CAAC;AAE9D,2DAA2D;AAC3D,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AAEpD,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,cAAc,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,SAAS,YAAY,EAmBjD,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,cAAc,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,cAAc,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,QAAQ,EAAE,cAAc,CAAC;IACzB,UAAU,EAAE,UAAU,CAAC;IACvB,WAAW,EAAE,WAAW,CAAC;IACzB,IAAI,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAKpE,OAAO,EAEL,KAAK,cAAc,EAEnB,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAM5C,iFAAiF;AACjF,eAAO,MAAM,oBAAoB,4BAA4B,CAAC;AAE9D,2DAA2D;AAC3D,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AAEpD,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,cAAc,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,SAAS,YAAY,EAmBjD,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,cAAc,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,cAAc,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,QAAQ,EAAE,cAAc,CAAC;IACzB,UAAU,EAAE,UAAU,CAAC;IACvB,WAAW,EAAE,WAAW,CAAC;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC,CAAC;AAoBF,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAEvE;AAED,qEAAqE;AACrE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,CAKzE;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,GAAE,SAAS,sBAAsB,EAA8B,GACtE,cAAc,EAAE,CAKlB;AAED,2EAA2E;AAC3E,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,SAAS,sBAAsB,EAAE,GAC1C,MAAM,EAAE,CAIV;AAED,0EAA0E;AAC1E,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,SAAS,sBAAsB,EAAE,EAC3C,UAAU,EAAE,MAAM,GACjB,sBAAsB,EAAE,CAI1B;AAgBD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc,CAwCxE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc,CAiDxE;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAEtE;AAED,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI;IAC5B,KAAK,EAAE,CAAC,CAAC;IACT,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAAI,YAAY,CAAC,CAAC,CAAC,GAAG;IACnD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3B,OAAO,CAAC,EAAE,CAAC,CAAC;KACb,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACf,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE;QACnB,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC;QAChC,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IACjB,OAAO,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACxE,IAAI,CAAC,IAAI,EAAE;QACT,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;IACzC,oBAAoB,EAAE,MAAM,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAAC;IAC9D,MAAM,EAAE,QAAQ,CAAC;IACjB,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAChD,CAAC;AA6IF,wBAAsB,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,CAkNhE"}
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -17,6 +17,22 @@ export declare const BaselineModeSchema: z.ZodEnum<{
|
|
|
17
17
|
auto: "auto";
|
|
18
18
|
pinned: "pinned";
|
|
19
19
|
}>;
|
|
20
|
+
export declare const DashboardHostSchema: z.ZodEnum<{
|
|
21
|
+
"127.0.0.1": "127.0.0.1";
|
|
22
|
+
localhost: "localhost";
|
|
23
|
+
}>;
|
|
24
|
+
export declare const DashboardConfigSchema: z.ZodDefault<z.ZodObject<{
|
|
25
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
26
|
+
host: z.ZodDefault<z.ZodEnum<{
|
|
27
|
+
"127.0.0.1": "127.0.0.1";
|
|
28
|
+
localhost: "localhost";
|
|
29
|
+
}>>;
|
|
30
|
+
port: z.ZodDefault<z.ZodNumber>;
|
|
31
|
+
autoPortFallback: z.ZodDefault<z.ZodBoolean>;
|
|
32
|
+
refreshMs: z.ZodDefault<z.ZodNumber>;
|
|
33
|
+
recentRoutes: z.ZodDefault<z.ZodNumber>;
|
|
34
|
+
openBrowser: z.ZodDefault<z.ZodBoolean>;
|
|
35
|
+
}, z.core.$strip>>;
|
|
20
36
|
export declare const LocalRuntimeSchema: z.ZodObject<{
|
|
21
37
|
id: z.ZodEnum<{
|
|
22
38
|
ollama: "ollama";
|
|
@@ -94,10 +110,23 @@ export declare const OpenTeamConfigSchema: z.ZodObject<{
|
|
|
94
110
|
consentBeforeFrontier: "consentBeforeFrontier";
|
|
95
111
|
off: "off";
|
|
96
112
|
}>>;
|
|
113
|
+
dashboard: z.ZodDefault<z.ZodObject<{
|
|
114
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
115
|
+
host: z.ZodDefault<z.ZodEnum<{
|
|
116
|
+
"127.0.0.1": "127.0.0.1";
|
|
117
|
+
localhost: "localhost";
|
|
118
|
+
}>>;
|
|
119
|
+
port: z.ZodDefault<z.ZodNumber>;
|
|
120
|
+
autoPortFallback: z.ZodDefault<z.ZodBoolean>;
|
|
121
|
+
refreshMs: z.ZodDefault<z.ZodNumber>;
|
|
122
|
+
recentRoutes: z.ZodDefault<z.ZodNumber>;
|
|
123
|
+
openBrowser: z.ZodDefault<z.ZodBoolean>;
|
|
124
|
+
}, z.core.$strip>>;
|
|
97
125
|
}, z.core.$strip>;
|
|
98
126
|
export type ModelRef = z.infer<typeof ModelRefSchema>;
|
|
99
127
|
export type RouterMode = z.infer<typeof RouterModeSchema>;
|
|
100
128
|
export type PrivacyMode = z.infer<typeof PrivacyModeSchema>;
|
|
101
129
|
export type OpenTeamConfig = z.infer<typeof OpenTeamConfigSchema>;
|
|
102
130
|
export type LocalRuntime = z.infer<typeof LocalRuntimeSchema>;
|
|
131
|
+
export type DashboardConfig = z.infer<typeof DashboardConfigSchema>;
|
|
103
132
|
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,cAAc;;;iBAGzB,CAAC;AAEH,eAAO,MAAM,gBAAgB;;;;EAA6C,CAAC;AAC3E,eAAO,MAAM,iBAAiB;;;;EAI5B,CAAC;AACH,eAAO,MAAM,kBAAkB;;;EAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,cAAc;;;iBAGzB,CAAC;AAEH,eAAO,MAAM,gBAAgB;;;;EAA6C,CAAC;AAC3E,eAAO,MAAM,iBAAiB;;;;EAI5B,CAAC;AACH,eAAO,MAAM,kBAAkB;;;EAA6B,CAAC;AAE7D,eAAO,MAAM,mBAAmB;;;EAAqC,CAAC;AAEtE,eAAO,MAAM,qBAAqB;;;;;;;;;;;kBAkB9B,CAAC;AAWL,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;iBAM7B,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2D/B,CAAC;AAEH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AACtD,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC1D,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC5D,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAClE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAC9D,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { appendFile, mkdir } from "node:fs/promises";
|
|
|
2
2
|
import type { Plugin, PluginModule } from "@opencode-ai/plugin";
|
|
3
3
|
import type { CliDeps } from "./commands/dispatch";
|
|
4
4
|
import type { OpenTeamConfig } from "./config/schema";
|
|
5
|
+
import type { ActivityEntry } from "./dashboard/types";
|
|
5
6
|
import { RuntimeRegistry } from "./local/registry";
|
|
6
7
|
type FileAppenderDeps = {
|
|
7
8
|
mkdir?: typeof mkdir;
|
|
@@ -11,6 +12,14 @@ export declare function logAvailabilityRefreshError(error: unknown): void;
|
|
|
11
12
|
export declare function logTelemetryError(error: unknown): void;
|
|
12
13
|
export declare function createFileAppender(filePath: string, deps?: FileAppenderDeps): (line: string) => Promise<void>;
|
|
13
14
|
export declare function isMissingFile(error: unknown): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Mapea un evento de opencode a una entrada de actividad **redactada** (solo el
|
|
17
|
+
* tipo de evento, nunca contenido de prompt). Devuelve `undefined` para eventos
|
|
18
|
+
* fuera de la allow-list.
|
|
19
|
+
*/
|
|
20
|
+
export declare function activityFromEvent(event: {
|
|
21
|
+
type: string;
|
|
22
|
+
}, now: () => number): ActivityEntry | undefined;
|
|
14
23
|
export declare function createCliDeps(config: OpenTeamConfig, registry: RuntimeRegistry, telemetryPath: string): CliDeps;
|
|
15
24
|
export declare const server: Plugin;
|
|
16
25
|
declare const plugin: PluginModule;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,KAAK,EAIN,MAAM,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EAAE,MAAM,EAAe,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAE7E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAKnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,KAAK,EAIN,MAAM,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EAAE,MAAM,EAAe,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAE7E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAKnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,mBAAmB,CAAC;AAEnE,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAkBnD,KAAK,gBAAgB,GAAG;IACtB,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,UAAU,CAAC;CAChC,CAAC;AAcF,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAGhE;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAGtD;AA0BD,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,gBAAqB,GAC1B,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAQjC;AAeD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAOrD;AAuCD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,EACvB,GAAG,EAAE,MAAM,MAAM,GAChB,aAAa,GAAG,SAAS,CAM3B;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,eAAe,EACzB,aAAa,EAAE,MAAM,GACpB,OAAO,CA4DT;AAED,eAAO,MAAM,MAAM,EAAE,MAyDpB,CAAC;AAEF,QAAA,MAAM,MAAM,EAAE,YAGb,CAAC;AAEF,eAAe,MAAM,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,24 @@ var PrivacyModeSchema = z.enum([
|
|
|
25
25
|
"off"
|
|
26
26
|
]);
|
|
27
27
|
var BaselineModeSchema = z.enum(["auto", "pinned"]);
|
|
28
|
+
var DashboardHostSchema = z.enum(["127.0.0.1", "localhost"]);
|
|
29
|
+
var DashboardConfigSchema = z.object({
|
|
30
|
+
enabled: z.boolean().default(false),
|
|
31
|
+
host: DashboardHostSchema.default("127.0.0.1"),
|
|
32
|
+
port: z.number().int().min(1024).max(65535).default(4599),
|
|
33
|
+
autoPortFallback: z.boolean().default(true),
|
|
34
|
+
refreshMs: z.number().int().min(250).default(2000),
|
|
35
|
+
recentRoutes: z.number().int().positive().default(50),
|
|
36
|
+
openBrowser: z.boolean().default(false)
|
|
37
|
+
}).default({
|
|
38
|
+
enabled: false,
|
|
39
|
+
host: "127.0.0.1",
|
|
40
|
+
port: 4599,
|
|
41
|
+
autoPortFallback: true,
|
|
42
|
+
refreshMs: 2000,
|
|
43
|
+
recentRoutes: 50,
|
|
44
|
+
openBrowser: false
|
|
45
|
+
});
|
|
28
46
|
var defaultLocalModel = {
|
|
29
47
|
providerID: "ollama",
|
|
30
48
|
modelID: "qwen3:8b"
|
|
@@ -86,7 +104,8 @@ var OpenTeamConfigSchema = z.object({
|
|
|
86
104
|
frontierTokensPerSession: z.number().int().positive().optional(),
|
|
87
105
|
hardStopOnBudgetExhaustion: z.boolean().default(false)
|
|
88
106
|
}).default({ hardStopOnBudgetExhaustion: false }),
|
|
89
|
-
privacyMode: PrivacyModeSchema.default("forceLocalOnSensitive")
|
|
107
|
+
privacyMode: PrivacyModeSchema.default("forceLocalOnSensitive"),
|
|
108
|
+
dashboard: DashboardConfigSchema
|
|
90
109
|
});
|
|
91
110
|
|
|
92
111
|
// src/config/persist.ts
|
|
@@ -948,6 +967,32 @@ function autoBaseline(config) {
|
|
|
948
967
|
};
|
|
949
968
|
}
|
|
950
969
|
|
|
970
|
+
// src/commands/dashboard.ts
|
|
971
|
+
function renderDashboardStatus(dashboard) {
|
|
972
|
+
const url = `http://${dashboard.host}:${dashboard.port}`;
|
|
973
|
+
if (!dashboard.enabled) {
|
|
974
|
+
return [
|
|
975
|
+
"Dashboard: deshabilitado.",
|
|
976
|
+
"",
|
|
977
|
+
'Para activarlo, pon "dashboard": { "enabled": true } en .opencode/openteam.json',
|
|
978
|
+
"y recarga opencode. Se servirá (loopback) en:",
|
|
979
|
+
` ${url}`
|
|
980
|
+
].join(`
|
|
981
|
+
`);
|
|
982
|
+
}
|
|
983
|
+
return [
|
|
984
|
+
"Dashboard: habilitado.",
|
|
985
|
+
` URL: ${url}`,
|
|
986
|
+
` Refresco: ${dashboard.refreshMs} ms (SSE + polling)`,
|
|
987
|
+
` Rutas: últimas ${dashboard.recentRoutes}`,
|
|
988
|
+
dashboard.autoPortFallback ? " Puerto: con fallback automático si está ocupado" : " Puerto: fijo (sin fallback)",
|
|
989
|
+
"",
|
|
990
|
+
"Nota: solo escucha en loopback y nunca expone prompts (solo hashes).",
|
|
991
|
+
"El servidor corre dentro de la sesión de opencode; se cierra al salir."
|
|
992
|
+
].join(`
|
|
993
|
+
`);
|
|
994
|
+
}
|
|
995
|
+
|
|
951
996
|
// src/commands/doctor.ts
|
|
952
997
|
function runtimeLine(snapshot) {
|
|
953
998
|
const mark = snapshot.reachable ? "✓" : "✗";
|
|
@@ -1097,6 +1142,7 @@ var HELP = [
|
|
|
1097
1142
|
" openteam baseline auto Baseline cheapest-capable (modo auto)",
|
|
1098
1143
|
" openteam doctor Diagnóstico de runtimes y config",
|
|
1099
1144
|
" openteam agents Lista los agentes y el LLM (local/frontier) de cada uno",
|
|
1145
|
+
" openteam dashboard Estado y URL del dashboard web (loopback)",
|
|
1100
1146
|
" openteam report Resumen de coste/ahorro (telemetría)",
|
|
1101
1147
|
" openteam yolo status Muestra si el modo YOLO está activo",
|
|
1102
1148
|
" openteam yolo on Activa YOLO (opencode auto-aprueba todo)",
|
|
@@ -1286,6 +1332,10 @@ async function runCli(argv, deps) {
|
|
|
1286
1332
|
if (command === "agents") {
|
|
1287
1333
|
return await runAgents(deps, configPath, opencodeConfigPath);
|
|
1288
1334
|
}
|
|
1335
|
+
if (command === "dashboard") {
|
|
1336
|
+
const config = await deps.loadConfig(configPath);
|
|
1337
|
+
return { exitCode: 0, stdout: renderDashboardStatus(config.dashboard) };
|
|
1338
|
+
}
|
|
1289
1339
|
if (command === undefined || command === "help" || command === "--help") {
|
|
1290
1340
|
return { exitCode: 0, stdout: HELP };
|
|
1291
1341
|
}
|
|
@@ -1316,13 +1366,23 @@ function commandArgv(action, model) {
|
|
|
1316
1366
|
return ["report"];
|
|
1317
1367
|
case "agents":
|
|
1318
1368
|
return ["agents"];
|
|
1369
|
+
case "dashboard":
|
|
1370
|
+
return ["dashboard"];
|
|
1319
1371
|
}
|
|
1320
1372
|
}
|
|
1321
1373
|
function createCommandTool(deps) {
|
|
1322
1374
|
return tool({
|
|
1323
|
-
description: "Comandos runtime de openteam: baseline (show/set/auto), doctor, report
|
|
1375
|
+
description: "Comandos runtime de openteam: baseline (show/set/auto), doctor, report, agents (LLM por agente: local/frontier + suscripción) y dashboard (estado/URL del panel web).",
|
|
1324
1376
|
args: {
|
|
1325
|
-
action: tool.schema.enum([
|
|
1377
|
+
action: tool.schema.enum([
|
|
1378
|
+
"show",
|
|
1379
|
+
"set",
|
|
1380
|
+
"auto",
|
|
1381
|
+
"doctor",
|
|
1382
|
+
"report",
|
|
1383
|
+
"agents",
|
|
1384
|
+
"dashboard"
|
|
1385
|
+
]).describe("Acción a ejecutar"),
|
|
1326
1386
|
model: tool.schema.string().optional().describe("Modelo provider/model (solo para action=set)")
|
|
1327
1387
|
},
|
|
1328
1388
|
async execute(args) {
|
|
@@ -1965,6 +2025,579 @@ async function readCostRecords(path, deps = {}) {
|
|
|
1965
2025
|
}
|
|
1966
2026
|
}
|
|
1967
2027
|
|
|
2028
|
+
// src/web/paths.ts
|
|
2029
|
+
var DEFAULT_BACKLOG_PATH = ".opencode/openteam-backlog.md";
|
|
2030
|
+
|
|
2031
|
+
// src/web/start.ts
|
|
2032
|
+
import { watch as fsWatch } from "node:fs";
|
|
2033
|
+
|
|
2034
|
+
// src/web/activity.ts
|
|
2035
|
+
function createActivityBuffer(max = 100) {
|
|
2036
|
+
const entries = [];
|
|
2037
|
+
return {
|
|
2038
|
+
push(entry) {
|
|
2039
|
+
entries.push(entry);
|
|
2040
|
+
if (entries.length > max) {
|
|
2041
|
+
entries.splice(0, entries.length - max);
|
|
2042
|
+
}
|
|
2043
|
+
},
|
|
2044
|
+
list() {
|
|
2045
|
+
return [...entries];
|
|
2046
|
+
}
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
// src/dashboard/render.ts
|
|
2051
|
+
var TIERS2 = [
|
|
2052
|
+
"trivial",
|
|
2053
|
+
"simple",
|
|
2054
|
+
"moderate",
|
|
2055
|
+
"hard"
|
|
2056
|
+
];
|
|
2057
|
+
function escapeHtml(value) {
|
|
2058
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2059
|
+
}
|
|
2060
|
+
function usd2(value) {
|
|
2061
|
+
return `$${value.toFixed(5)}`;
|
|
2062
|
+
}
|
|
2063
|
+
function pct(value) {
|
|
2064
|
+
return `${value.toFixed(2)}%`;
|
|
2065
|
+
}
|
|
2066
|
+
function agentModel(agent) {
|
|
2067
|
+
return agent.model === undefined ? "hereda default" : `${agent.model.providerID}/${agent.model.modelID}`;
|
|
2068
|
+
}
|
|
2069
|
+
function summaryPanel(cost) {
|
|
2070
|
+
return [
|
|
2071
|
+
'<section class="panel" id="panel-summary">',
|
|
2072
|
+
"<h2>Resumen de routing</h2>",
|
|
2073
|
+
'<div class="grid">',
|
|
2074
|
+
`<div class="stat"><span class="k">Decisiones</span><span class="v">${cost.count}</span></div>`,
|
|
2075
|
+
`<div class="stat"><span class="k">Local</span><span class="v">${cost.localCount}</span></div>`,
|
|
2076
|
+
`<div class="stat"><span class="k">Frontier</span><span class="v">${cost.frontierCount}</span></div>`,
|
|
2077
|
+
`<div class="stat"><span class="k">Coste real</span><span class="v">${usd2(cost.totalEstimatedUSD)}</span></div>`,
|
|
2078
|
+
`<div class="stat"><span class="k">Baseline</span><span class="v">${usd2(cost.totalBaselineUSD)}</span></div>`,
|
|
2079
|
+
`<div class="stat"><span class="k">Ahorro</span><span class="v good">${usd2(cost.totalSavingsUSD)} (${pct(cost.savingsPct)})</span></div>`,
|
|
2080
|
+
`<div class="stat"><span class="k">Tokens in</span><span class="v">${cost.tokensIn}</span></div>`,
|
|
2081
|
+
`<div class="stat"><span class="k">Tokens out</span><span class="v">${cost.tokensOut}</span></div>`,
|
|
2082
|
+
"</div>",
|
|
2083
|
+
"</section>"
|
|
2084
|
+
].join("");
|
|
2085
|
+
}
|
|
2086
|
+
function tierPanel(cost) {
|
|
2087
|
+
const rows = TIERS2.map((tier) => {
|
|
2088
|
+
const summary = cost.byTier[tier];
|
|
2089
|
+
return `<tr><td>${tier}</td><td>${summary.count}</td><td>${usd2(summary.estimatedUSD)}</td><td class="good">${usd2(summary.savingsUSD)}</td></tr>`;
|
|
2090
|
+
}).join("");
|
|
2091
|
+
return [
|
|
2092
|
+
'<section class="panel" id="panel-tier">',
|
|
2093
|
+
"<h2>Por tier</h2>",
|
|
2094
|
+
"<table><thead><tr><th>Tier</th><th>Nº</th><th>Coste</th><th>Ahorro</th></tr></thead>",
|
|
2095
|
+
`<tbody>${rows}</tbody></table>`,
|
|
2096
|
+
"</section>"
|
|
2097
|
+
].join("");
|
|
2098
|
+
}
|
|
2099
|
+
function loopItemRow(item) {
|
|
2100
|
+
const box = item.done ? "☑" : "☐";
|
|
2101
|
+
const cls = item.done ? "done" : "open";
|
|
2102
|
+
const who = item.assignee === undefined ? "" : `<span class="who">@${escapeHtml(item.assignee)}</span> `;
|
|
2103
|
+
return `<li class="${cls}"><span class="box">${box}</span> ${who}${escapeHtml(item.text)}</li>`;
|
|
2104
|
+
}
|
|
2105
|
+
function loopPanel(loop) {
|
|
2106
|
+
if (loop === undefined) {
|
|
2107
|
+
return [
|
|
2108
|
+
'<section class="panel" id="panel-loop">',
|
|
2109
|
+
"<h2>Loop</h2>",
|
|
2110
|
+
'<p class="muted">Sin backlog activo (no hay <code>openteam-backlog.md</code>).</p>',
|
|
2111
|
+
"</section>"
|
|
2112
|
+
].join("");
|
|
2113
|
+
}
|
|
2114
|
+
const items = loop.items.map(loopItemRow).join("");
|
|
2115
|
+
const commit = loop.lastCommit === undefined ? "" : `<p class="muted">Último commit: <code>${escapeHtml(loop.lastCommit.hash)}</code> ${escapeHtml(loop.lastCommit.subject)}</p>`;
|
|
2116
|
+
return [
|
|
2117
|
+
'<section class="panel" id="panel-loop">',
|
|
2118
|
+
"<h2>Loop</h2>",
|
|
2119
|
+
`<div class="progress" role="progressbar" aria-valuenow="${loop.progressPct}" aria-valuemin="0" aria-valuemax="100"><div class="bar" style="width:${loop.progressPct}%"></div></div>`,
|
|
2120
|
+
`<p class="muted">${loop.done}/${loop.total} completados · ${loop.open} abiertos · ${loop.progressPct}%</p>`,
|
|
2121
|
+
`<ul class="items">${items}</ul>`,
|
|
2122
|
+
commit,
|
|
2123
|
+
"</section>"
|
|
2124
|
+
].join("");
|
|
2125
|
+
}
|
|
2126
|
+
function teamPanel(team) {
|
|
2127
|
+
if (team.length === 0) {
|
|
2128
|
+
return [
|
|
2129
|
+
'<section class="panel" id="panel-team">',
|
|
2130
|
+
"<h2>Equipo</h2>",
|
|
2131
|
+
'<p class="muted">Sin agentes en <code>.opencode/agent/</code>.</p>',
|
|
2132
|
+
"</section>"
|
|
2133
|
+
].join("");
|
|
2134
|
+
}
|
|
2135
|
+
const rows = team.map((agent) => `<tr><td>${escapeHtml(agent.name)}</td><td>${escapeHtml(agent.mode)}</td><td>${escapeHtml(agentModel(agent))}</td></tr>`).join("");
|
|
2136
|
+
return [
|
|
2137
|
+
'<section class="panel" id="panel-team">',
|
|
2138
|
+
"<h2>Equipo</h2>",
|
|
2139
|
+
"<table><thead><tr><th>Agente</th><th>Modo</th><th>LLM</th></tr></thead>",
|
|
2140
|
+
`<tbody>${rows}</tbody></table>`,
|
|
2141
|
+
"</section>"
|
|
2142
|
+
].join("");
|
|
2143
|
+
}
|
|
2144
|
+
function routeRow(route) {
|
|
2145
|
+
return `<tr><td>${route.tier}</td><td class="${route.routeKind}">${route.routeKind}</td><td>${escapeHtml(route.model)}</td><td class="hash">${escapeHtml(route.promptHash)}</td><td>${usd2(route.estimatedCostUSD)}</td><td class="good">${usd2(route.estimatedSavingsUSD)}</td></tr>`;
|
|
2146
|
+
}
|
|
2147
|
+
function routesPanel(routes) {
|
|
2148
|
+
if (routes.length === 0) {
|
|
2149
|
+
return [
|
|
2150
|
+
'<section class="panel" id="panel-routes">',
|
|
2151
|
+
"<h2>Decisiones recientes</h2>",
|
|
2152
|
+
'<p class="muted">Sin decisiones de routing todavía.</p>',
|
|
2153
|
+
"</section>"
|
|
2154
|
+
].join("");
|
|
2155
|
+
}
|
|
2156
|
+
const rows = routes.map(routeRow).join("");
|
|
2157
|
+
return [
|
|
2158
|
+
'<section class="panel" id="panel-routes">',
|
|
2159
|
+
"<h2>Decisiones recientes</h2>",
|
|
2160
|
+
"<table><thead><tr><th>Tier</th><th>Ruta</th><th>Modelo</th><th>Prompt (hash)</th><th>Coste</th><th>Ahorro</th></tr></thead>",
|
|
2161
|
+
`<tbody>${rows}</tbody></table>`,
|
|
2162
|
+
"</section>"
|
|
2163
|
+
].join("");
|
|
2164
|
+
}
|
|
2165
|
+
function activityRow(entry) {
|
|
2166
|
+
const who = entry.agent === undefined ? "" : `<span class="who">${escapeHtml(entry.agent)}</span> `;
|
|
2167
|
+
return `<li class="${entry.kind}"><span class="kind">${entry.kind}</span> ${who}${escapeHtml(entry.summary)}</li>`;
|
|
2168
|
+
}
|
|
2169
|
+
function activityPanel(activity) {
|
|
2170
|
+
if (activity.length === 0) {
|
|
2171
|
+
return [
|
|
2172
|
+
'<section class="panel" id="panel-activity">',
|
|
2173
|
+
"<h2>Actividad</h2>",
|
|
2174
|
+
'<p class="muted">Sin actividad registrada.</p>',
|
|
2175
|
+
"</section>"
|
|
2176
|
+
].join("");
|
|
2177
|
+
}
|
|
2178
|
+
const rows = activity.map(activityRow).join("");
|
|
2179
|
+
return [
|
|
2180
|
+
'<section class="panel" id="panel-activity">',
|
|
2181
|
+
"<h2>Actividad</h2>",
|
|
2182
|
+
`<ul class="timeline">${rows}</ul>`,
|
|
2183
|
+
"</section>"
|
|
2184
|
+
].join("");
|
|
2185
|
+
}
|
|
2186
|
+
var STYLE = `
|
|
2187
|
+
:root{color-scheme:dark;--bg:#0f1115;--panel:#171a21;--fg:#e6e8eb;--muted:#8b929c;--good:#3fb950;--local:#58a6ff;--frontier:#d29922;--line:#262b34}
|
|
2188
|
+
*{box-sizing:border-box}
|
|
2189
|
+
body{margin:0;background:var(--bg);color:var(--fg);font:14px/1.5 ui-sans-serif,system-ui,Segoe UI,Roboto,sans-serif}
|
|
2190
|
+
header{padding:16px 24px;border-bottom:1px solid var(--line);display:flex;justify-content:space-between;align-items:baseline;gap:12px;flex-wrap:wrap}
|
|
2191
|
+
header h1{font-size:18px;margin:0}
|
|
2192
|
+
header .meta{color:var(--muted);font-size:12px}
|
|
2193
|
+
main{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:16px;padding:24px}
|
|
2194
|
+
.panel{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:16px}
|
|
2195
|
+
.panel h2{font-size:14px;margin:0 0 12px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}
|
|
2196
|
+
.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}
|
|
2197
|
+
.stat{display:flex;flex-direction:column;gap:2px}
|
|
2198
|
+
.stat .k{color:var(--muted);font-size:12px}
|
|
2199
|
+
.stat .v{font-size:18px;font-weight:600}
|
|
2200
|
+
.good{color:var(--good)}
|
|
2201
|
+
.local{color:var(--local)}
|
|
2202
|
+
.frontier{color:var(--frontier)}
|
|
2203
|
+
.muted{color:var(--muted)}
|
|
2204
|
+
table{width:100%;border-collapse:collapse;font-size:13px}
|
|
2205
|
+
th,td{text-align:left;padding:6px 8px;border-bottom:1px solid var(--line)}
|
|
2206
|
+
th{color:var(--muted);font-weight:600}
|
|
2207
|
+
.hash{font-family:ui-monospace,Consolas,monospace;color:var(--muted)}
|
|
2208
|
+
.progress{height:10px;background:#0b0d11;border-radius:6px;overflow:hidden;border:1px solid var(--line)}
|
|
2209
|
+
.progress .bar{height:100%;background:var(--good)}
|
|
2210
|
+
ul.items,ul.timeline{list-style:none;margin:8px 0 0;padding:0;max-height:320px;overflow:auto}
|
|
2211
|
+
ul.items li,ul.timeline li{padding:4px 0;border-bottom:1px solid var(--line)}
|
|
2212
|
+
ul.items li.done{color:var(--muted)}
|
|
2213
|
+
ul.items .box{font-family:monospace}
|
|
2214
|
+
.who{color:var(--local)}
|
|
2215
|
+
.kind{display:inline-block;min-width:66px;color:var(--muted);font-size:12px}
|
|
2216
|
+
code{font-family:ui-monospace,Consolas,monospace;background:#0b0d11;padding:1px 5px;border-radius:4px}
|
|
2217
|
+
`;
|
|
2218
|
+
var CLIENT_JS = `
|
|
2219
|
+
(function(){
|
|
2220
|
+
function reloadIfChanged(prev){
|
|
2221
|
+
fetch('/api/state').then(function(r){return r.json()}).then(function(s){
|
|
2222
|
+
if(s.generatedAt!==prev){location.reload()}
|
|
2223
|
+
}).catch(function(){});
|
|
2224
|
+
}
|
|
2225
|
+
var current=document.documentElement.getAttribute('data-generated')||'';
|
|
2226
|
+
if('EventSource' in window){
|
|
2227
|
+
try{
|
|
2228
|
+
var es=new EventSource('/events');
|
|
2229
|
+
es.addEventListener('state',function(){location.reload()});
|
|
2230
|
+
es.onerror=function(){/* fallback below */};
|
|
2231
|
+
}catch(e){/* ignore */}
|
|
2232
|
+
}
|
|
2233
|
+
var ms=parseInt(document.documentElement.getAttribute('data-refresh')||'2000',10);
|
|
2234
|
+
setInterval(function(){reloadIfChanged(current)},isNaN(ms)?2000:Math.max(250,ms));
|
|
2235
|
+
})();
|
|
2236
|
+
`;
|
|
2237
|
+
function renderDashboardHtml(state, options = {}) {
|
|
2238
|
+
const refreshMs = options.refreshMs ?? 2000;
|
|
2239
|
+
const body = [
|
|
2240
|
+
summaryPanel(state.cost),
|
|
2241
|
+
loopPanel(state.loop),
|
|
2242
|
+
teamPanel(state.team),
|
|
2243
|
+
tierPanel(state.cost),
|
|
2244
|
+
routesPanel(state.recentRoutes),
|
|
2245
|
+
activityPanel(state.activity)
|
|
2246
|
+
].join("");
|
|
2247
|
+
const session = state.session.id === undefined ? "" : ` · sesión ${escapeHtml(state.session.id)}`;
|
|
2248
|
+
return [
|
|
2249
|
+
"<!doctype html>",
|
|
2250
|
+
`<html lang="es" data-generated="${escapeHtml(state.generatedAt)}" data-refresh="${refreshMs}">`,
|
|
2251
|
+
"<head>",
|
|
2252
|
+
'<meta charset="utf-8">',
|
|
2253
|
+
'<meta name="viewport" content="width=device-width,initial-scale=1">',
|
|
2254
|
+
"<title>openteam dashboard</title>",
|
|
2255
|
+
`<style>${STYLE}</style>`,
|
|
2256
|
+
"</head>",
|
|
2257
|
+
"<body>",
|
|
2258
|
+
"<header>",
|
|
2259
|
+
"<h1>openteam dashboard</h1>",
|
|
2260
|
+
`<span class="meta">actualizado ${escapeHtml(state.generatedAt)}${session}</span>`,
|
|
2261
|
+
"</header>",
|
|
2262
|
+
`<main>${body}</main>`,
|
|
2263
|
+
`<script>${CLIENT_JS}</script>`,
|
|
2264
|
+
"</body>",
|
|
2265
|
+
"</html>"
|
|
2266
|
+
].join("");
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
// src/dashboard/backlog.ts
|
|
2270
|
+
var ITEM_RE = /^\s*[-*+]\s+\[( |x|X)\]\s+(.*)$/;
|
|
2271
|
+
var ASSIGNEE_RE = /^\[@([^\]]+)\]\s*(.*)$/;
|
|
2272
|
+
function parseAssignee(text) {
|
|
2273
|
+
const match = ASSIGNEE_RE.exec(text);
|
|
2274
|
+
if (match === null) {
|
|
2275
|
+
return { text: text.trim() };
|
|
2276
|
+
}
|
|
2277
|
+
const assignee = (match[1] ?? "").trim();
|
|
2278
|
+
const rest = (match[2] ?? "").trim();
|
|
2279
|
+
if (assignee.length === 0) {
|
|
2280
|
+
return { text: text.trim() };
|
|
2281
|
+
}
|
|
2282
|
+
return { assignee, text: rest };
|
|
2283
|
+
}
|
|
2284
|
+
function parseBacklog(text) {
|
|
2285
|
+
const items = [];
|
|
2286
|
+
for (const line of text.split(/\r?\n/)) {
|
|
2287
|
+
const match = ITEM_RE.exec(line);
|
|
2288
|
+
if (match === null) {
|
|
2289
|
+
continue;
|
|
2290
|
+
}
|
|
2291
|
+
const done = (match[1] ?? " ").toLowerCase() === "x";
|
|
2292
|
+
const rawText = (match[2] ?? "").trim();
|
|
2293
|
+
const { assignee, text: itemText } = parseAssignee(rawText);
|
|
2294
|
+
const item = { text: itemText, done };
|
|
2295
|
+
if (assignee !== undefined) {
|
|
2296
|
+
item.assignee = assignee;
|
|
2297
|
+
}
|
|
2298
|
+
items.push(item);
|
|
2299
|
+
}
|
|
2300
|
+
return items;
|
|
2301
|
+
}
|
|
2302
|
+
function buildLoopSnapshot(backlogPath, items) {
|
|
2303
|
+
const total = items.length;
|
|
2304
|
+
const done = items.filter((item) => item.done).length;
|
|
2305
|
+
const open = total - done;
|
|
2306
|
+
const progressPct = total === 0 ? 100 : Math.round(done / total * 100);
|
|
2307
|
+
return {
|
|
2308
|
+
backlogPath,
|
|
2309
|
+
total,
|
|
2310
|
+
done,
|
|
2311
|
+
open,
|
|
2312
|
+
progressPct,
|
|
2313
|
+
items: [...items]
|
|
2314
|
+
};
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
// src/dashboard/state.ts
|
|
2318
|
+
var DEFAULT_RECENT_ROUTES = 50;
|
|
2319
|
+
var DEFAULT_ACTIVITY_LIMIT = 100;
|
|
2320
|
+
function toRouteView(record) {
|
|
2321
|
+
return {
|
|
2322
|
+
ts: record.ts,
|
|
2323
|
+
tier: record.tier,
|
|
2324
|
+
routeKind: record.routeKind,
|
|
2325
|
+
model: `${record.selected.providerID}/${record.selected.modelID}`,
|
|
2326
|
+
promptHash: record.promptHash,
|
|
2327
|
+
estimatedCostUSD: record.estimatedCostUSD,
|
|
2328
|
+
estimatedSavingsUSD: record.estimatedSavingsUSD,
|
|
2329
|
+
budgetAction: record.budgetAction
|
|
2330
|
+
};
|
|
2331
|
+
}
|
|
2332
|
+
function recentRoutes(records, limit) {
|
|
2333
|
+
return [...records].sort((a, b) => b.ts - a.ts).slice(0, Math.max(0, limit)).map(toRouteView);
|
|
2334
|
+
}
|
|
2335
|
+
function recentActivity(entries, limit) {
|
|
2336
|
+
return [...entries].sort((a, b) => b.ts - a.ts).slice(0, Math.max(0, limit));
|
|
2337
|
+
}
|
|
2338
|
+
function loopFrom(backlog) {
|
|
2339
|
+
if (backlog === undefined) {
|
|
2340
|
+
return;
|
|
2341
|
+
}
|
|
2342
|
+
const snapshot = buildLoopSnapshot(backlog.path, backlog.items);
|
|
2343
|
+
if (backlog.lastCommit !== undefined) {
|
|
2344
|
+
snapshot.lastCommit = backlog.lastCommit;
|
|
2345
|
+
}
|
|
2346
|
+
return snapshot;
|
|
2347
|
+
}
|
|
2348
|
+
function buildDashboardState(inputs) {
|
|
2349
|
+
const recentRoutesLimit = inputs.recentRoutesLimit ?? DEFAULT_RECENT_ROUTES;
|
|
2350
|
+
const activityLimit = inputs.activityLimit ?? DEFAULT_ACTIVITY_LIMIT;
|
|
2351
|
+
const state = {
|
|
2352
|
+
generatedAt: inputs.generatedAt,
|
|
2353
|
+
session: {},
|
|
2354
|
+
cost: summarizeCostRecords(inputs.costRecords),
|
|
2355
|
+
recentRoutes: recentRoutes(inputs.costRecords, recentRoutesLimit),
|
|
2356
|
+
team: [...inputs.team],
|
|
2357
|
+
activity: recentActivity(inputs.activity, activityLimit)
|
|
2358
|
+
};
|
|
2359
|
+
if (inputs.session?.id !== undefined) {
|
|
2360
|
+
state.session.id = inputs.session.id;
|
|
2361
|
+
}
|
|
2362
|
+
if (inputs.session?.startedAt !== undefined) {
|
|
2363
|
+
state.session.startedAt = inputs.session.startedAt;
|
|
2364
|
+
}
|
|
2365
|
+
const loop = loopFrom(inputs.backlog);
|
|
2366
|
+
if (loop !== undefined) {
|
|
2367
|
+
state.loop = loop;
|
|
2368
|
+
}
|
|
2369
|
+
return state;
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
// src/web/server.ts
|
|
2373
|
+
var SSE_HEADERS = {
|
|
2374
|
+
"content-type": "text/event-stream",
|
|
2375
|
+
"cache-control": "no-cache",
|
|
2376
|
+
connection: "keep-alive"
|
|
2377
|
+
};
|
|
2378
|
+
var JSON_HEADERS = { "content-type": "application/json" };
|
|
2379
|
+
var HTML_HEADERS = { "content-type": "text/html; charset=utf-8" };
|
|
2380
|
+
function isAddressInUse(error) {
|
|
2381
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "EADDRINUSE";
|
|
2382
|
+
}
|
|
2383
|
+
async function renderState(deps) {
|
|
2384
|
+
return renderDashboardHtml(buildDashboardState(await deps.readSnapshot()), {
|
|
2385
|
+
refreshMs: deps.config.refreshMs
|
|
2386
|
+
});
|
|
2387
|
+
}
|
|
2388
|
+
async function stateJson(deps) {
|
|
2389
|
+
return JSON.stringify(buildDashboardState(await deps.readSnapshot()));
|
|
2390
|
+
}
|
|
2391
|
+
function sseMessage(json) {
|
|
2392
|
+
return `event: state
|
|
2393
|
+
data: ${json}
|
|
2394
|
+
|
|
2395
|
+
`;
|
|
2396
|
+
}
|
|
2397
|
+
function createDashboardServer(deps) {
|
|
2398
|
+
const serve = deps.serve ?? Bun.serve;
|
|
2399
|
+
const log = deps.log ?? ((message) => console.log(message));
|
|
2400
|
+
const encoder = new TextEncoder;
|
|
2401
|
+
const clients = new Set;
|
|
2402
|
+
const handler = async (request) => {
|
|
2403
|
+
const url2 = new URL(request.url);
|
|
2404
|
+
if (request.method !== "GET") {
|
|
2405
|
+
return new Response("method not allowed", { status: 405 });
|
|
2406
|
+
}
|
|
2407
|
+
if (url2.pathname === "/" || url2.pathname === "/index.html") {
|
|
2408
|
+
return new Response(await renderState(deps), { headers: HTML_HEADERS });
|
|
2409
|
+
}
|
|
2410
|
+
if (url2.pathname === "/api/state") {
|
|
2411
|
+
return new Response(await stateJson(deps), { headers: JSON_HEADERS });
|
|
2412
|
+
}
|
|
2413
|
+
if (url2.pathname === "/healthz") {
|
|
2414
|
+
return new Response(JSON.stringify({ ok: true }), {
|
|
2415
|
+
headers: JSON_HEADERS
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2418
|
+
if (url2.pathname === "/favicon.ico") {
|
|
2419
|
+
return new Response(null, { status: 204 });
|
|
2420
|
+
}
|
|
2421
|
+
if (url2.pathname === "/events") {
|
|
2422
|
+
const initial = await stateJson(deps);
|
|
2423
|
+
const stream = new ReadableStream({
|
|
2424
|
+
start(controller) {
|
|
2425
|
+
controller.enqueue(encoder.encode(sseMessage(initial)));
|
|
2426
|
+
clients.add(controller);
|
|
2427
|
+
},
|
|
2428
|
+
cancel(controller) {
|
|
2429
|
+
clients.delete(controller);
|
|
2430
|
+
}
|
|
2431
|
+
});
|
|
2432
|
+
return new Response(stream, { headers: SSE_HEADERS });
|
|
2433
|
+
}
|
|
2434
|
+
return new Response("not found", { status: 404 });
|
|
2435
|
+
};
|
|
2436
|
+
const maxAttempts = deps.config.autoPortFallback ? 20 : 1;
|
|
2437
|
+
let server;
|
|
2438
|
+
let lastError;
|
|
2439
|
+
for (let offset = 0;offset < maxAttempts; offset += 1) {
|
|
2440
|
+
const port = deps.config.port + offset;
|
|
2441
|
+
if (port > 65535) {
|
|
2442
|
+
break;
|
|
2443
|
+
}
|
|
2444
|
+
try {
|
|
2445
|
+
server = serve({
|
|
2446
|
+
hostname: deps.config.host,
|
|
2447
|
+
port,
|
|
2448
|
+
fetch: handler
|
|
2449
|
+
});
|
|
2450
|
+
break;
|
|
2451
|
+
} catch (error) {
|
|
2452
|
+
lastError = error;
|
|
2453
|
+
if (!isAddressInUse(error)) {
|
|
2454
|
+
throw error;
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
if (server === undefined) {
|
|
2459
|
+
throw lastError ?? new Error("dashboard: no available port");
|
|
2460
|
+
}
|
|
2461
|
+
const boundPort = server.port ?? deps.config.port;
|
|
2462
|
+
const url = `http://${deps.config.host}:${boundPort}`;
|
|
2463
|
+
log(`[openteam] dashboard en ${url}`);
|
|
2464
|
+
const notify = async () => {
|
|
2465
|
+
if (clients.size === 0) {
|
|
2466
|
+
return;
|
|
2467
|
+
}
|
|
2468
|
+
const message = encoder.encode(sseMessage(await stateJson(deps)));
|
|
2469
|
+
for (const controller of clients) {
|
|
2470
|
+
try {
|
|
2471
|
+
controller.enqueue(message);
|
|
2472
|
+
} catch {
|
|
2473
|
+
clients.delete(controller);
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
};
|
|
2477
|
+
const close = () => {
|
|
2478
|
+
for (const controller of clients) {
|
|
2479
|
+
try {
|
|
2480
|
+
controller.close();
|
|
2481
|
+
} catch {}
|
|
2482
|
+
}
|
|
2483
|
+
clients.clear();
|
|
2484
|
+
server.stop(true);
|
|
2485
|
+
};
|
|
2486
|
+
return { url, port: boundPort, notify, close };
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
// src/web/snapshot.ts
|
|
2490
|
+
function createSnapshotReader(deps, paths, context) {
|
|
2491
|
+
return async () => {
|
|
2492
|
+
const [telemetryText, backlogText, agentFiles, lastCommit] = await Promise.all([
|
|
2493
|
+
deps.readText(paths.telemetryPath),
|
|
2494
|
+
deps.readText(paths.backlogPath),
|
|
2495
|
+
deps.listAgentFiles(paths.agentDir),
|
|
2496
|
+
deps.gitLastCommit?.() ?? Promise.resolve(undefined)
|
|
2497
|
+
]);
|
|
2498
|
+
const costRecords = parseCostRecordsJsonl(telemetryText ?? "");
|
|
2499
|
+
const team = agentFiles.map(parseAgentFile);
|
|
2500
|
+
const inputs = {
|
|
2501
|
+
generatedAt: deps.now(),
|
|
2502
|
+
costRecords,
|
|
2503
|
+
team,
|
|
2504
|
+
activity: context.activity()
|
|
2505
|
+
};
|
|
2506
|
+
if (backlogText !== undefined) {
|
|
2507
|
+
const backlog = {
|
|
2508
|
+
path: paths.backlogPath,
|
|
2509
|
+
items: parseBacklog(backlogText)
|
|
2510
|
+
};
|
|
2511
|
+
if (lastCommit !== undefined) {
|
|
2512
|
+
backlog.lastCommit = lastCommit;
|
|
2513
|
+
}
|
|
2514
|
+
inputs.backlog = backlog;
|
|
2515
|
+
}
|
|
2516
|
+
if (context.session !== undefined) {
|
|
2517
|
+
inputs.session = context.session;
|
|
2518
|
+
}
|
|
2519
|
+
if (context.recentRoutesLimit !== undefined) {
|
|
2520
|
+
inputs.recentRoutesLimit = context.recentRoutesLimit;
|
|
2521
|
+
}
|
|
2522
|
+
return inputs;
|
|
2523
|
+
};
|
|
2524
|
+
}
|
|
2525
|
+
|
|
2526
|
+
// src/web/watch.ts
|
|
2527
|
+
function watchSources(paths, onChange, deps, debounceMs = 250) {
|
|
2528
|
+
const setTimer = deps.setTimer ?? ((cb, ms) => setTimeout(cb, ms));
|
|
2529
|
+
const clearTimer = deps.clearTimer ?? ((handle) => clearTimeout(handle));
|
|
2530
|
+
const watchers = [];
|
|
2531
|
+
let pending;
|
|
2532
|
+
const trigger = () => {
|
|
2533
|
+
if (pending !== undefined) {
|
|
2534
|
+
clearTimer(pending);
|
|
2535
|
+
}
|
|
2536
|
+
pending = setTimer(() => {
|
|
2537
|
+
pending = undefined;
|
|
2538
|
+
onChange();
|
|
2539
|
+
}, debounceMs);
|
|
2540
|
+
};
|
|
2541
|
+
for (const path of paths) {
|
|
2542
|
+
try {
|
|
2543
|
+
watchers.push(deps.watch(path, trigger));
|
|
2544
|
+
} catch {}
|
|
2545
|
+
}
|
|
2546
|
+
return {
|
|
2547
|
+
close() {
|
|
2548
|
+
if (pending !== undefined) {
|
|
2549
|
+
clearTimer(pending);
|
|
2550
|
+
pending = undefined;
|
|
2551
|
+
}
|
|
2552
|
+
for (const watcher of watchers) {
|
|
2553
|
+
watcher.close();
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
};
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
// src/web/start.ts
|
|
2560
|
+
function startDashboard(deps) {
|
|
2561
|
+
const backlogPath = deps.backlogPath ?? DEFAULT_BACKLOG_PATH;
|
|
2562
|
+
const activity = createActivityBuffer(deps.config.recentRoutes);
|
|
2563
|
+
const readSnapshot = createSnapshotReader({
|
|
2564
|
+
readText: deps.readText,
|
|
2565
|
+
listAgentFiles: deps.listAgentFiles,
|
|
2566
|
+
now: deps.now,
|
|
2567
|
+
...deps.gitLastCommit !== undefined ? { gitLastCommit: deps.gitLastCommit } : {}
|
|
2568
|
+
}, {
|
|
2569
|
+
telemetryPath: deps.telemetryPath,
|
|
2570
|
+
backlogPath,
|
|
2571
|
+
agentDir: deps.agentDir
|
|
2572
|
+
}, {
|
|
2573
|
+
activity: () => activity.list(),
|
|
2574
|
+
recentRoutesLimit: deps.config.recentRoutes,
|
|
2575
|
+
...deps.session !== undefined ? { session: deps.session } : {}
|
|
2576
|
+
});
|
|
2577
|
+
const createServer = deps.serve ?? createDashboardServer;
|
|
2578
|
+
const server = createServer({
|
|
2579
|
+
readSnapshot,
|
|
2580
|
+
config: deps.config,
|
|
2581
|
+
...deps.log !== undefined ? { log: deps.log } : {}
|
|
2582
|
+
});
|
|
2583
|
+
const watchFn = deps.watch ?? ((path, listener) => fsWatch(path, { persistent: false }, listener));
|
|
2584
|
+
const watcher = watchSources([deps.telemetryPath, backlogPath, deps.agentDir], () => {
|
|
2585
|
+
server.notify();
|
|
2586
|
+
}, { watch: watchFn });
|
|
2587
|
+
return {
|
|
2588
|
+
url: server.url,
|
|
2589
|
+
port: server.port,
|
|
2590
|
+
pushActivity(entry) {
|
|
2591
|
+
activity.push(entry);
|
|
2592
|
+
server.notify();
|
|
2593
|
+
},
|
|
2594
|
+
close() {
|
|
2595
|
+
watcher.close();
|
|
2596
|
+
server.close();
|
|
2597
|
+
}
|
|
2598
|
+
};
|
|
2599
|
+
}
|
|
2600
|
+
|
|
1968
2601
|
// src/index.ts
|
|
1969
2602
|
function createShellExec($) {
|
|
1970
2603
|
return async (command, args) => {
|
|
@@ -2014,6 +2647,45 @@ function createTelemetrySink(rawOptions) {
|
|
|
2014
2647
|
function isMissingFile2(error) {
|
|
2015
2648
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
2016
2649
|
}
|
|
2650
|
+
async function readTextOptional(path) {
|
|
2651
|
+
try {
|
|
2652
|
+
return await readFile2(path, "utf8");
|
|
2653
|
+
} catch (error) {
|
|
2654
|
+
if (isMissingFile2(error)) {
|
|
2655
|
+
return;
|
|
2656
|
+
}
|
|
2657
|
+
throw error;
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
function createGitLastCommit(exec) {
|
|
2661
|
+
return async () => {
|
|
2662
|
+
const output = await exec("git", [
|
|
2663
|
+
"log",
|
|
2664
|
+
"-1",
|
|
2665
|
+
"--pretty=format:%h%x1f%s%x1f%cI"
|
|
2666
|
+
]);
|
|
2667
|
+
if (output.exitCode !== 0) {
|
|
2668
|
+
return;
|
|
2669
|
+
}
|
|
2670
|
+
const [hash, subject, at] = output.stdout.trim().split("\x1F");
|
|
2671
|
+
if (hash === undefined || subject === undefined || at === undefined) {
|
|
2672
|
+
return;
|
|
2673
|
+
}
|
|
2674
|
+
return { hash, subject, at };
|
|
2675
|
+
};
|
|
2676
|
+
}
|
|
2677
|
+
var DASHBOARD_EVENT_SUMMARIES = {
|
|
2678
|
+
"session.created": "Sesión creada",
|
|
2679
|
+
"session.idle": "Sesión en reposo",
|
|
2680
|
+
"session.error": "Error de sesión"
|
|
2681
|
+
};
|
|
2682
|
+
function activityFromEvent(event, now) {
|
|
2683
|
+
const summary = DASHBOARD_EVENT_SUMMARIES[event.type];
|
|
2684
|
+
if (summary === undefined) {
|
|
2685
|
+
return;
|
|
2686
|
+
}
|
|
2687
|
+
return { ts: now(), kind: "agent", summary };
|
|
2688
|
+
}
|
|
2017
2689
|
function createCliDeps(config, registry, telemetryPath) {
|
|
2018
2690
|
return {
|
|
2019
2691
|
loadConfig: async (path) => {
|
|
@@ -2089,6 +2761,26 @@ var server = async (ctx, rawOptions) => {
|
|
|
2089
2761
|
sink: createTelemetrySink(rawOptions)
|
|
2090
2762
|
});
|
|
2091
2763
|
const cliDeps = createCliDeps(config, registry, telemetryPath);
|
|
2764
|
+
let dashboard;
|
|
2765
|
+
if (config.dashboard.enabled) {
|
|
2766
|
+
try {
|
|
2767
|
+
dashboard = startDashboard({
|
|
2768
|
+
config: config.dashboard,
|
|
2769
|
+
telemetryPath,
|
|
2770
|
+
agentDir: dirname2(ORCHESTRATOR_AGENT_PATH),
|
|
2771
|
+
backlogPath: DEFAULT_BACKLOG_PATH,
|
|
2772
|
+
readText: readTextOptional,
|
|
2773
|
+
listAgentFiles: cliDeps.listAgentFiles,
|
|
2774
|
+
now: () => new Date().toISOString(),
|
|
2775
|
+
gitLastCommit: createGitLastCommit(createShellExec(ctx.$)),
|
|
2776
|
+
log: (message) => console.log(`[openteam] ${message}`)
|
|
2777
|
+
});
|
|
2778
|
+
console.log(`[openteam] dashboard en ${dashboard.url}`);
|
|
2779
|
+
} catch (error) {
|
|
2780
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2781
|
+
console.warn(`[openteam] no se pudo iniciar el dashboard: ${message}`);
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2092
2784
|
return {
|
|
2093
2785
|
...hooks,
|
|
2094
2786
|
tool: {
|
|
@@ -2098,6 +2790,12 @@ var server = async (ctx, rawOptions) => {
|
|
|
2098
2790
|
if (event.type === "session.created" || event.type === "session.idle") {
|
|
2099
2791
|
cache.refresh();
|
|
2100
2792
|
}
|
|
2793
|
+
if (dashboard !== undefined) {
|
|
2794
|
+
const entry = activityFromEvent(event, () => Date.now());
|
|
2795
|
+
if (entry !== undefined) {
|
|
2796
|
+
dashboard.pushActivity(entry);
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2101
2799
|
}
|
|
2102
2800
|
};
|
|
2103
2801
|
};
|
|
@@ -2113,5 +2811,6 @@ export {
|
|
|
2113
2811
|
isMissingFile2 as isMissingFile,
|
|
2114
2812
|
src_default as default,
|
|
2115
2813
|
createFileAppender,
|
|
2116
|
-
createCliDeps
|
|
2814
|
+
createCliDeps,
|
|
2815
|
+
activityFromEvent
|
|
2117
2816
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ToolDefinition } from "@opencode-ai/plugin";
|
|
2
2
|
import { type CliDeps } from "../commands/dispatch";
|
|
3
|
-
export type CommandAction = "show" | "set" | "auto" | "doctor" | "report" | "agents";
|
|
3
|
+
export type CommandAction = "show" | "set" | "auto" | "doctor" | "report" | "agents" | "dashboard";
|
|
4
4
|
/**
|
|
5
5
|
* Map a tool action (+ optional model) to the argv understood by {@link runCli}.
|
|
6
6
|
* Pure so it can be unit-tested without the opencode runtime.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commandTool.d.ts","sourceRoot":"","sources":["../../src/plugin/commandTool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,qBAAqB,CAAC;AAEhE,OAAO,EAAE,KAAK,OAAO,EAAU,MAAM,sBAAsB,CAAC;AAE5D,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,KAAK,GACL,MAAM,GACN,QAAQ,GACR,QAAQ,GACR,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"commandTool.d.ts","sourceRoot":"","sources":["../../src/plugin/commandTool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,qBAAqB,CAAC;AAEhE,OAAO,EAAE,KAAK,OAAO,EAAU,MAAM,sBAAsB,CAAC;AAE5D,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,KAAK,GACL,MAAM,GACN,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,WAAW,CAAC;AAEhB;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAmB3E;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,cAAc,CA0B/D"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ActivityEntry } from "../dashboard/types";
|
|
2
|
+
/**
|
|
3
|
+
* Buffer en memoria de actividad reciente (eventos de opencode y del loop). Mantiene
|
|
4
|
+
* como mucho `max` entradas, descartando las más antiguas. El estado vive aquí, en el
|
|
5
|
+
* driver, no en el core puro del dashboard.
|
|
6
|
+
*/
|
|
7
|
+
export type ActivityBuffer = {
|
|
8
|
+
push: (entry: ActivityEntry) => void;
|
|
9
|
+
list: () => ActivityEntry[];
|
|
10
|
+
};
|
|
11
|
+
export declare function createActivityBuffer(max?: number): ActivityBuffer;
|
|
12
|
+
//# sourceMappingURL=activity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activity.d.ts","sourceRoot":"","sources":["../../src/web/activity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IACrC,IAAI,EAAE,MAAM,aAAa,EAAE,CAAC;CAC7B,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,GAAG,SAAM,GAAG,cAAc,CAc9D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/web/paths.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,eAAO,MAAM,oBAAoB,kCAAkC,CAAC;AACpE,eAAO,MAAM,sBAAsB,oCAAoC,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { DashboardConfig } from "../config/schema";
|
|
2
|
+
import type { DashboardInputs } from "../dashboard/state";
|
|
3
|
+
export type DashboardServerDeps = {
|
|
4
|
+
/** Relee las fuentes y produce los inputs crudos del dashboard. */
|
|
5
|
+
readSnapshot: () => Promise<DashboardInputs>;
|
|
6
|
+
config: DashboardConfig;
|
|
7
|
+
/** Inyectable para tests; por defecto `Bun.serve`. */
|
|
8
|
+
serve?: typeof Bun.serve;
|
|
9
|
+
/** Logger (por defecto `console`). */
|
|
10
|
+
log?: (message: string) => void;
|
|
11
|
+
};
|
|
12
|
+
export type DashboardServer = {
|
|
13
|
+
url: string;
|
|
14
|
+
port: number;
|
|
15
|
+
/** Reevalúa el estado y lo empuja a los clientes SSE conectados. */
|
|
16
|
+
notify: () => Promise<void>;
|
|
17
|
+
close: () => void;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Arranca el servidor del dashboard con `Bun.serve` en loopback. Sirve `/` (HTML),
|
|
21
|
+
* `/api/state` (JSON), `/events` (SSE) y `/healthz`. Solo lectura: cualquier método
|
|
22
|
+
* ≠ GET → 405; ruta desconocida → 404. Con `autoPortFallback` prueba puertos
|
|
23
|
+
* sucesivos si el preferido está ocupado.
|
|
24
|
+
*/
|
|
25
|
+
export declare function createDashboardServer(deps: DashboardServerDeps): DashboardServer;
|
|
26
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/web/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAY1D,MAAM,MAAM,mBAAmB,GAAG;IAChC,mEAAmE;IACnE,YAAY,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAC;IAC7C,MAAM,EAAE,eAAe,CAAC;IACxB,sDAAsD;IACtD,KAAK,CAAC,EAAE,OAAO,GAAG,CAAC,KAAK,CAAC;IACzB,sCAAsC;IACtC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,CAAC;AAyBF;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,mBAAmB,GACxB,eAAe,CAsGjB"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { AgentFile } from "../commands/agents";
|
|
2
|
+
import type { DashboardInputs } from "../dashboard/state";
|
|
3
|
+
import type { ActivityEntry, CommitView } from "../dashboard/types";
|
|
4
|
+
/** Dependencias de I/O que el driver inyecta para leer las fuentes del dashboard. */
|
|
5
|
+
export type SnapshotDeps = {
|
|
6
|
+
/** Lee un fichero de texto; devuelve `undefined` si no existe (ENOENT). */
|
|
7
|
+
readText: (path: string) => Promise<string | undefined>;
|
|
8
|
+
/** Lista los ficheros `.md` de `.opencode/agent/`. */
|
|
9
|
+
listAgentFiles: (dir: string) => Promise<AgentFile[]>;
|
|
10
|
+
/** Reloj: ISO-8601. Fuera del core puro. */
|
|
11
|
+
now: () => string;
|
|
12
|
+
/** Último commit (git); opcional. */
|
|
13
|
+
gitLastCommit?: () => Promise<CommitView | undefined>;
|
|
14
|
+
};
|
|
15
|
+
export type SnapshotPaths = {
|
|
16
|
+
telemetryPath: string;
|
|
17
|
+
backlogPath: string;
|
|
18
|
+
agentDir: string;
|
|
19
|
+
};
|
|
20
|
+
export type SnapshotContext = {
|
|
21
|
+
/** Actividad viva (buffer del driver). */
|
|
22
|
+
activity: () => ActivityEntry[];
|
|
23
|
+
session?: {
|
|
24
|
+
id?: string;
|
|
25
|
+
startedAt?: string;
|
|
26
|
+
};
|
|
27
|
+
recentRoutesLimit?: number;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Crea un lector de snapshot: cada llamada relee las fuentes en disco y devuelve
|
|
31
|
+
* los `DashboardInputs` crudos que `buildDashboardState` (puro) compondrá.
|
|
32
|
+
*/
|
|
33
|
+
export declare function createSnapshotReader(deps: SnapshotDeps, paths: SnapshotPaths, context: SnapshotContext): () => Promise<DashboardInputs>;
|
|
34
|
+
//# sourceMappingURL=snapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../src/web/snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAa,MAAM,oBAAoB,CAAC;AAG/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGpE,qFAAqF;AACrF,MAAM,MAAM,YAAY,GAAG;IACzB,2EAA2E;IAC3E,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACxD,sDAAsD;IACtD,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IACtD,4CAA4C;IAC5C,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,qCAAqC;IACrC,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAAC;CACvD,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,aAAa,EAAE,CAAC;IAChC,OAAO,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,YAAY,EAClB,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,eAAe,GACvB,MAAM,OAAO,CAAC,eAAe,CAAC,CAwChC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { watch as fsWatch } from "node:fs";
|
|
2
|
+
import type { AgentFile } from "../commands/agents";
|
|
3
|
+
import type { DashboardConfig } from "../config/schema";
|
|
4
|
+
import type { ActivityEntry, CommitView } from "../dashboard/types";
|
|
5
|
+
export type DashboardRuntimeDeps = {
|
|
6
|
+
config: DashboardConfig;
|
|
7
|
+
telemetryPath: string;
|
|
8
|
+
agentDir: string;
|
|
9
|
+
backlogPath?: string;
|
|
10
|
+
readText: (path: string) => Promise<string | undefined>;
|
|
11
|
+
listAgentFiles: (dir: string) => Promise<AgentFile[]>;
|
|
12
|
+
now: () => string;
|
|
13
|
+
gitLastCommit?: () => Promise<CommitView | undefined>;
|
|
14
|
+
session?: {
|
|
15
|
+
id?: string;
|
|
16
|
+
startedAt?: string;
|
|
17
|
+
};
|
|
18
|
+
/** Inyectables para tests. */
|
|
19
|
+
serve?: typeof import("./server").createDashboardServer;
|
|
20
|
+
watch?: (path: string, listener: () => void) => ReturnType<typeof fsWatch>;
|
|
21
|
+
log?: (message: string) => void;
|
|
22
|
+
};
|
|
23
|
+
export type DashboardRuntime = {
|
|
24
|
+
url: string;
|
|
25
|
+
port: number;
|
|
26
|
+
/** Registra actividad en vivo y la empuja a los clientes SSE. */
|
|
27
|
+
pushActivity: (entry: ActivityEntry) => void;
|
|
28
|
+
close: () => void;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Compone el runtime del dashboard: buffer de actividad + lector de snapshot +
|
|
32
|
+
* servidor `Bun.serve` + watcher de ficheros. El plugin lo arranca cuando
|
|
33
|
+
* `config.dashboard.enabled` y lo cierra al terminar la sesión.
|
|
34
|
+
*/
|
|
35
|
+
export declare function startDashboard(deps: DashboardRuntimeDeps): DashboardRuntime;
|
|
36
|
+
//# sourceMappingURL=start.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/web/start.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,OAAO,EAAE,MAAM,SAAS,CAAC;AAE3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAOpE,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,eAAe,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACxD,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IACtD,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAAC;IACtD,OAAO,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,8BAA8B;IAC9B,KAAK,CAAC,EAAE,cAAc,UAAU,EAAE,qBAAqB,CAAC;IACxD,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,KAAK,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;IAC3E,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,YAAY,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC7C,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,oBAAoB,GAAG,gBAAgB,CAuD3E"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { FSWatcher } from "node:fs";
|
|
2
|
+
/** Firma mínima de `fs.watch` que necesitamos (inyectable en tests). */
|
|
3
|
+
export type WatchFn = (path: string, listener: () => void) => FSWatcher;
|
|
4
|
+
export type WatchDeps = {
|
|
5
|
+
watch: WatchFn;
|
|
6
|
+
/** Programa un callback tras `ms`; por defecto `setTimeout`. */
|
|
7
|
+
setTimer?: (callback: () => void, ms: number) => unknown;
|
|
8
|
+
clearTimer?: (handle: unknown) => void;
|
|
9
|
+
};
|
|
10
|
+
export type FileWatcher = {
|
|
11
|
+
close: () => void;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Observa varias rutas y, con un debounce, invoca `onChange` cuando alguna cambia.
|
|
15
|
+
* Tolera rutas que aún no existen (se ignora el error de `watch`). El I/O real
|
|
16
|
+
* (`fs.watch`) se inyecta para poder testear el debounce sin tocar disco.
|
|
17
|
+
*/
|
|
18
|
+
export declare function watchSources(paths: readonly string[], onChange: () => void, deps: WatchDeps, debounceMs?: number): FileWatcher;
|
|
19
|
+
//# sourceMappingURL=watch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../src/web/watch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,wEAAwE;AACxE,MAAM,MAAM,OAAO,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,KAAK,SAAS,CAAC;AAExE,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,gEAAgE;IAChE,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;IACzD,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,QAAQ,EAAE,MAAM,IAAI,EACpB,IAAI,EAAE,SAAS,EACf,UAAU,SAAM,GACf,WAAW,CAqCb"}
|