@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 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;AAYlB,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;AAuNF,wBAAsB,MAAM,CAC1B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,SAAS,CAAC,CA0DpB"}
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"}
@@ -42,6 +42,7 @@ export type InitAnswers = {
42
42
  routerMode: RouterMode;
43
43
  privacyMode: PrivacyMode;
44
44
  yolo: boolean;
45
+ dashboard: boolean;
45
46
  };
46
47
  export type OpencodeProviderConfig = {
47
48
  npm: string;
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
@@ -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;AAW7D,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;iBAM7B,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA0D/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"}
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;
@@ -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;AAEtD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAgBnD,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;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,eAAe,EACzB,aAAa,EAAE,MAAM,GACpB,OAAO,CA4DT;AAED,eAAO,MAAM,MAAM,EAAE,MA8BpB,CAAC;AAEF,QAAA,MAAM,MAAM,EAAE,YAGb,CAAC;AAEF,eAAe,MAAM,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 y agents (LLM por agente: local/frontier + suscripción).",
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(["show", "set", "auto", "doctor", "report", "agents"]).describe("Acción a ejecutar"),
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
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;AAEb;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAiB3E;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,cAAc,CAkB/D"}
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,4 @@
1
+ /** Rutas por defecto de las fuentes que alimentan el dashboard. */
2
+ export declare const DEFAULT_BACKLOG_PATH = ".opencode/openteam-backlog.md";
3
+ export declare const DEFAULT_DECISIONS_PATH = ".opencode/openteam-decisions.md";
4
+ //# sourceMappingURL=paths.d.ts.map
@@ -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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jmanuelcorral/openteam",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "Plugin opencode de routing coste-consciente local-first con orquestación Squad.",
5
5
  "license": "MIT",
6
6
  "author": "Jose Manuel Corral",