@jmanuelcorral/openteam 0.1.15 → 0.1.17
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 +51 -1
- 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/config/schema.d.ts +29 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/dashboard/backlog.d.ts +9 -0
- package/dist/dashboard/backlog.d.ts.map +1 -0
- package/dist/dashboard/render.d.ts +14 -0
- package/dist/dashboard/render.d.ts.map +1 -0
- package/dist/dashboard/state.d.ts +33 -0
- package/dist/dashboard/state.d.ts.map +1 -0
- package/dist/dashboard/types.d.ts +71 -0
- package/dist/dashboard/types.d.ts.map +1 -0
- 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/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
|
};
|