@jmanuelcorral/openteam 0.1.21 → 0.1.22
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 +29 -31
- package/dist/cli/dashboardServe.d.ts +15 -8
- package/dist/cli/dashboardServe.d.ts.map +1 -1
- package/dist/cli.js +889 -276
- package/dist/commands/dashboard.d.ts +4 -4
- package/dist/commands/dashboard.d.ts.map +1 -1
- package/dist/commands/orchestratorAgent.d.ts.map +1 -1
- package/dist/contract/opencode.d.ts +1 -1
- package/dist/contract/opencode.d.ts.map +1 -1
- package/dist/dashboard/render.d.ts.map +1 -1
- package/dist/dashboard/state.d.ts +14 -6
- package/dist/dashboard/state.d.ts.map +1 -1
- package/dist/dashboard/types.d.ts +14 -3
- package/dist/dashboard/types.d.ts.map +1 -1
- package/dist/index.d.ts +8 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +476 -802
- package/dist/orchestrator/coordinator.d.ts +14 -2
- package/dist/orchestrator/coordinator.d.ts.map +1 -1
- package/dist/plugin/capture.d.ts +46 -0
- package/dist/plugin/capture.d.ts.map +1 -0
- package/dist/plugin/hooks.d.ts +2 -2
- package/dist/plugin/hooks.d.ts.map +1 -1
- package/dist/plugin/toolcalls.d.ts +21 -0
- package/dist/plugin/toolcalls.d.ts.map +1 -0
- package/dist/telemetry/aggregate.d.ts +89 -0
- package/dist/telemetry/aggregate.d.ts.map +1 -0
- package/dist/telemetry/decisions.d.ts +25 -0
- package/dist/telemetry/decisions.d.ts.map +1 -0
- package/dist/telemetry/eventLog.d.ts +63 -0
- package/dist/telemetry/eventLog.d.ts.map +1 -0
- package/dist/telemetry/events.d.ts +217 -0
- package/dist/telemetry/events.d.ts.map +1 -0
- package/dist/web/snapshot.d.ts +13 -7
- package/dist/web/snapshot.d.ts.map +1 -1
- package/dist/web/start.d.ts +10 -7
- package/dist/web/start.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/web/activity.d.ts +0 -12
- package/dist/web/activity.d.ts.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -2,31 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { execFile } from "node:child_process";
|
|
5
|
-
import { access, mkdir as mkdir2, readdir, readFile
|
|
5
|
+
import { access, mkdir as mkdir2, readdir, readFile, writeFile as writeFile2 } from "node:fs/promises";
|
|
6
6
|
import { dirname as dirname2, join as join2 } from "node:path";
|
|
7
7
|
import { promisify } from "node:util";
|
|
8
8
|
|
|
9
9
|
// src/web/start.ts
|
|
10
10
|
import { watch as fsWatch } from "node:fs";
|
|
11
11
|
|
|
12
|
-
// src/web/activity.ts
|
|
13
|
-
function createActivityBuffer(max = 100) {
|
|
14
|
-
const entries = [];
|
|
15
|
-
return {
|
|
16
|
-
push(entry) {
|
|
17
|
-
entries.push(entry);
|
|
18
|
-
if (entries.length > max) {
|
|
19
|
-
entries.splice(0, entries.length - max);
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
list() {
|
|
23
|
-
return [...entries];
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
12
|
// src/web/paths.ts
|
|
29
13
|
var DEFAULT_BACKLOG_PATH = ".opencode/openteam-backlog.md";
|
|
14
|
+
var DEFAULT_DECISIONS_PATH = ".opencode/openteam-decisions.md";
|
|
30
15
|
|
|
31
16
|
// src/web/server.ts
|
|
32
17
|
import {
|
|
@@ -49,6 +34,12 @@ function usd(value) {
|
|
|
49
34
|
function pct(value) {
|
|
50
35
|
return `${value.toFixed(2)}%`;
|
|
51
36
|
}
|
|
37
|
+
function ms(value) {
|
|
38
|
+
return value >= 1000 ? `${(value / 1000).toFixed(2)}s` : `${Math.round(value)}ms`;
|
|
39
|
+
}
|
|
40
|
+
function avgMs(stats) {
|
|
41
|
+
return stats.count === 0 ? "—" : ms(stats.totalDurationMs / stats.count);
|
|
42
|
+
}
|
|
52
43
|
function agentModel(agent) {
|
|
53
44
|
return agent.model === undefined ? "hereda default" : `${agent.model.providerID}/${agent.model.modelID}`;
|
|
54
45
|
}
|
|
@@ -169,6 +160,101 @@ function activityPanel(activity) {
|
|
|
169
160
|
"</section>"
|
|
170
161
|
].join("");
|
|
171
162
|
}
|
|
163
|
+
function totalsPanel(totals) {
|
|
164
|
+
const t = totals.toolcalls;
|
|
165
|
+
return [
|
|
166
|
+
'<section class="panel" id="panel-totals">',
|
|
167
|
+
"<h2>Telemetría real (todas las sesiones)</h2>",
|
|
168
|
+
'<div class="grid">',
|
|
169
|
+
`<div class="stat"><span class="k">Sesiones</span><span class="v">${totals.sessions}</span></div>`,
|
|
170
|
+
`<div class="stat"><span class="k">Mensajes</span><span class="v">${totals.messageCount}</span></div>`,
|
|
171
|
+
`<div class="stat"><span class="k">Coste real</span><span class="v">${usd(totals.realCostUSD)}</span></div>`,
|
|
172
|
+
`<div class="stat"><span class="k">Ahorro est.</span><span class="v good">${usd(totals.estimatedSavingsUSD)}</span></div>`,
|
|
173
|
+
`<div class="stat"><span class="k">Tokens in</span><span class="v">${totals.tokensIn}</span></div>`,
|
|
174
|
+
`<div class="stat"><span class="k">Tokens out</span><span class="v">${totals.tokensOut}</span></div>`,
|
|
175
|
+
`<div class="stat"><span class="k">Reasoning</span><span class="v">${totals.tokensReasoning}</span></div>`,
|
|
176
|
+
`<div class="stat"><span class="k">Cache r/w</span><span class="v">${totals.tokensCacheRead}/${totals.tokensCacheWrite}</span></div>`,
|
|
177
|
+
`<div class="stat"><span class="k">Toolcalls</span><span class="v">${t.count} <span class="good">${t.ok}✓</span> <span class="frontier">${t.failed}✗</span></span></div>`,
|
|
178
|
+
`<div class="stat"><span class="k">Dur. media tool</span><span class="v">${avgMs(t)}</span></div>`,
|
|
179
|
+
"</div>",
|
|
180
|
+
"</section>"
|
|
181
|
+
].join("");
|
|
182
|
+
}
|
|
183
|
+
function sessionRow(session) {
|
|
184
|
+
const models = session.models.map(escapeHtml).join("<br>") || "—";
|
|
185
|
+
return [
|
|
186
|
+
"<tr>",
|
|
187
|
+
`<td class="hash">${escapeHtml(session.sessionID)}</td>`,
|
|
188
|
+
`<td>${models}</td>`,
|
|
189
|
+
`<td><span class="local">${session.localCount}</span>/<span class="frontier">${session.frontierCount}</span></td>`,
|
|
190
|
+
`<td>${usd(session.realCostUSD)}</td>`,
|
|
191
|
+
`<td>${session.tokensIn}/${session.tokensOut}</td>`,
|
|
192
|
+
`<td>${session.toolcalls.count} (${avgMs(session.toolcalls)})</td>`,
|
|
193
|
+
"</tr>"
|
|
194
|
+
].join("");
|
|
195
|
+
}
|
|
196
|
+
function sessionsPanel(sessions) {
|
|
197
|
+
if (sessions.length === 0) {
|
|
198
|
+
return [
|
|
199
|
+
'<section class="panel" id="panel-sessions">',
|
|
200
|
+
"<h2>Sesiones</h2>",
|
|
201
|
+
'<p class="muted">Sin sesiones registradas todavía.</p>',
|
|
202
|
+
"</section>"
|
|
203
|
+
].join("");
|
|
204
|
+
}
|
|
205
|
+
const rows = sessions.map(sessionRow).join("");
|
|
206
|
+
return [
|
|
207
|
+
'<section class="panel" id="panel-sessions">',
|
|
208
|
+
"<h2>Sesiones</h2>",
|
|
209
|
+
"<table><thead><tr><th>Sesión</th><th>Modelos</th><th>L/F</th><th>Coste</th><th>Tok in/out</th><th>Tools</th></tr></thead>",
|
|
210
|
+
`<tbody>${rows}</tbody></table>`,
|
|
211
|
+
"</section>"
|
|
212
|
+
].join("");
|
|
213
|
+
}
|
|
214
|
+
function decisionRow(decision) {
|
|
215
|
+
const who = decision.agent === undefined ? "" : `<span class="who">${escapeHtml(decision.agent)}</span> `;
|
|
216
|
+
const tags = decision.tags === undefined || decision.tags.length === 0 ? "" : ` ${decision.tags.map((tag) => `<span class="tag">#${escapeHtml(tag)}</span>`).join(" ")}`;
|
|
217
|
+
return `<li class="decision"><span class="kind">decision</span> ${who}${escapeHtml(decision.summary)}${tags}</li>`;
|
|
218
|
+
}
|
|
219
|
+
function decisionsPanel(decisions) {
|
|
220
|
+
if (decisions.length === 0) {
|
|
221
|
+
return [
|
|
222
|
+
'<section class="panel" id="panel-decisions">',
|
|
223
|
+
"<h2>Decisiones</h2>",
|
|
224
|
+
'<p class="muted">Sin decisiones registradas (el scribe escribe en <code>openteam-decisions.md</code>).</p>',
|
|
225
|
+
"</section>"
|
|
226
|
+
].join("");
|
|
227
|
+
}
|
|
228
|
+
const rows = decisions.map(decisionRow).join("");
|
|
229
|
+
return [
|
|
230
|
+
'<section class="panel" id="panel-decisions">',
|
|
231
|
+
"<h2>Decisiones</h2>",
|
|
232
|
+
`<ul class="timeline">${rows}</ul>`,
|
|
233
|
+
"</section>"
|
|
234
|
+
].join("");
|
|
235
|
+
}
|
|
236
|
+
function meetingRow(meeting) {
|
|
237
|
+
const roles = meeting.roles.map(escapeHtml).join(", ");
|
|
238
|
+
const purpose = meeting.purpose === undefined ? "" : `: ${escapeHtml(meeting.purpose)}`;
|
|
239
|
+
return `<li class="agent"><span class="kind">reunión</span> <span class="who">${roles}</span>${purpose}</li>`;
|
|
240
|
+
}
|
|
241
|
+
function meetingsPanel(meetings) {
|
|
242
|
+
if (meetings.length === 0) {
|
|
243
|
+
return [
|
|
244
|
+
'<section class="panel" id="panel-meetings">',
|
|
245
|
+
"<h2>Reuniones</h2>",
|
|
246
|
+
'<p class="muted">Sin reuniones multi-agente todavía.</p>',
|
|
247
|
+
"</section>"
|
|
248
|
+
].join("");
|
|
249
|
+
}
|
|
250
|
+
const rows = meetings.map(meetingRow).join("");
|
|
251
|
+
return [
|
|
252
|
+
'<section class="panel" id="panel-meetings">',
|
|
253
|
+
"<h2>Reuniones</h2>",
|
|
254
|
+
`<ul class="timeline">${rows}</ul>`,
|
|
255
|
+
"</section>"
|
|
256
|
+
].join("");
|
|
257
|
+
}
|
|
172
258
|
var STYLE = `
|
|
173
259
|
:root{color-scheme:dark;--bg:#0f1115;--panel:#171a21;--fg:#e6e8eb;--muted:#8b929c;--good:#3fb950;--local:#58a6ff;--frontier:#d29922;--line:#262b34}
|
|
174
260
|
*{box-sizing:border-box}
|
|
@@ -198,6 +284,7 @@ ul.items li,ul.timeline li{padding:4px 0;border-bottom:1px solid var(--line)}
|
|
|
198
284
|
ul.items li.done{color:var(--muted)}
|
|
199
285
|
ul.items .box{font-family:monospace}
|
|
200
286
|
.who{color:var(--local)}
|
|
287
|
+
.tag{color:var(--frontier);font-size:12px}
|
|
201
288
|
.kind{display:inline-block;min-width:66px;color:var(--muted);font-size:12px}
|
|
202
289
|
code{font-family:ui-monospace,Consolas,monospace;background:#0b0d11;padding:1px 5px;border-radius:4px}
|
|
203
290
|
`;
|
|
@@ -224,10 +311,14 @@ function renderDashboardHtml(state, options = {}) {
|
|
|
224
311
|
const refreshMs = options.refreshMs ?? 2000;
|
|
225
312
|
const body = [
|
|
226
313
|
summaryPanel(state.cost),
|
|
314
|
+
totalsPanel(state.totals),
|
|
315
|
+
sessionsPanel(state.sessions),
|
|
227
316
|
loopPanel(state.loop),
|
|
228
317
|
teamPanel(state.team),
|
|
229
318
|
tierPanel(state.cost),
|
|
230
319
|
routesPanel(state.recentRoutes),
|
|
320
|
+
decisionsPanel(state.decisions),
|
|
321
|
+
meetingsPanel(state.meetings),
|
|
231
322
|
activityPanel(state.activity)
|
|
232
323
|
].join("");
|
|
233
324
|
const session = state.session.id === undefined ? "" : ` · sesión ${escapeHtml(state.session.id)}`;
|
|
@@ -297,31 +388,612 @@ function summarizeCostRecords(records) {
|
|
|
297
388
|
tier.estimatedUSD += record.estimatedCostUSD;
|
|
298
389
|
tier.savingsUSD += record.estimatedSavingsUSD;
|
|
299
390
|
}
|
|
300
|
-
report.savingsPct = report.totalBaselineUSD > 0 ? report.totalSavingsUSD / report.totalBaselineUSD * 100 : 0;
|
|
301
|
-
return report;
|
|
391
|
+
report.savingsPct = report.totalBaselineUSD > 0 ? report.totalSavingsUSD / report.totalBaselineUSD * 100 : 0;
|
|
392
|
+
return report;
|
|
393
|
+
}
|
|
394
|
+
function usd2(value) {
|
|
395
|
+
return `$${value.toFixed(5)}`;
|
|
396
|
+
}
|
|
397
|
+
function renderReport(report) {
|
|
398
|
+
if (report.count === 0) {
|
|
399
|
+
return "openteam report: sin registros de telemetría todavía.";
|
|
400
|
+
}
|
|
401
|
+
const lines = [
|
|
402
|
+
"openteam report:",
|
|
403
|
+
` decisiones: ${report.count} (local ${report.localCount} · frontier ${report.frontierCount})`,
|
|
404
|
+
` coste real: ${usd2(report.totalEstimatedUSD)}`,
|
|
405
|
+
` baseline: ${usd2(report.totalBaselineUSD)}`,
|
|
406
|
+
` ahorro: ${usd2(report.totalSavingsUSD)} (${report.savingsPct.toFixed(2)}%)`,
|
|
407
|
+
` tokens: in ${report.tokensIn} · out ${report.tokensOut}`,
|
|
408
|
+
" por tier:"
|
|
409
|
+
];
|
|
410
|
+
for (const tier of TIERS2) {
|
|
411
|
+
const summary = report.byTier[tier];
|
|
412
|
+
lines.push(` ${tier.padEnd(9)} ${summary.count} · coste ${usd2(summary.estimatedUSD)} · ahorro ${usd2(summary.savingsUSD)}`);
|
|
413
|
+
}
|
|
414
|
+
return lines.join(`
|
|
415
|
+
`);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// src/telemetry/aggregate.ts
|
|
419
|
+
function emptyToolcallStats() {
|
|
420
|
+
return { count: 0, ok: 0, failed: 0, totalDurationMs: 0, byTool: {} };
|
|
421
|
+
}
|
|
422
|
+
function emptySession(sessionID, ts) {
|
|
423
|
+
return {
|
|
424
|
+
sessionID,
|
|
425
|
+
firstTs: ts,
|
|
426
|
+
lastTs: ts,
|
|
427
|
+
routeCount: 0,
|
|
428
|
+
localCount: 0,
|
|
429
|
+
frontierCount: 0,
|
|
430
|
+
estimatedCostUSD: 0,
|
|
431
|
+
estimatedSavingsUSD: 0,
|
|
432
|
+
realCostUSD: 0,
|
|
433
|
+
messageCount: 0,
|
|
434
|
+
tokensIn: 0,
|
|
435
|
+
tokensOut: 0,
|
|
436
|
+
tokensReasoning: 0,
|
|
437
|
+
tokensCacheRead: 0,
|
|
438
|
+
tokensCacheWrite: 0,
|
|
439
|
+
toolcalls: emptyToolcallStats(),
|
|
440
|
+
models: [],
|
|
441
|
+
agents: []
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
function pushUnique(list, value) {
|
|
445
|
+
if (value !== undefined && !list.includes(value)) {
|
|
446
|
+
list.push(value);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
function applyRoute(session, event) {
|
|
450
|
+
session.routeCount += 1;
|
|
451
|
+
if (event.routeKind === "local") {
|
|
452
|
+
session.localCount += 1;
|
|
453
|
+
} else {
|
|
454
|
+
session.frontierCount += 1;
|
|
455
|
+
}
|
|
456
|
+
session.estimatedCostUSD += event.estimatedCostUSD;
|
|
457
|
+
session.estimatedSavingsUSD += event.estimatedSavingsUSD;
|
|
458
|
+
pushUnique(session.models, `${event.selected.providerID}/${event.selected.modelID}`);
|
|
459
|
+
pushUnique(session.agents, event.agent);
|
|
460
|
+
}
|
|
461
|
+
function applyMessage(session, event) {
|
|
462
|
+
session.messageCount += 1;
|
|
463
|
+
session.realCostUSD += event.costUSD;
|
|
464
|
+
session.tokensIn += event.tokensIn;
|
|
465
|
+
session.tokensOut += event.tokensOut;
|
|
466
|
+
session.tokensReasoning += event.tokensReasoning;
|
|
467
|
+
session.tokensCacheRead += event.tokensCacheRead;
|
|
468
|
+
session.tokensCacheWrite += event.tokensCacheWrite;
|
|
469
|
+
pushUnique(session.models, `${event.providerID}/${event.modelID}`);
|
|
470
|
+
pushUnique(session.agents, event.agent);
|
|
471
|
+
}
|
|
472
|
+
function applyToolUsage(stats, event) {
|
|
473
|
+
stats.count += 1;
|
|
474
|
+
stats.totalDurationMs += event.durationMs;
|
|
475
|
+
if (event.ok) {
|
|
476
|
+
stats.ok += 1;
|
|
477
|
+
} else {
|
|
478
|
+
stats.failed += 1;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
function applyToolcall(session, event) {
|
|
482
|
+
applyToolUsage(session.toolcalls, event);
|
|
483
|
+
const byTool = session.toolcalls.byTool[event.tool] ?? {
|
|
484
|
+
count: 0,
|
|
485
|
+
ok: 0,
|
|
486
|
+
failed: 0,
|
|
487
|
+
totalDurationMs: 0
|
|
488
|
+
};
|
|
489
|
+
applyToolUsage(byTool, event);
|
|
490
|
+
session.toolcalls.byTool[event.tool] = byTool;
|
|
491
|
+
pushUnique(session.agents, event.agent);
|
|
492
|
+
}
|
|
493
|
+
function mergeTool(target, source) {
|
|
494
|
+
target.count += source.count;
|
|
495
|
+
target.ok += source.ok;
|
|
496
|
+
target.failed += source.failed;
|
|
497
|
+
target.totalDurationMs += source.totalDurationMs;
|
|
498
|
+
}
|
|
499
|
+
function accumulateTotals(totals, session) {
|
|
500
|
+
totals.sessions += 1;
|
|
501
|
+
totals.routeCount += session.routeCount;
|
|
502
|
+
totals.localCount += session.localCount;
|
|
503
|
+
totals.frontierCount += session.frontierCount;
|
|
504
|
+
totals.estimatedCostUSD += session.estimatedCostUSD;
|
|
505
|
+
totals.estimatedSavingsUSD += session.estimatedSavingsUSD;
|
|
506
|
+
totals.realCostUSD += session.realCostUSD;
|
|
507
|
+
totals.messageCount += session.messageCount;
|
|
508
|
+
totals.tokensIn += session.tokensIn;
|
|
509
|
+
totals.tokensOut += session.tokensOut;
|
|
510
|
+
totals.tokensReasoning += session.tokensReasoning;
|
|
511
|
+
totals.tokensCacheRead += session.tokensCacheRead;
|
|
512
|
+
totals.tokensCacheWrite += session.tokensCacheWrite;
|
|
513
|
+
mergeTool(totals.toolcalls, session.toolcalls);
|
|
514
|
+
for (const [tool, usage] of Object.entries(session.toolcalls.byTool)) {
|
|
515
|
+
const target = totals.toolcalls.byTool[tool] ?? {
|
|
516
|
+
count: 0,
|
|
517
|
+
ok: 0,
|
|
518
|
+
failed: 0,
|
|
519
|
+
totalDurationMs: 0
|
|
520
|
+
};
|
|
521
|
+
mergeTool(target, usage);
|
|
522
|
+
totals.toolcalls.byTool[tool] = target;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
function toDecision(event) {
|
|
526
|
+
const view = {
|
|
527
|
+
ts: event.ts,
|
|
528
|
+
sessionID: event.sessionID,
|
|
529
|
+
summary: event.summary
|
|
530
|
+
};
|
|
531
|
+
if (event.agent !== undefined) {
|
|
532
|
+
view.agent = event.agent;
|
|
533
|
+
}
|
|
534
|
+
if (event.tags !== undefined) {
|
|
535
|
+
view.tags = [...event.tags];
|
|
536
|
+
}
|
|
537
|
+
return view;
|
|
538
|
+
}
|
|
539
|
+
function toMeeting(event) {
|
|
540
|
+
const view = {
|
|
541
|
+
ts: event.ts,
|
|
542
|
+
sessionID: event.sessionID,
|
|
543
|
+
batchID: event.batchID,
|
|
544
|
+
roles: [...event.roles]
|
|
545
|
+
};
|
|
546
|
+
if (event.purpose !== undefined) {
|
|
547
|
+
view.purpose = event.purpose;
|
|
548
|
+
}
|
|
549
|
+
if (event.decisionIDs !== undefined) {
|
|
550
|
+
view.decisionIDs = [...event.decisionIDs];
|
|
551
|
+
}
|
|
552
|
+
return view;
|
|
553
|
+
}
|
|
554
|
+
function toActivity(event) {
|
|
555
|
+
const view = {
|
|
556
|
+
ts: event.ts,
|
|
557
|
+
sessionID: event.sessionID,
|
|
558
|
+
kind: event.kind,
|
|
559
|
+
summary: event.summary
|
|
560
|
+
};
|
|
561
|
+
if (event.agent !== undefined) {
|
|
562
|
+
view.agent = event.agent;
|
|
563
|
+
}
|
|
564
|
+
return view;
|
|
565
|
+
}
|
|
566
|
+
function byTsDesc(list) {
|
|
567
|
+
return [...list].sort((a, b) => b.ts - a.ts);
|
|
568
|
+
}
|
|
569
|
+
function aggregateEvents(events) {
|
|
570
|
+
const sessions = new Map;
|
|
571
|
+
const decisions = [];
|
|
572
|
+
const meetings = [];
|
|
573
|
+
const activity = [];
|
|
574
|
+
const sessionFor = (sessionID, ts) => {
|
|
575
|
+
const existing = sessions.get(sessionID);
|
|
576
|
+
if (existing === undefined) {
|
|
577
|
+
const created = emptySession(sessionID, ts);
|
|
578
|
+
sessions.set(sessionID, created);
|
|
579
|
+
return created;
|
|
580
|
+
}
|
|
581
|
+
existing.firstTs = Math.min(existing.firstTs, ts);
|
|
582
|
+
existing.lastTs = Math.max(existing.lastTs, ts);
|
|
583
|
+
return existing;
|
|
584
|
+
};
|
|
585
|
+
for (const event of events) {
|
|
586
|
+
switch (event.type) {
|
|
587
|
+
case "route":
|
|
588
|
+
applyRoute(sessionFor(event.sessionID, event.ts), event);
|
|
589
|
+
break;
|
|
590
|
+
case "message":
|
|
591
|
+
applyMessage(sessionFor(event.sessionID, event.ts), event);
|
|
592
|
+
break;
|
|
593
|
+
case "toolcall":
|
|
594
|
+
applyToolcall(sessionFor(event.sessionID, event.ts), event);
|
|
595
|
+
break;
|
|
596
|
+
case "decision":
|
|
597
|
+
decisions.push(toDecision(event));
|
|
598
|
+
break;
|
|
599
|
+
case "meeting":
|
|
600
|
+
meetings.push(toMeeting(event));
|
|
601
|
+
break;
|
|
602
|
+
case "activity":
|
|
603
|
+
activity.push(toActivity(event));
|
|
604
|
+
break;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
const totals = {
|
|
608
|
+
sessions: 0,
|
|
609
|
+
routeCount: 0,
|
|
610
|
+
localCount: 0,
|
|
611
|
+
frontierCount: 0,
|
|
612
|
+
estimatedCostUSD: 0,
|
|
613
|
+
estimatedSavingsUSD: 0,
|
|
614
|
+
realCostUSD: 0,
|
|
615
|
+
messageCount: 0,
|
|
616
|
+
tokensIn: 0,
|
|
617
|
+
tokensOut: 0,
|
|
618
|
+
tokensReasoning: 0,
|
|
619
|
+
tokensCacheRead: 0,
|
|
620
|
+
tokensCacheWrite: 0,
|
|
621
|
+
toolcalls: emptyToolcallStats()
|
|
622
|
+
};
|
|
623
|
+
const summaries = [...sessions.values()].sort((a, b) => b.lastTs - a.lastTs);
|
|
624
|
+
for (const summary of summaries) {
|
|
625
|
+
summary.models.sort();
|
|
626
|
+
summary.agents.sort();
|
|
627
|
+
accumulateTotals(totals, summary);
|
|
628
|
+
}
|
|
629
|
+
return {
|
|
630
|
+
sessions: summaries,
|
|
631
|
+
totals,
|
|
632
|
+
decisions: byTsDesc(decisions),
|
|
633
|
+
meetings: byTsDesc(meetings),
|
|
634
|
+
activity: byTsDesc(activity)
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// src/telemetry/events.ts
|
|
639
|
+
import { z as z3 } from "zod";
|
|
640
|
+
|
|
641
|
+
// src/capabilities/types.ts
|
|
642
|
+
import { z } from "zod";
|
|
643
|
+
var CapabilityTierSchema = z.union([
|
|
644
|
+
z.literal(0),
|
|
645
|
+
z.literal(1),
|
|
646
|
+
z.literal(2),
|
|
647
|
+
z.literal(3),
|
|
648
|
+
z.literal(4),
|
|
649
|
+
z.literal(5)
|
|
650
|
+
]);
|
|
651
|
+
var ComplexityTierSchema = z.enum([
|
|
652
|
+
"trivial",
|
|
653
|
+
"simple",
|
|
654
|
+
"moderate",
|
|
655
|
+
"hard"
|
|
656
|
+
]);
|
|
657
|
+
var ModelCapabilityProfileSchema = z.object({
|
|
658
|
+
ref: z.object({
|
|
659
|
+
providerID: z.string().min(1),
|
|
660
|
+
modelID: z.string().min(1)
|
|
661
|
+
}),
|
|
662
|
+
kind: z.enum(["local", "frontier", "router"]),
|
|
663
|
+
contextWindow: z.number().int().positive(),
|
|
664
|
+
maxOutputTokens: z.number().int().positive(),
|
|
665
|
+
supportsToolCalling: z.boolean(),
|
|
666
|
+
supportsVision: z.boolean(),
|
|
667
|
+
reasoningTier: CapabilityTierSchema,
|
|
668
|
+
codeQualityTier: CapabilityTierSchema,
|
|
669
|
+
costPer1M: z.object({
|
|
670
|
+
inputUSD: z.number().min(0),
|
|
671
|
+
outputUSD: z.number().min(0)
|
|
672
|
+
}),
|
|
673
|
+
availability: z.enum(["available", "degraded", "unavailable"])
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
// src/config/schema.ts
|
|
677
|
+
import { z as z2 } from "zod";
|
|
678
|
+
var ModelRefSchema = z2.object({
|
|
679
|
+
providerID: z2.string().min(1),
|
|
680
|
+
modelID: z2.string().min(1)
|
|
681
|
+
});
|
|
682
|
+
var RouterModeSchema = z2.enum(["economy", "balanced", "quality"]);
|
|
683
|
+
var PrivacyModeSchema = z2.enum([
|
|
684
|
+
"forceLocalOnSensitive",
|
|
685
|
+
"consentBeforeFrontier",
|
|
686
|
+
"off"
|
|
687
|
+
]);
|
|
688
|
+
var BaselineModeSchema = z2.enum(["auto", "pinned"]);
|
|
689
|
+
var DashboardHostSchema = z2.enum(["127.0.0.1", "localhost"]);
|
|
690
|
+
var DashboardConfigSchema = z2.object({
|
|
691
|
+
enabled: z2.boolean().default(false),
|
|
692
|
+
host: DashboardHostSchema.default("127.0.0.1"),
|
|
693
|
+
port: z2.number().int().min(1024).max(65535).default(4599),
|
|
694
|
+
autoPortFallback: z2.boolean().default(true),
|
|
695
|
+
refreshMs: z2.number().int().min(250).default(2000),
|
|
696
|
+
recentRoutes: z2.number().int().positive().default(50),
|
|
697
|
+
openBrowser: z2.boolean().default(false)
|
|
698
|
+
}).default({
|
|
699
|
+
enabled: false,
|
|
700
|
+
host: "127.0.0.1",
|
|
701
|
+
port: 4599,
|
|
702
|
+
autoPortFallback: true,
|
|
703
|
+
refreshMs: 2000,
|
|
704
|
+
recentRoutes: 50,
|
|
705
|
+
openBrowser: false
|
|
706
|
+
});
|
|
707
|
+
var defaultLocalModel = {
|
|
708
|
+
providerID: "ollama",
|
|
709
|
+
modelID: "qwen3:8b"
|
|
710
|
+
};
|
|
711
|
+
var defaultFrontierModel = {
|
|
712
|
+
providerID: "anthropic",
|
|
713
|
+
modelID: "claude-sonnet-4-5"
|
|
714
|
+
};
|
|
715
|
+
var LocalRuntimeSchema = z2.object({
|
|
716
|
+
id: z2.enum(["ollama", "lmstudio", "foundry-local"]),
|
|
717
|
+
enabled: z2.boolean().default(true),
|
|
718
|
+
baseURL: z2.string().url().optional(),
|
|
719
|
+
discovery: z2.enum(["cli", "sdk", "manual"]).optional(),
|
|
720
|
+
defaultModel: ModelRefSchema
|
|
721
|
+
});
|
|
722
|
+
var OpenTeamConfigSchema = z2.object({
|
|
723
|
+
baseline: z2.object({
|
|
724
|
+
mode: BaselineModeSchema.default("auto"),
|
|
725
|
+
pinnedModel: ModelRefSchema.nullable().default(null),
|
|
726
|
+
hardDefault: ModelRefSchema.default(defaultFrontierModel)
|
|
727
|
+
}).default({
|
|
728
|
+
mode: "auto",
|
|
729
|
+
pinnedModel: null,
|
|
730
|
+
hardDefault: defaultFrontierModel
|
|
731
|
+
}),
|
|
732
|
+
router: z2.object({
|
|
733
|
+
mode: RouterModeSchema.default("balanced"),
|
|
734
|
+
localDefault: ModelRefSchema.default(defaultLocalModel),
|
|
735
|
+
trivialPromptMaxChars: z2.number().int().positive().default(280),
|
|
736
|
+
frontierPromptMinChars: z2.number().int().positive().default(2000)
|
|
737
|
+
}).default({
|
|
738
|
+
mode: "balanced",
|
|
739
|
+
localDefault: defaultLocalModel,
|
|
740
|
+
trivialPromptMaxChars: 280,
|
|
741
|
+
frontierPromptMinChars: 2000
|
|
742
|
+
}),
|
|
743
|
+
local: z2.object({
|
|
744
|
+
runtimes: z2.array(LocalRuntimeSchema).min(1).default([
|
|
745
|
+
{
|
|
746
|
+
id: "ollama",
|
|
747
|
+
enabled: true,
|
|
748
|
+
baseURL: "http://localhost:11434/v1",
|
|
749
|
+
defaultModel: { providerID: "ollama", modelID: "qwen3:8b" }
|
|
750
|
+
}
|
|
751
|
+
])
|
|
752
|
+
}).default({
|
|
753
|
+
runtimes: [
|
|
754
|
+
{
|
|
755
|
+
id: "ollama",
|
|
756
|
+
enabled: true,
|
|
757
|
+
baseURL: "http://localhost:11434/v1",
|
|
758
|
+
defaultModel: defaultLocalModel
|
|
759
|
+
}
|
|
760
|
+
]
|
|
761
|
+
}),
|
|
762
|
+
budgets: z2.object({
|
|
763
|
+
sessionUSD: z2.number().positive().optional(),
|
|
764
|
+
monthlyUSD: z2.number().positive().optional(),
|
|
765
|
+
frontierTokensPerSession: z2.number().int().positive().optional(),
|
|
766
|
+
hardStopOnBudgetExhaustion: z2.boolean().default(false)
|
|
767
|
+
}).default({ hardStopOnBudgetExhaustion: false }),
|
|
768
|
+
privacyMode: PrivacyModeSchema.default("forceLocalOnSensitive"),
|
|
769
|
+
dashboard: DashboardConfigSchema
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
// src/telemetry/events.ts
|
|
773
|
+
var EVENT_SCHEMA_VERSION = 1;
|
|
774
|
+
var EventBaseSchema = z3.object({
|
|
775
|
+
v: z3.literal(EVENT_SCHEMA_VERSION),
|
|
776
|
+
ts: z3.number().finite(),
|
|
777
|
+
sessionID: z3.string().min(1)
|
|
778
|
+
});
|
|
779
|
+
var RouteEventSchema = EventBaseSchema.extend({
|
|
780
|
+
type: z3.literal("route"),
|
|
781
|
+
promptHash: z3.string().min(1),
|
|
782
|
+
promptChars: z3.number().int().min(0),
|
|
783
|
+
tier: ComplexityTierSchema,
|
|
784
|
+
routeKind: z3.enum(["local", "frontier"]),
|
|
785
|
+
selected: ModelRefSchema,
|
|
786
|
+
rationale: z3.string(),
|
|
787
|
+
estimatedCostUSD: z3.number().finite().min(0),
|
|
788
|
+
baselineCostUSD: z3.number().finite().min(0),
|
|
789
|
+
estimatedSavingsUSD: z3.number().finite(),
|
|
790
|
+
budgetAction: z3.string().min(1),
|
|
791
|
+
tokensIn: z3.number().int().min(0).optional(),
|
|
792
|
+
tokensOut: z3.number().int().min(0).optional(),
|
|
793
|
+
decisionID: z3.string().min(1).optional(),
|
|
794
|
+
agent: z3.string().min(1).optional(),
|
|
795
|
+
success: z3.boolean().optional(),
|
|
796
|
+
batchID: z3.string().min(1).optional(),
|
|
797
|
+
failureReason: z3.string().min(1).optional(),
|
|
798
|
+
failureStage: z3.string().min(1).optional()
|
|
799
|
+
});
|
|
800
|
+
var MessageEventSchema = EventBaseSchema.extend({
|
|
801
|
+
type: z3.literal("message"),
|
|
802
|
+
providerID: z3.string().min(1),
|
|
803
|
+
modelID: z3.string().min(1),
|
|
804
|
+
agent: z3.string().min(1).optional(),
|
|
805
|
+
mode: z3.string().min(1).optional(),
|
|
806
|
+
messageID: z3.string().min(1).optional(),
|
|
807
|
+
costUSD: z3.number().finite().min(0),
|
|
808
|
+
tokensIn: z3.number().int().min(0),
|
|
809
|
+
tokensOut: z3.number().int().min(0),
|
|
810
|
+
tokensReasoning: z3.number().int().min(0),
|
|
811
|
+
tokensCacheRead: z3.number().int().min(0),
|
|
812
|
+
tokensCacheWrite: z3.number().int().min(0),
|
|
813
|
+
durationMs: z3.number().int().min(0)
|
|
814
|
+
});
|
|
815
|
+
var ToolcallEventSchema = EventBaseSchema.extend({
|
|
816
|
+
type: z3.literal("toolcall"),
|
|
817
|
+
tool: z3.string().min(1),
|
|
818
|
+
callID: z3.string().min(1),
|
|
819
|
+
agent: z3.string().min(1).optional(),
|
|
820
|
+
durationMs: z3.number().int().min(0),
|
|
821
|
+
ok: z3.boolean(),
|
|
822
|
+
title: z3.string().optional()
|
|
823
|
+
});
|
|
824
|
+
var MeetingEventSchema = EventBaseSchema.extend({
|
|
825
|
+
type: z3.literal("meeting"),
|
|
826
|
+
batchID: z3.string().min(1),
|
|
827
|
+
purpose: z3.string().optional(),
|
|
828
|
+
roles: z3.array(z3.string().min(1)),
|
|
829
|
+
decisionIDs: z3.array(z3.string().min(1)).optional()
|
|
830
|
+
});
|
|
831
|
+
var DecisionEventSchema = EventBaseSchema.extend({
|
|
832
|
+
type: z3.literal("decision"),
|
|
833
|
+
agent: z3.string().min(1).optional(),
|
|
834
|
+
summary: z3.string().min(1),
|
|
835
|
+
tags: z3.array(z3.string().min(1)).optional()
|
|
836
|
+
});
|
|
837
|
+
var ActivityEventSchema = EventBaseSchema.extend({
|
|
838
|
+
type: z3.literal("activity"),
|
|
839
|
+
kind: z3.enum(["route", "agent", "decision", "commit"]),
|
|
840
|
+
agent: z3.string().min(1).optional(),
|
|
841
|
+
summary: z3.string().min(1)
|
|
842
|
+
});
|
|
843
|
+
var OpenTeamEventSchema = z3.discriminatedUnion("type", [
|
|
844
|
+
RouteEventSchema,
|
|
845
|
+
MessageEventSchema,
|
|
846
|
+
ToolcallEventSchema,
|
|
847
|
+
MeetingEventSchema,
|
|
848
|
+
DecisionEventSchema,
|
|
849
|
+
ActivityEventSchema
|
|
850
|
+
]);
|
|
851
|
+
|
|
852
|
+
// src/telemetry/types.ts
|
|
853
|
+
import { z as z4 } from "zod";
|
|
854
|
+
var CostRecordSchema = z4.object({
|
|
855
|
+
ts: z4.number().finite(),
|
|
856
|
+
sessionID: z4.string().min(1).optional(),
|
|
857
|
+
promptHash: z4.string().min(1),
|
|
858
|
+
promptChars: z4.number().int().min(0),
|
|
859
|
+
tier: ComplexityTierSchema,
|
|
860
|
+
routeKind: z4.enum(["local", "frontier"]),
|
|
861
|
+
selected: ModelRefSchema,
|
|
862
|
+
rationale: z4.string(),
|
|
863
|
+
estimatedCostUSD: z4.number().finite().min(0),
|
|
864
|
+
baselineCostUSD: z4.number().finite().min(0),
|
|
865
|
+
estimatedSavingsUSD: z4.number().finite(),
|
|
866
|
+
budgetAction: z4.string().min(1),
|
|
867
|
+
tokensIn: z4.number().int().min(0).optional(),
|
|
868
|
+
tokensOut: z4.number().int().min(0).optional()
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
// src/telemetry/read.ts
|
|
872
|
+
var DEFAULT_TELEMETRY_PATH = ".opencode/openteam-telemetry.jsonl";
|
|
873
|
+
function parseCostRecordsJsonl(text) {
|
|
874
|
+
const records = [];
|
|
875
|
+
for (const line of text.split(`
|
|
876
|
+
`)) {
|
|
877
|
+
const trimmed = line.trim();
|
|
878
|
+
if (trimmed.length === 0) {
|
|
879
|
+
continue;
|
|
880
|
+
}
|
|
881
|
+
let candidate;
|
|
882
|
+
try {
|
|
883
|
+
candidate = JSON.parse(trimmed);
|
|
884
|
+
} catch {
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
const parsed = CostRecordSchema.safeParse(candidate);
|
|
888
|
+
if (parsed.success) {
|
|
889
|
+
records.push(parsed.data);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return records;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// src/telemetry/eventLog.ts
|
|
896
|
+
var DEFAULT_SESSIONS_DIR = ".opencode/openteam/sessions";
|
|
897
|
+
var LEGACY_SESSION_ID = "legacy";
|
|
898
|
+
function defaultJoin(dir, file) {
|
|
899
|
+
return dir.endsWith("/") ? `${dir}${file}` : `${dir}/${file}`;
|
|
900
|
+
}
|
|
901
|
+
function parseEventsJsonl(text) {
|
|
902
|
+
const events = [];
|
|
903
|
+
for (const line of text.split(`
|
|
904
|
+
`)) {
|
|
905
|
+
const trimmed = line.trim();
|
|
906
|
+
if (trimmed.length === 0) {
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
let candidate;
|
|
910
|
+
try {
|
|
911
|
+
candidate = JSON.parse(trimmed);
|
|
912
|
+
} catch {
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
const parsed = OpenTeamEventSchema.safeParse(candidate);
|
|
916
|
+
if (parsed.success) {
|
|
917
|
+
events.push(parsed.data);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
return events;
|
|
302
921
|
}
|
|
303
|
-
function
|
|
304
|
-
|
|
922
|
+
function costRecordToRouteEvent(record) {
|
|
923
|
+
const event = {
|
|
924
|
+
v: EVENT_SCHEMA_VERSION,
|
|
925
|
+
type: "route",
|
|
926
|
+
ts: record.ts,
|
|
927
|
+
sessionID: record.sessionID ?? LEGACY_SESSION_ID,
|
|
928
|
+
promptHash: record.promptHash,
|
|
929
|
+
promptChars: record.promptChars,
|
|
930
|
+
tier: record.tier,
|
|
931
|
+
routeKind: record.routeKind,
|
|
932
|
+
selected: record.selected,
|
|
933
|
+
rationale: record.rationale,
|
|
934
|
+
estimatedCostUSD: record.estimatedCostUSD,
|
|
935
|
+
baselineCostUSD: record.baselineCostUSD,
|
|
936
|
+
estimatedSavingsUSD: record.estimatedSavingsUSD,
|
|
937
|
+
budgetAction: record.budgetAction
|
|
938
|
+
};
|
|
939
|
+
if (record.tokensIn !== undefined) {
|
|
940
|
+
event.tokensIn = record.tokensIn;
|
|
941
|
+
}
|
|
942
|
+
if (record.tokensOut !== undefined) {
|
|
943
|
+
event.tokensOut = record.tokensOut;
|
|
944
|
+
}
|
|
945
|
+
return event;
|
|
946
|
+
}
|
|
947
|
+
function routeEventToCostRecord(event) {
|
|
948
|
+
const record = {
|
|
949
|
+
ts: event.ts,
|
|
950
|
+
sessionID: event.sessionID,
|
|
951
|
+
promptHash: event.promptHash,
|
|
952
|
+
promptChars: event.promptChars,
|
|
953
|
+
tier: event.tier,
|
|
954
|
+
routeKind: event.routeKind,
|
|
955
|
+
selected: event.selected,
|
|
956
|
+
rationale: event.rationale,
|
|
957
|
+
estimatedCostUSD: event.estimatedCostUSD,
|
|
958
|
+
baselineCostUSD: event.baselineCostUSD,
|
|
959
|
+
estimatedSavingsUSD: event.estimatedSavingsUSD,
|
|
960
|
+
budgetAction: event.budgetAction
|
|
961
|
+
};
|
|
962
|
+
if (event.tokensIn !== undefined) {
|
|
963
|
+
record.tokensIn = event.tokensIn;
|
|
964
|
+
}
|
|
965
|
+
if (event.tokensOut !== undefined) {
|
|
966
|
+
record.tokensOut = event.tokensOut;
|
|
967
|
+
}
|
|
968
|
+
return record;
|
|
305
969
|
}
|
|
306
|
-
function
|
|
307
|
-
|
|
308
|
-
|
|
970
|
+
async function readRouteCostRecords(dir, deps) {
|
|
971
|
+
const events = await readSessionEvents(dir, deps);
|
|
972
|
+
const records = [];
|
|
973
|
+
for (const event of events) {
|
|
974
|
+
if (event.type === "route") {
|
|
975
|
+
records.push(routeEventToCostRecord(event));
|
|
976
|
+
}
|
|
309
977
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
978
|
+
return records;
|
|
979
|
+
}
|
|
980
|
+
async function readSessionEvents(dir, deps) {
|
|
981
|
+
const join = deps.join ?? defaultJoin;
|
|
982
|
+
const files = (await deps.listFiles(dir)).filter((file) => file.endsWith(".jsonl"));
|
|
983
|
+
const perFile = await Promise.all(files.map(async (file) => {
|
|
984
|
+
const text = await deps.readText(join(dir, file));
|
|
985
|
+
return text === undefined ? [] : parseEventsJsonl(text);
|
|
986
|
+
}));
|
|
987
|
+
const events = perFile.flat();
|
|
988
|
+
if (deps.legacyTelemetryPath !== undefined) {
|
|
989
|
+
const legacyText = await deps.readText(deps.legacyTelemetryPath);
|
|
990
|
+
if (legacyText !== undefined) {
|
|
991
|
+
for (const record of parseCostRecordsJsonl(legacyText)) {
|
|
992
|
+
events.push(costRecordToRouteEvent(record));
|
|
993
|
+
}
|
|
994
|
+
}
|
|
322
995
|
}
|
|
323
|
-
return
|
|
324
|
-
`);
|
|
996
|
+
return events.sort((a, b) => a.ts - b.ts);
|
|
325
997
|
}
|
|
326
998
|
|
|
327
999
|
// src/dashboard/backlog.ts
|
|
@@ -375,6 +1047,8 @@ function buildLoopSnapshot(backlogPath, items) {
|
|
|
375
1047
|
// src/dashboard/state.ts
|
|
376
1048
|
var DEFAULT_RECENT_ROUTES = 50;
|
|
377
1049
|
var DEFAULT_ACTIVITY_LIMIT = 100;
|
|
1050
|
+
var DEFAULT_DECISIONS_LIMIT = 100;
|
|
1051
|
+
var DEFAULT_MEETINGS_LIMIT = 50;
|
|
378
1052
|
function toRouteView(record) {
|
|
379
1053
|
return {
|
|
380
1054
|
ts: record.ts,
|
|
@@ -387,11 +1061,49 @@ function toRouteView(record) {
|
|
|
387
1061
|
budgetAction: record.budgetAction
|
|
388
1062
|
};
|
|
389
1063
|
}
|
|
1064
|
+
function isRoute(event) {
|
|
1065
|
+
return event.type === "route";
|
|
1066
|
+
}
|
|
390
1067
|
function recentRoutes(records, limit) {
|
|
391
1068
|
return [...records].sort((a, b) => b.ts - a.ts).slice(0, Math.max(0, limit)).map(toRouteView);
|
|
392
1069
|
}
|
|
393
|
-
function
|
|
394
|
-
|
|
1070
|
+
function decisionToActivity(decision) {
|
|
1071
|
+
const entry = {
|
|
1072
|
+
ts: decision.ts,
|
|
1073
|
+
kind: "decision",
|
|
1074
|
+
summary: decision.summary
|
|
1075
|
+
};
|
|
1076
|
+
if (decision.agent !== undefined) {
|
|
1077
|
+
entry.agent = decision.agent;
|
|
1078
|
+
}
|
|
1079
|
+
return entry;
|
|
1080
|
+
}
|
|
1081
|
+
function meetingToActivity(meeting) {
|
|
1082
|
+
const roles = meeting.roles.join(", ");
|
|
1083
|
+
const purpose = meeting.purpose === undefined ? "" : `: ${meeting.purpose}`;
|
|
1084
|
+
return {
|
|
1085
|
+
ts: meeting.ts,
|
|
1086
|
+
kind: "agent",
|
|
1087
|
+
summary: `Reunión (${roles})${purpose}`
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
function activityFeed(aggregate, limit) {
|
|
1091
|
+
const entries = [
|
|
1092
|
+
...aggregate.activity.map((view) => {
|
|
1093
|
+
const entry = {
|
|
1094
|
+
ts: view.ts,
|
|
1095
|
+
kind: view.kind,
|
|
1096
|
+
summary: view.summary
|
|
1097
|
+
};
|
|
1098
|
+
if (view.agent !== undefined) {
|
|
1099
|
+
entry.agent = view.agent;
|
|
1100
|
+
}
|
|
1101
|
+
return entry;
|
|
1102
|
+
}),
|
|
1103
|
+
...aggregate.decisions.map(decisionToActivity),
|
|
1104
|
+
...aggregate.meetings.map(meetingToActivity)
|
|
1105
|
+
];
|
|
1106
|
+
return entries.sort((a, b) => b.ts - a.ts).slice(0, Math.max(0, limit));
|
|
395
1107
|
}
|
|
396
1108
|
function loopFrom(backlog) {
|
|
397
1109
|
if (backlog === undefined) {
|
|
@@ -406,13 +1118,21 @@ function loopFrom(backlog) {
|
|
|
406
1118
|
function buildDashboardState(inputs) {
|
|
407
1119
|
const recentRoutesLimit = inputs.recentRoutesLimit ?? DEFAULT_RECENT_ROUTES;
|
|
408
1120
|
const activityLimit = inputs.activityLimit ?? DEFAULT_ACTIVITY_LIMIT;
|
|
1121
|
+
const decisionsLimit = inputs.decisionsLimit ?? DEFAULT_DECISIONS_LIMIT;
|
|
1122
|
+
const meetingsLimit = inputs.meetingsLimit ?? DEFAULT_MEETINGS_LIMIT;
|
|
1123
|
+
const aggregate = aggregateEvents(inputs.events);
|
|
1124
|
+
const costRecords = inputs.events.filter(isRoute).map(routeEventToCostRecord);
|
|
409
1125
|
const state = {
|
|
410
1126
|
generatedAt: inputs.generatedAt,
|
|
411
1127
|
session: {},
|
|
412
|
-
cost: summarizeCostRecords(
|
|
413
|
-
|
|
1128
|
+
cost: summarizeCostRecords(costRecords),
|
|
1129
|
+
totals: aggregate.totals,
|
|
1130
|
+
sessions: aggregate.sessions,
|
|
1131
|
+
recentRoutes: recentRoutes(costRecords, recentRoutesLimit),
|
|
1132
|
+
decisions: aggregate.decisions.slice(0, Math.max(0, decisionsLimit)),
|
|
1133
|
+
meetings: aggregate.meetings.slice(0, Math.max(0, meetingsLimit)),
|
|
414
1134
|
team: [...inputs.team],
|
|
415
|
-
activity:
|
|
1135
|
+
activity: activityFeed(aggregate, activityLimit)
|
|
416
1136
|
};
|
|
417
1137
|
if (inputs.session?.id !== undefined) {
|
|
418
1138
|
state.session.id = inputs.session.id;
|
|
@@ -703,216 +1423,95 @@ function renderAgentList(classifications, context) {
|
|
|
703
1423
|
`);
|
|
704
1424
|
}
|
|
705
1425
|
|
|
706
|
-
// src/telemetry/
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
]);
|
|
722
|
-
var ComplexityTierSchema = z.enum([
|
|
723
|
-
"trivial",
|
|
724
|
-
"simple",
|
|
725
|
-
"moderate",
|
|
726
|
-
"hard"
|
|
727
|
-
]);
|
|
728
|
-
var ModelCapabilityProfileSchema = z.object({
|
|
729
|
-
ref: z.object({
|
|
730
|
-
providerID: z.string().min(1),
|
|
731
|
-
modelID: z.string().min(1)
|
|
732
|
-
}),
|
|
733
|
-
kind: z.enum(["local", "frontier", "router"]),
|
|
734
|
-
contextWindow: z.number().int().positive(),
|
|
735
|
-
maxOutputTokens: z.number().int().positive(),
|
|
736
|
-
supportsToolCalling: z.boolean(),
|
|
737
|
-
supportsVision: z.boolean(),
|
|
738
|
-
reasoningTier: CapabilityTierSchema,
|
|
739
|
-
codeQualityTier: CapabilityTierSchema,
|
|
740
|
-
costPer1M: z.object({
|
|
741
|
-
inputUSD: z.number().min(0),
|
|
742
|
-
outputUSD: z.number().min(0)
|
|
743
|
-
}),
|
|
744
|
-
availability: z.enum(["available", "degraded", "unavailable"])
|
|
745
|
-
});
|
|
746
|
-
|
|
747
|
-
// src/config/schema.ts
|
|
748
|
-
import { z as z2 } from "zod";
|
|
749
|
-
var ModelRefSchema = z2.object({
|
|
750
|
-
providerID: z2.string().min(1),
|
|
751
|
-
modelID: z2.string().min(1)
|
|
752
|
-
});
|
|
753
|
-
var RouterModeSchema = z2.enum(["economy", "balanced", "quality"]);
|
|
754
|
-
var PrivacyModeSchema = z2.enum([
|
|
755
|
-
"forceLocalOnSensitive",
|
|
756
|
-
"consentBeforeFrontier",
|
|
757
|
-
"off"
|
|
758
|
-
]);
|
|
759
|
-
var BaselineModeSchema = z2.enum(["auto", "pinned"]);
|
|
760
|
-
var DashboardHostSchema = z2.enum(["127.0.0.1", "localhost"]);
|
|
761
|
-
var DashboardConfigSchema = z2.object({
|
|
762
|
-
enabled: z2.boolean().default(false),
|
|
763
|
-
host: DashboardHostSchema.default("127.0.0.1"),
|
|
764
|
-
port: z2.number().int().min(1024).max(65535).default(4599),
|
|
765
|
-
autoPortFallback: z2.boolean().default(true),
|
|
766
|
-
refreshMs: z2.number().int().min(250).default(2000),
|
|
767
|
-
recentRoutes: z2.number().int().positive().default(50),
|
|
768
|
-
openBrowser: z2.boolean().default(false)
|
|
769
|
-
}).default({
|
|
770
|
-
enabled: false,
|
|
771
|
-
host: "127.0.0.1",
|
|
772
|
-
port: 4599,
|
|
773
|
-
autoPortFallback: true,
|
|
774
|
-
refreshMs: 2000,
|
|
775
|
-
recentRoutes: 50,
|
|
776
|
-
openBrowser: false
|
|
777
|
-
});
|
|
778
|
-
var defaultLocalModel = {
|
|
779
|
-
providerID: "ollama",
|
|
780
|
-
modelID: "qwen3:8b"
|
|
781
|
-
};
|
|
782
|
-
var defaultFrontierModel = {
|
|
783
|
-
providerID: "anthropic",
|
|
784
|
-
modelID: "claude-sonnet-4-5"
|
|
785
|
-
};
|
|
786
|
-
var LocalRuntimeSchema = z2.object({
|
|
787
|
-
id: z2.enum(["ollama", "lmstudio", "foundry-local"]),
|
|
788
|
-
enabled: z2.boolean().default(true),
|
|
789
|
-
baseURL: z2.string().url().optional(),
|
|
790
|
-
discovery: z2.enum(["cli", "sdk", "manual"]).optional(),
|
|
791
|
-
defaultModel: ModelRefSchema
|
|
792
|
-
});
|
|
793
|
-
var OpenTeamConfigSchema = z2.object({
|
|
794
|
-
baseline: z2.object({
|
|
795
|
-
mode: BaselineModeSchema.default("auto"),
|
|
796
|
-
pinnedModel: ModelRefSchema.nullable().default(null),
|
|
797
|
-
hardDefault: ModelRefSchema.default(defaultFrontierModel)
|
|
798
|
-
}).default({
|
|
799
|
-
mode: "auto",
|
|
800
|
-
pinnedModel: null,
|
|
801
|
-
hardDefault: defaultFrontierModel
|
|
802
|
-
}),
|
|
803
|
-
router: z2.object({
|
|
804
|
-
mode: RouterModeSchema.default("balanced"),
|
|
805
|
-
localDefault: ModelRefSchema.default(defaultLocalModel),
|
|
806
|
-
trivialPromptMaxChars: z2.number().int().positive().default(280),
|
|
807
|
-
frontierPromptMinChars: z2.number().int().positive().default(2000)
|
|
808
|
-
}).default({
|
|
809
|
-
mode: "balanced",
|
|
810
|
-
localDefault: defaultLocalModel,
|
|
811
|
-
trivialPromptMaxChars: 280,
|
|
812
|
-
frontierPromptMinChars: 2000
|
|
813
|
-
}),
|
|
814
|
-
local: z2.object({
|
|
815
|
-
runtimes: z2.array(LocalRuntimeSchema).min(1).default([
|
|
816
|
-
{
|
|
817
|
-
id: "ollama",
|
|
818
|
-
enabled: true,
|
|
819
|
-
baseURL: "http://localhost:11434/v1",
|
|
820
|
-
defaultModel: { providerID: "ollama", modelID: "qwen3:8b" }
|
|
821
|
-
}
|
|
822
|
-
])
|
|
823
|
-
}).default({
|
|
824
|
-
runtimes: [
|
|
825
|
-
{
|
|
826
|
-
id: "ollama",
|
|
827
|
-
enabled: true,
|
|
828
|
-
baseURL: "http://localhost:11434/v1",
|
|
829
|
-
defaultModel: defaultLocalModel
|
|
830
|
-
}
|
|
831
|
-
]
|
|
832
|
-
}),
|
|
833
|
-
budgets: z2.object({
|
|
834
|
-
sessionUSD: z2.number().positive().optional(),
|
|
835
|
-
monthlyUSD: z2.number().positive().optional(),
|
|
836
|
-
frontierTokensPerSession: z2.number().int().positive().optional(),
|
|
837
|
-
hardStopOnBudgetExhaustion: z2.boolean().default(false)
|
|
838
|
-
}).default({ hardStopOnBudgetExhaustion: false }),
|
|
839
|
-
privacyMode: PrivacyModeSchema.default("forceLocalOnSensitive"),
|
|
840
|
-
dashboard: DashboardConfigSchema
|
|
841
|
-
});
|
|
842
|
-
|
|
843
|
-
// src/telemetry/types.ts
|
|
844
|
-
var CostRecordSchema = z3.object({
|
|
845
|
-
ts: z3.number().finite(),
|
|
846
|
-
sessionID: z3.string().min(1).optional(),
|
|
847
|
-
promptHash: z3.string().min(1),
|
|
848
|
-
promptChars: z3.number().int().min(0),
|
|
849
|
-
tier: ComplexityTierSchema,
|
|
850
|
-
routeKind: z3.enum(["local", "frontier"]),
|
|
851
|
-
selected: ModelRefSchema,
|
|
852
|
-
rationale: z3.string(),
|
|
853
|
-
estimatedCostUSD: z3.number().finite().min(0),
|
|
854
|
-
baselineCostUSD: z3.number().finite().min(0),
|
|
855
|
-
estimatedSavingsUSD: z3.number().finite(),
|
|
856
|
-
budgetAction: z3.string().min(1),
|
|
857
|
-
tokensIn: z3.number().int().min(0).optional(),
|
|
858
|
-
tokensOut: z3.number().int().min(0).optional()
|
|
859
|
-
});
|
|
860
|
-
|
|
861
|
-
// src/telemetry/read.ts
|
|
862
|
-
var DEFAULT_TELEMETRY_PATH = ".opencode/openteam-telemetry.jsonl";
|
|
863
|
-
function parseCostRecordsJsonl(text) {
|
|
864
|
-
const records = [];
|
|
1426
|
+
// src/telemetry/decisions.ts
|
|
1427
|
+
var DECISIONS_SESSION_ID = "decisions";
|
|
1428
|
+
var BULLET_RE = /^\s*[-*]\s+(.+?)\s*$/;
|
|
1429
|
+
var DATE_RE = /^(\d{4}-\d{2}-\d{2})\b\s*/;
|
|
1430
|
+
var AGENT_RE = /^\[([^\]]+)\]\s*/;
|
|
1431
|
+
var TAG_RE = /#([A-Za-z0-9][\w-]*)/g;
|
|
1432
|
+
function parseDate(value) {
|
|
1433
|
+
const parsed = Date.parse(`${value}T00:00:00Z`);
|
|
1434
|
+
return Number.isNaN(parsed) ? undefined : parsed;
|
|
1435
|
+
}
|
|
1436
|
+
function parseDecisionsMarkdown(text, options = {}) {
|
|
1437
|
+
const sessionID = options.sessionID ?? DECISIONS_SESSION_ID;
|
|
1438
|
+
const baseTs = options.baseTs ?? 0;
|
|
1439
|
+
const events = [];
|
|
1440
|
+
let index = 0;
|
|
865
1441
|
for (const line of text.split(`
|
|
866
1442
|
`)) {
|
|
867
|
-
const
|
|
868
|
-
if (
|
|
1443
|
+
const bullet = BULLET_RE.exec(line);
|
|
1444
|
+
if (bullet === null) {
|
|
869
1445
|
continue;
|
|
870
1446
|
}
|
|
871
|
-
let
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
1447
|
+
let content = bullet[1] ?? "";
|
|
1448
|
+
let ts = baseTs + index;
|
|
1449
|
+
const dateMatch = DATE_RE.exec(content);
|
|
1450
|
+
if (dateMatch?.[1] !== undefined) {
|
|
1451
|
+
const parsed = parseDate(dateMatch[1]);
|
|
1452
|
+
if (parsed !== undefined) {
|
|
1453
|
+
ts = parsed;
|
|
1454
|
+
content = content.slice(dateMatch[0].length);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
let agent;
|
|
1458
|
+
const agentMatch = AGENT_RE.exec(content);
|
|
1459
|
+
if (agentMatch?.[1] !== undefined) {
|
|
1460
|
+
agent = agentMatch[1].trim();
|
|
1461
|
+
content = content.slice(agentMatch[0].length);
|
|
1462
|
+
}
|
|
1463
|
+
const tags = [];
|
|
1464
|
+
for (const tagMatch of content.matchAll(TAG_RE)) {
|
|
1465
|
+
const tag = tagMatch[1];
|
|
1466
|
+
if (tag !== undefined && !tags.includes(tag)) {
|
|
1467
|
+
tags.push(tag);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
const summary = content.replace(TAG_RE, "").replace(/\s+/g, " ").trim();
|
|
1471
|
+
if (summary.length === 0) {
|
|
875
1472
|
continue;
|
|
876
1473
|
}
|
|
877
|
-
const
|
|
878
|
-
|
|
879
|
-
|
|
1474
|
+
const event = {
|
|
1475
|
+
v: EVENT_SCHEMA_VERSION,
|
|
1476
|
+
type: "decision",
|
|
1477
|
+
ts,
|
|
1478
|
+
sessionID,
|
|
1479
|
+
summary
|
|
1480
|
+
};
|
|
1481
|
+
if (agent !== undefined && agent.length > 0) {
|
|
1482
|
+
event.agent = agent;
|
|
880
1483
|
}
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
}
|
|
884
|
-
function isMissingFile(error) {
|
|
885
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
886
|
-
}
|
|
887
|
-
async function readCostRecords(path, deps = {}) {
|
|
888
|
-
const readFileFn = deps.readFile ?? readFile;
|
|
889
|
-
try {
|
|
890
|
-
const content = await readFileFn(path, "utf8");
|
|
891
|
-
return parseCostRecordsJsonl(content);
|
|
892
|
-
} catch (error) {
|
|
893
|
-
if (isMissingFile(error)) {
|
|
894
|
-
return [];
|
|
1484
|
+
if (tags.length > 0) {
|
|
1485
|
+
event.tags = tags;
|
|
895
1486
|
}
|
|
896
|
-
|
|
1487
|
+
events.push(event);
|
|
1488
|
+
index += 1;
|
|
897
1489
|
}
|
|
1490
|
+
return events;
|
|
898
1491
|
}
|
|
899
1492
|
|
|
900
1493
|
// src/web/snapshot.ts
|
|
901
|
-
function createSnapshotReader(deps, paths, context) {
|
|
1494
|
+
function createSnapshotReader(deps, paths, context = {}) {
|
|
1495
|
+
const readEventsDeps = {
|
|
1496
|
+
listFiles: deps.listFiles,
|
|
1497
|
+
readText: deps.readText,
|
|
1498
|
+
...paths.legacyTelemetryPath !== undefined ? { legacyTelemetryPath: paths.legacyTelemetryPath } : {}
|
|
1499
|
+
};
|
|
902
1500
|
return async () => {
|
|
903
|
-
const [
|
|
904
|
-
|
|
1501
|
+
const [sessionEvents, decisionsText, backlogText, agentFiles, lastCommit] = await Promise.all([
|
|
1502
|
+
readSessionEvents(paths.sessionsDir, readEventsDeps),
|
|
1503
|
+
deps.readText(paths.decisionsPath),
|
|
905
1504
|
deps.readText(paths.backlogPath),
|
|
906
1505
|
deps.listAgentFiles(paths.agentDir),
|
|
907
1506
|
deps.gitLastCommit?.() ?? Promise.resolve(undefined)
|
|
908
1507
|
]);
|
|
909
|
-
const
|
|
1508
|
+
const decisionEvents = parseDecisionsMarkdown(decisionsText ?? "");
|
|
1509
|
+
const events = [...sessionEvents, ...decisionEvents];
|
|
910
1510
|
const team = agentFiles.map(parseAgentFile);
|
|
911
1511
|
const inputs = {
|
|
912
1512
|
generatedAt: deps.now(),
|
|
913
|
-
|
|
914
|
-
team
|
|
915
|
-
activity: context.activity()
|
|
1513
|
+
events,
|
|
1514
|
+
team
|
|
916
1515
|
};
|
|
917
1516
|
if (backlogText !== undefined) {
|
|
918
1517
|
const backlog = {
|
|
@@ -936,7 +1535,7 @@ function createSnapshotReader(deps, paths, context) {
|
|
|
936
1535
|
|
|
937
1536
|
// src/web/watch.ts
|
|
938
1537
|
function watchSources(paths, onChange, deps, debounceMs = 250) {
|
|
939
|
-
const setTimer = deps.setTimer ?? ((cb,
|
|
1538
|
+
const setTimer = deps.setTimer ?? ((cb, ms2) => setTimeout(cb, ms2));
|
|
940
1539
|
const clearTimer = deps.clearTimer ?? ((handle) => clearTimeout(handle));
|
|
941
1540
|
const watchers = [];
|
|
942
1541
|
let pending;
|
|
@@ -973,18 +1572,20 @@ function startDashboard(deps) {
|
|
|
973
1572
|
}
|
|
974
1573
|
async function createDashboardRuntime(deps) {
|
|
975
1574
|
const backlogPath = deps.backlogPath ?? DEFAULT_BACKLOG_PATH;
|
|
976
|
-
const
|
|
1575
|
+
const decisionsPath = deps.decisionsPath ?? DEFAULT_DECISIONS_PATH;
|
|
977
1576
|
const readSnapshot = createSnapshotReader({
|
|
978
1577
|
readText: deps.readText,
|
|
1578
|
+
listFiles: deps.listFiles,
|
|
979
1579
|
listAgentFiles: deps.listAgentFiles,
|
|
980
1580
|
now: deps.now,
|
|
981
1581
|
...deps.gitLastCommit !== undefined ? { gitLastCommit: deps.gitLastCommit } : {}
|
|
982
1582
|
}, {
|
|
983
|
-
|
|
1583
|
+
sessionsDir: deps.sessionsDir,
|
|
1584
|
+
decisionsPath,
|
|
984
1585
|
backlogPath,
|
|
985
|
-
agentDir: deps.agentDir
|
|
1586
|
+
agentDir: deps.agentDir,
|
|
1587
|
+
...deps.legacyTelemetryPath !== undefined ? { legacyTelemetryPath: deps.legacyTelemetryPath } : {}
|
|
986
1588
|
}, {
|
|
987
|
-
activity: () => activity.list(),
|
|
988
1589
|
recentRoutesLimit: deps.config.recentRoutes,
|
|
989
1590
|
...deps.session !== undefined ? { session: deps.session } : {}
|
|
990
1591
|
});
|
|
@@ -995,16 +1596,12 @@ async function createDashboardRuntime(deps) {
|
|
|
995
1596
|
...deps.log !== undefined ? { log: deps.log } : {}
|
|
996
1597
|
});
|
|
997
1598
|
const watchFn = deps.watch ?? ((path, listener) => fsWatch(path, { persistent: false }, listener));
|
|
998
|
-
const watcher = watchSources([deps.
|
|
1599
|
+
const watcher = watchSources([deps.sessionsDir, decisionsPath, backlogPath, deps.agentDir], () => {
|
|
999
1600
|
server.notify();
|
|
1000
1601
|
}, { watch: watchFn });
|
|
1001
1602
|
return {
|
|
1002
1603
|
url: server.url,
|
|
1003
1604
|
port: server.port,
|
|
1004
|
-
pushActivity(entry) {
|
|
1005
|
-
activity.push(entry);
|
|
1006
|
-
server.notify();
|
|
1007
|
-
},
|
|
1008
1605
|
close() {
|
|
1009
1606
|
watcher.close();
|
|
1010
1607
|
server.close();
|
|
@@ -1015,6 +1612,7 @@ async function createDashboardRuntime(deps) {
|
|
|
1015
1612
|
// src/cli/dashboardServe.ts
|
|
1016
1613
|
function parseDashboardServeArgs(rest) {
|
|
1017
1614
|
return {
|
|
1615
|
+
status: rest.includes("--status"),
|
|
1018
1616
|
serve: rest.includes("--serve"),
|
|
1019
1617
|
open: rest.includes("--open")
|
|
1020
1618
|
};
|
|
@@ -1026,10 +1624,13 @@ async function runDashboardServe(deps, options) {
|
|
|
1026
1624
|
try {
|
|
1027
1625
|
runtime = await start({
|
|
1028
1626
|
config: { ...config.dashboard, enabled: true },
|
|
1029
|
-
|
|
1627
|
+
sessionsDir: deps.sessionsDir,
|
|
1628
|
+
decisionsPath: deps.decisionsPath,
|
|
1030
1629
|
backlogPath: deps.backlogPath,
|
|
1031
1630
|
agentDir: deps.agentDir,
|
|
1631
|
+
...deps.legacyTelemetryPath !== undefined ? { legacyTelemetryPath: deps.legacyTelemetryPath } : {},
|
|
1032
1632
|
readText: deps.readText,
|
|
1633
|
+
listFiles: deps.listFiles,
|
|
1033
1634
|
listAgentFiles: deps.listAgentFiles,
|
|
1034
1635
|
now: deps.now,
|
|
1035
1636
|
...deps.gitLastCommit !== undefined ? { gitLastCommit: deps.gitLastCommit } : {},
|
|
@@ -1868,29 +2469,19 @@ function autoBaseline(config) {
|
|
|
1868
2469
|
// src/commands/dashboard.ts
|
|
1869
2470
|
function renderDashboardStatus(dashboard) {
|
|
1870
2471
|
const url = `http://${dashboard.host}:${dashboard.port}`;
|
|
1871
|
-
if (!dashboard.enabled) {
|
|
1872
|
-
return [
|
|
1873
|
-
"Dashboard: deshabilitado.",
|
|
1874
|
-
"",
|
|
1875
|
-
'Para activarlo dentro de opencode, pon "dashboard": { "enabled": true }',
|
|
1876
|
-
"en .opencode/openteam.json y recarga opencode. Se servirá (loopback) en:",
|
|
1877
|
-
` ${url}`,
|
|
1878
|
-
"",
|
|
1879
|
-
"O véelo ahora mismo sin editar la config:",
|
|
1880
|
-
" openteam dashboard --serve (añade --open para abrir el navegador)"
|
|
1881
|
-
].join(`
|
|
1882
|
-
`);
|
|
1883
|
-
}
|
|
1884
2472
|
return [
|
|
1885
|
-
"Dashboard:
|
|
2473
|
+
"Dashboard (multi-sesión, se lanza desde la CLI):",
|
|
1886
2474
|
` URL: ${url}`,
|
|
1887
2475
|
` Refresco: ${dashboard.refreshMs} ms (SSE + polling)`,
|
|
1888
2476
|
` Rutas: últimas ${dashboard.recentRoutes}`,
|
|
1889
2477
|
dashboard.autoPortFallback ? " Puerto: con fallback automático si está ocupado" : " Puerto: fijo (sin fallback)",
|
|
1890
2478
|
"",
|
|
1891
|
-
"
|
|
1892
|
-
"
|
|
1893
|
-
"
|
|
2479
|
+
"Lánzalo con:",
|
|
2480
|
+
" openteam dashboard (Ctrl+C para parar; --open abre el navegador)",
|
|
2481
|
+
"",
|
|
2482
|
+
"Agrega TODAS las sesiones de opencode que escriben eventos en",
|
|
2483
|
+
" .opencode/openteam/sessions/*.jsonl",
|
|
2484
|
+
"Solo escucha en loopback y nunca expone prompts (solo hashes)."
|
|
1894
2485
|
].join(`
|
|
1895
2486
|
`);
|
|
1896
2487
|
}
|
|
@@ -2027,7 +2618,12 @@ function buildOrchestratorAgent(frontier, options = {}) {
|
|
|
2027
2618
|
"- **scribe** — memoria silenciosa del equipo. Registra decisiones y",
|
|
2028
2619
|
" aprendizajes en un log compartido (`.opencode/openteam-decisions.md`) sin",
|
|
2029
2620
|
" ejecutar cambios de código. Modelo **local**; permisos de solo lectura más",
|
|
2030
|
-
" edición de ese log.",
|
|
2621
|
+
" edición de ese log. **Formato parseable** para el dashboard: una decisión",
|
|
2622
|
+
" por línea como item de lista Markdown, ya **redactada** (sin prompts ni",
|
|
2623
|
+
" datos sensibles):",
|
|
2624
|
+
" `- YYYY-MM-DD [agente] Resumen breve de la decisión #tag1 #tag2`",
|
|
2625
|
+
" La fecha, el `[agente]` y los `#tags` son opcionales; el resumen es",
|
|
2626
|
+
" obligatorio.",
|
|
2031
2627
|
"- **ralph** — automatización y triage. Monitoriza el trabajo pendiente",
|
|
2032
2628
|
" (issues/tareas), lo prioriza, coordina la ejecución delegando en los",
|
|
2033
2629
|
" especialistas y **escala al humano** ante bloqueos, riesgos o aprobaciones.",
|
|
@@ -2157,8 +2753,8 @@ var HELP = [
|
|
|
2157
2753
|
" openteam baseline auto Baseline cheapest-capable (modo auto)",
|
|
2158
2754
|
" openteam doctor Diagnóstico de runtimes y config",
|
|
2159
2755
|
" openteam agents Lista los agentes y el LLM (local/frontier) de cada uno",
|
|
2160
|
-
" openteam dashboard
|
|
2161
|
-
" openteam dashboard --
|
|
2756
|
+
" openteam dashboard Lanza el dashboard web multi-sesión (Ctrl+C para parar; --open abre el navegador)",
|
|
2757
|
+
" openteam dashboard --status Muestra la config del dashboard sin lanzarlo",
|
|
2162
2758
|
" openteam report Resumen de coste/ahorro (telemetría)",
|
|
2163
2759
|
" openteam yolo status Muestra si el modo YOLO está activo",
|
|
2164
2760
|
" openteam yolo on Activa YOLO (opencode auto-aprueba todo)",
|
|
@@ -2843,7 +3439,7 @@ function createGitLastCommit(exec) {
|
|
|
2843
3439
|
// src/cli.ts
|
|
2844
3440
|
var execFileAsync = promisify(execFile);
|
|
2845
3441
|
var AGENT_DIR = dirname2(ORCHESTRATOR_AGENT_PATH);
|
|
2846
|
-
function
|
|
3442
|
+
function isMissingFile(error) {
|
|
2847
3443
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
2848
3444
|
}
|
|
2849
3445
|
var nodeExec = async (command, args) => {
|
|
@@ -2861,10 +3457,10 @@ var nodeExec = async (command, args) => {
|
|
|
2861
3457
|
};
|
|
2862
3458
|
async function loadConfig(path) {
|
|
2863
3459
|
try {
|
|
2864
|
-
const content = await
|
|
3460
|
+
const content = await readFile(path, "utf8");
|
|
2865
3461
|
return loadOpenTeamConfig(JSON.parse(content));
|
|
2866
3462
|
} catch (error) {
|
|
2867
|
-
if (
|
|
3463
|
+
if (isMissingFile(error)) {
|
|
2868
3464
|
return loadOpenTeamConfig({});
|
|
2869
3465
|
}
|
|
2870
3466
|
throw error;
|
|
@@ -2872,14 +3468,24 @@ async function loadConfig(path) {
|
|
|
2872
3468
|
}
|
|
2873
3469
|
async function readTextOptional(path) {
|
|
2874
3470
|
try {
|
|
2875
|
-
return await
|
|
3471
|
+
return await readFile(path, "utf8");
|
|
2876
3472
|
} catch (error) {
|
|
2877
|
-
if (
|
|
3473
|
+
if (isMissingFile(error)) {
|
|
2878
3474
|
return;
|
|
2879
3475
|
}
|
|
2880
3476
|
throw error;
|
|
2881
3477
|
}
|
|
2882
3478
|
}
|
|
3479
|
+
async function listDirFiles(dir) {
|
|
3480
|
+
try {
|
|
3481
|
+
return await readdir(dir);
|
|
3482
|
+
} catch (error) {
|
|
3483
|
+
if (isMissingFile(error)) {
|
|
3484
|
+
return [];
|
|
3485
|
+
}
|
|
3486
|
+
throw error;
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
2883
3489
|
async function openBrowser(url) {
|
|
2884
3490
|
if (process.platform === "win32") {
|
|
2885
3491
|
await nodeExec("cmd", ["/c", "start", "", url]);
|
|
@@ -2901,10 +3507,10 @@ var deps = {
|
|
|
2901
3507
|
saveConfig: (config, path) => writeOpenTeamConfigFile(config, path),
|
|
2902
3508
|
readOpencodeConfig: async (path) => {
|
|
2903
3509
|
try {
|
|
2904
|
-
const content = await
|
|
3510
|
+
const content = await readFile(path, "utf8");
|
|
2905
3511
|
return JSON.parse(content);
|
|
2906
3512
|
} catch (error) {
|
|
2907
|
-
if (
|
|
3513
|
+
if (isMissingFile(error)) {
|
|
2908
3514
|
return;
|
|
2909
3515
|
}
|
|
2910
3516
|
throw error;
|
|
@@ -2924,7 +3530,7 @@ var deps = {
|
|
|
2924
3530
|
try {
|
|
2925
3531
|
entries = await readdir(dir);
|
|
2926
3532
|
} catch (error) {
|
|
2927
|
-
if (
|
|
3533
|
+
if (isMissingFile(error)) {
|
|
2928
3534
|
return [];
|
|
2929
3535
|
}
|
|
2930
3536
|
throw error;
|
|
@@ -2932,7 +3538,7 @@ var deps = {
|
|
|
2932
3538
|
const mdFiles = entries.filter((entry) => entry.endsWith(".md"));
|
|
2933
3539
|
return Promise.all(mdFiles.map(async (entry) => ({
|
|
2934
3540
|
name: entry.replace(/\.md$/, ""),
|
|
2935
|
-
contents: await
|
|
3541
|
+
contents: await readFile(join2(dir, entry), "utf8")
|
|
2936
3542
|
})));
|
|
2937
3543
|
},
|
|
2938
3544
|
probe: (config) => {
|
|
@@ -2943,7 +3549,11 @@ var deps = {
|
|
|
2943
3549
|
});
|
|
2944
3550
|
return registry.probe(config.local.runtimes);
|
|
2945
3551
|
},
|
|
2946
|
-
readTelemetry: (path) =>
|
|
3552
|
+
readTelemetry: (path) => readRouteCostRecords(DEFAULT_SESSIONS_DIR, {
|
|
3553
|
+
listFiles: listDirFiles,
|
|
3554
|
+
readText: readTextOptional,
|
|
3555
|
+
legacyTelemetryPath: path
|
|
3556
|
+
}),
|
|
2947
3557
|
configPath: DEFAULT_CONFIG_PATH,
|
|
2948
3558
|
telemetryPath: DEFAULT_TELEMETRY_PATH,
|
|
2949
3559
|
opencodeConfigPath: OPENCODE_CONFIG_PATH,
|
|
@@ -2983,14 +3593,17 @@ async function main() {
|
|
|
2983
3593
|
}
|
|
2984
3594
|
if (argv[0] === "dashboard") {
|
|
2985
3595
|
const flags = parseDashboardServeArgs(argv.slice(1));
|
|
2986
|
-
if (flags.
|
|
3596
|
+
if (!flags.status) {
|
|
2987
3597
|
const result2 = await runDashboardServe({
|
|
2988
3598
|
loadConfig,
|
|
2989
3599
|
configPath: DEFAULT_CONFIG_PATH,
|
|
2990
|
-
|
|
3600
|
+
sessionsDir: DEFAULT_SESSIONS_DIR,
|
|
3601
|
+
decisionsPath: DEFAULT_DECISIONS_PATH,
|
|
2991
3602
|
backlogPath: DEFAULT_BACKLOG_PATH,
|
|
2992
3603
|
agentDir: AGENT_DIR,
|
|
3604
|
+
legacyTelemetryPath: DEFAULT_TELEMETRY_PATH,
|
|
2993
3605
|
readText: readTextOptional,
|
|
3606
|
+
listFiles: listDirFiles,
|
|
2994
3607
|
listAgentFiles: deps.listAgentFiles,
|
|
2995
3608
|
now: () => new Date().toISOString(),
|
|
2996
3609
|
gitLastCommit: createGitLastCommit(nodeExec),
|