@jmanuelcorral/openteam 0.1.21 → 0.1.23

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.
Files changed (43) hide show
  1. package/README.md +29 -31
  2. package/dist/cli/dashboardServe.d.ts +15 -8
  3. package/dist/cli/dashboardServe.d.ts.map +1 -1
  4. package/dist/cli.js +936 -286
  5. package/dist/commands/dashboard.d.ts +4 -4
  6. package/dist/commands/dashboard.d.ts.map +1 -1
  7. package/dist/commands/orchestratorAgent.d.ts.map +1 -1
  8. package/dist/commands/setup.d.ts.map +1 -1
  9. package/dist/commands/slashCommand.d.ts +32 -8
  10. package/dist/commands/slashCommand.d.ts.map +1 -1
  11. package/dist/contract/opencode.d.ts +1 -1
  12. package/dist/contract/opencode.d.ts.map +1 -1
  13. package/dist/dashboard/render.d.ts.map +1 -1
  14. package/dist/dashboard/state.d.ts +14 -6
  15. package/dist/dashboard/state.d.ts.map +1 -1
  16. package/dist/dashboard/types.d.ts +14 -3
  17. package/dist/dashboard/types.d.ts.map +1 -1
  18. package/dist/index.d.ts +8 -9
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +476 -802
  21. package/dist/orchestrator/coordinator.d.ts +14 -2
  22. package/dist/orchestrator/coordinator.d.ts.map +1 -1
  23. package/dist/plugin/capture.d.ts +46 -0
  24. package/dist/plugin/capture.d.ts.map +1 -0
  25. package/dist/plugin/hooks.d.ts +2 -2
  26. package/dist/plugin/hooks.d.ts.map +1 -1
  27. package/dist/plugin/toolcalls.d.ts +21 -0
  28. package/dist/plugin/toolcalls.d.ts.map +1 -0
  29. package/dist/telemetry/aggregate.d.ts +89 -0
  30. package/dist/telemetry/aggregate.d.ts.map +1 -0
  31. package/dist/telemetry/decisions.d.ts +25 -0
  32. package/dist/telemetry/decisions.d.ts.map +1 -0
  33. package/dist/telemetry/eventLog.d.ts +63 -0
  34. package/dist/telemetry/eventLog.d.ts.map +1 -0
  35. package/dist/telemetry/events.d.ts +217 -0
  36. package/dist/telemetry/events.d.ts.map +1 -0
  37. package/dist/web/snapshot.d.ts +13 -7
  38. package/dist/web/snapshot.d.ts.map +1 -1
  39. package/dist/web/start.d.ts +10 -7
  40. package/dist/web/start.d.ts.map +1 -1
  41. package/package.json +1 -1
  42. package/dist/web/activity.d.ts +0 -12
  43. 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 as readFile2, writeFile as writeFile2 } from "node:fs/promises";
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)}`;
@@ -307,21 +398,602 @@ function renderReport(report) {
307
398
  if (report.count === 0) {
308
399
  return "openteam report: sin registros de telemetría todavía.";
309
400
  }
310
- const lines = [
311
- "openteam report:",
312
- ` decisiones: ${report.count} (local ${report.localCount} · frontier ${report.frontierCount})`,
313
- ` coste real: ${usd2(report.totalEstimatedUSD)}`,
314
- ` baseline: ${usd2(report.totalBaselineUSD)}`,
315
- ` ahorro: ${usd2(report.totalSavingsUSD)} (${report.savingsPct.toFixed(2)}%)`,
316
- ` tokens: in ${report.tokensIn} · out ${report.tokensOut}`,
317
- " por tier:"
318
- ];
319
- for (const tier of TIERS2) {
320
- const summary = report.byTier[tier];
321
- lines.push(` ${tier.padEnd(9)} ${summary.count} · coste ${usd2(summary.estimatedUSD)} · ahorro ${usd2(summary.savingsUSD)}`);
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;
921
+ }
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;
969
+ }
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
+ }
322
977
  }
323
- return lines.join(`
324
- `);
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
+ }
995
+ }
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 recentActivity(entries, limit) {
394
- return [...entries].sort((a, b) => b.ts - a.ts).slice(0, Math.max(0, limit));
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(inputs.costRecords),
413
- recentRoutes: recentRoutes(inputs.costRecords, recentRoutesLimit),
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: recentActivity(inputs.activity, activityLimit)
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/read.ts
707
- import { readFile } from "node:fs/promises";
708
-
709
- // src/telemetry/types.ts
710
- import { z as z3 } from "zod";
711
-
712
- // src/capabilities/types.ts
713
- import { z } from "zod";
714
- var CapabilityTierSchema = z.union([
715
- z.literal(0),
716
- z.literal(1),
717
- z.literal(2),
718
- z.literal(3),
719
- z.literal(4),
720
- z.literal(5)
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 trimmed = line.trim();
868
- if (trimmed.length === 0) {
1443
+ const bullet = BULLET_RE.exec(line);
1444
+ if (bullet === null) {
869
1445
  continue;
870
1446
  }
871
- let candidate;
872
- try {
873
- candidate = JSON.parse(trimmed);
874
- } catch {
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 parsed = CostRecordSchema.safeParse(candidate);
878
- if (parsed.success) {
879
- records.push(parsed.data);
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
- return records;
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
- throw error;
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 [telemetryText, backlogText, agentFiles, lastCommit] = await Promise.all([
904
- deps.readText(paths.telemetryPath),
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 costRecords = parseCostRecordsJsonl(telemetryText ?? "");
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
- costRecords,
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, ms) => setTimeout(cb, ms));
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 activity = createActivityBuffer(deps.config.recentRoutes);
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
- telemetryPath: deps.telemetryPath,
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.telemetryPath, backlogPath, deps.agentDir], () => {
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
- telemetryPath: deps.telemetryPath,
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: habilitado.",
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
- "Nota: solo escucha en loopback y nunca expone prompts (solo hashes).",
1892
- "El servidor corre dentro de la sesión de opencode; se cierra al salir.",
1893
- "Para verlo fuera de opencode: openteam dashboard --serve"
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 Estado y URL del dashboard web (loopback)",
2161
- " openteam dashboard --serve Levanta el dashboard web (Ctrl+C para parar; --open abre el navegador)",
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)",
@@ -2387,13 +2983,21 @@ async function writeOpenTeamConfigFile(config, path = DEFAULT_CONFIG_PATH, deps
2387
2983
  }
2388
2984
 
2389
2985
  // src/commands/slashCommand.ts
2390
- var OPENTEAM_COMMAND_PATH = ".opencode/command/openteam.md";
2391
- function buildOpenteamCommand() {
2986
+ var OPENTEAM_COMMAND_DIR = ".opencode/command";
2987
+ var OPENTEAM_COMMAND_PATH = `${OPENTEAM_COMMAND_DIR}/openteam.md`;
2988
+ function renderCommand(description, body) {
2989
+ return ["---", `description: ${description}`, "---", "", body, ""].join(`
2990
+ `);
2991
+ }
2992
+ function fixedActionBody(action) {
2392
2993
  return [
2393
- "---",
2394
- "description: Comandos runtime de openteam (baseline show|set|auto, doctor, agents, dashboard, report)",
2395
- "---",
2396
- "",
2994
+ `Llama a la herramienta \`openteam\` una sola vez con \`action: "${action}"\`.`,
2995
+ "Devuelve su salida tal cual, sin reinterpretarla."
2996
+ ].join(`
2997
+ `);
2998
+ }
2999
+ function buildOpenteamCommand() {
3000
+ return renderCommand("Comandos runtime de openteam (baseline show|set|auto, doctor, agents, dashboard, report)", [
2397
3001
  "Usa la herramienta `openteam` para ejecutar el comando indicado por el usuario: $ARGUMENTS",
2398
3002
  "",
2399
3003
  "Interpreta los argumentos así y llama a la herramienta `openteam` una sola vez:",
@@ -2406,10 +3010,36 @@ function buildOpenteamCommand() {
2406
3010
  '- `dashboard` → `action: "dashboard"`',
2407
3011
  '- `report` → `action: "report"`',
2408
3012
  "",
2409
- "Devuelve la salida de la herramienta tal cual, sin reinterpretarla.",
2410
- ""
3013
+ "Devuelve la salida de la herramienta tal cual, sin reinterpretarla."
2411
3014
  ].join(`
2412
- `);
3015
+ `));
3016
+ }
3017
+ function buildBaselineCommand() {
3018
+ return renderCommand("openteam · baseline frontier cheapest-capable (show | set <provider/model> | auto)", [
3019
+ "Usa la herramienta `openteam` para el baseline frontier: $ARGUMENTS",
3020
+ "",
3021
+ '- vacío o `show` → `action: "show"`',
3022
+ '- `set <provider/model>` → `action: "set"`, `model: "<provider/model>"`',
3023
+ '- `auto` → `action: "auto"`',
3024
+ "",
3025
+ "Llama a la herramienta una sola vez y devuelve su salida tal cual."
3026
+ ].join(`
3027
+ `));
3028
+ }
3029
+ function buildOpenteamCommands() {
3030
+ const file = (name, contents) => ({
3031
+ name,
3032
+ path: `${OPENTEAM_COMMAND_DIR}/${name}.md`,
3033
+ contents
3034
+ });
3035
+ return [
3036
+ file("openteam", buildOpenteamCommand()),
3037
+ file("openteam-baseline", buildBaselineCommand()),
3038
+ file("openteam-doctor", renderCommand("openteam · diagnóstico de runtimes locales y configuración", fixedActionBody("doctor"))),
3039
+ file("openteam-agents", renderCommand("openteam · lista los agentes y el LLM (local/frontier + suscripción) de cada uno", fixedActionBody("agents"))),
3040
+ file("openteam-dashboard", renderCommand("openteam · estado y URL del dashboard web multi-sesión", fixedActionBody("dashboard"))),
3041
+ file("openteam-report", renderCommand("openteam · resumen de coste/ahorro (telemetría)", fixedActionBody("report")))
3042
+ ];
2413
3043
  }
2414
3044
 
2415
3045
  // src/commands/setup.ts
@@ -2759,11 +3389,10 @@ async function runSetup(deps) {
2759
3389
  const openTeamConfig = buildOpenTeamConfig(answers);
2760
3390
  const opencodeConfig = buildOpencodeConfig(answers);
2761
3391
  const orchestratorAgent = buildOrchestratorAgent(frontier, { yolo });
2762
- const slashCommand = buildOpenteamCommand();
3392
+ const slashCommands = buildOpenteamCommands();
2763
3393
  const openTeamPath = join(deps.cwd, DEFAULT_CONFIG_PATH);
2764
3394
  const opencodePath = join(deps.cwd, OPENCODE_CONFIG_PATH);
2765
3395
  const agentPath = join(deps.cwd, ORCHESTRATOR_AGENT_PATH);
2766
- const commandPath = join(deps.cwd, OPENTEAM_COMMAND_PATH);
2767
3396
  const existing = [];
2768
3397
  if (await deps.fileExists(openTeamPath)) {
2769
3398
  existing.push(DEFAULT_CONFIG_PATH);
@@ -2774,8 +3403,10 @@ async function runSetup(deps) {
2774
3403
  if (await deps.fileExists(agentPath)) {
2775
3404
  existing.push(ORCHESTRATOR_AGENT_PATH);
2776
3405
  }
2777
- if (await deps.fileExists(commandPath)) {
2778
- existing.push(OPENTEAM_COMMAND_PATH);
3406
+ for (const command of slashCommands) {
3407
+ if (await deps.fileExists(join(deps.cwd, command.path))) {
3408
+ existing.push(command.path);
3409
+ }
2779
3410
  }
2780
3411
  if (existing.length > 0) {
2781
3412
  const overwrite = await prompt.confirm({
@@ -2790,25 +3421,27 @@ async function runSetup(deps) {
2790
3421
  await deps.writeFile(openTeamPath, serializeOpenTeamConfig(openTeamConfig));
2791
3422
  await deps.writeFile(opencodePath, serializeOpencodeConfig(opencodeConfig));
2792
3423
  await deps.writeFile(agentPath, orchestratorAgent);
2793
- await deps.writeFile(commandPath, slashCommand);
3424
+ for (const command of slashCommands) {
3425
+ await deps.writeFile(join(deps.cwd, command.path), command.contents);
3426
+ }
2794
3427
  const enabledSummary = runtimes.filter((runtime) => runtime.enabled).map((runtime) => `${runtime.id} (${runtime.defaultModelID})`).join(", ");
2795
3428
  prompt.note([
2796
3429
  `Baseline frontier: ${frontier.providerID}/${frontier.modelID}`,
2797
3430
  `Runtimes locales: ${enabledSummary || "ninguno habilitado"}`,
2798
3431
  `Modo YOLO: ${yolo ? "activado (auto-aprueba permisos)" : "desactivado"}`,
2799
3432
  `Dashboard web: ${dashboard ? "activado (http://127.0.0.1:4599 mientras opencode esté abierto)" : "desactivado"}`,
2800
- `Escrito: ${OPENCODE_CONFIG_PATH}, ${DEFAULT_CONFIG_PATH}, ${ORCHESTRATOR_AGENT_PATH}, ${OPENTEAM_COMMAND_PATH}`,
3433
+ `Escrito: ${OPENCODE_CONFIG_PATH}, ${DEFAULT_CONFIG_PATH}, ${ORCHESTRATOR_AGENT_PATH}, ${OPENTEAM_COMMAND_DIR}/*.md (${slashCommands.length} comandos)`,
2801
3434
  "",
2802
3435
  "Siguientes pasos:",
2803
3436
  ` 1. Autentica el provider frontier: opencode auth login`,
2804
3437
  reachableIds.length === 0 ? " 2. Arranca tu runtime local (Ollama/LM Studio/Foundry) antes de usarlo" : " 2. Abre opencode en este repo; openteam enruta local-first automáticamente",
2805
- " 3. Pulsa Tab y elige el agente 'openteam', o usa el slash command /openteam"
3438
+ " 3. Pulsa Tab y elige el agente 'openteam', o teclea / y elige un comando /openteam"
2806
3439
  ].join(`
2807
3440
  `), "openteam configurado");
2808
3441
  prompt.outro("Listo. Abre opencode y selecciona el agente 'openteam'.");
2809
3442
  return {
2810
3443
  exitCode: 0,
2811
- stdout: `openteam setup completado: ${OPENCODE_CONFIG_PATH}, ${DEFAULT_CONFIG_PATH}, ${ORCHESTRATOR_AGENT_PATH}, ${OPENTEAM_COMMAND_PATH}`
3444
+ stdout: `openteam setup completado: ${OPENCODE_CONFIG_PATH}, ${DEFAULT_CONFIG_PATH}, ${ORCHESTRATOR_AGENT_PATH}, ${OPENTEAM_COMMAND_DIR}/*.md`
2812
3445
  };
2813
3446
  } catch (error) {
2814
3447
  const message = error instanceof Error ? error.message : String(error);
@@ -2843,7 +3476,7 @@ function createGitLastCommit(exec) {
2843
3476
  // src/cli.ts
2844
3477
  var execFileAsync = promisify(execFile);
2845
3478
  var AGENT_DIR = dirname2(ORCHESTRATOR_AGENT_PATH);
2846
- function isMissingFile2(error) {
3479
+ function isMissingFile(error) {
2847
3480
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
2848
3481
  }
2849
3482
  var nodeExec = async (command, args) => {
@@ -2861,10 +3494,10 @@ var nodeExec = async (command, args) => {
2861
3494
  };
2862
3495
  async function loadConfig(path) {
2863
3496
  try {
2864
- const content = await readFile2(path, "utf8");
3497
+ const content = await readFile(path, "utf8");
2865
3498
  return loadOpenTeamConfig(JSON.parse(content));
2866
3499
  } catch (error) {
2867
- if (isMissingFile2(error)) {
3500
+ if (isMissingFile(error)) {
2868
3501
  return loadOpenTeamConfig({});
2869
3502
  }
2870
3503
  throw error;
@@ -2872,14 +3505,24 @@ async function loadConfig(path) {
2872
3505
  }
2873
3506
  async function readTextOptional(path) {
2874
3507
  try {
2875
- return await readFile2(path, "utf8");
3508
+ return await readFile(path, "utf8");
2876
3509
  } catch (error) {
2877
- if (isMissingFile2(error)) {
3510
+ if (isMissingFile(error)) {
2878
3511
  return;
2879
3512
  }
2880
3513
  throw error;
2881
3514
  }
2882
3515
  }
3516
+ async function listDirFiles(dir) {
3517
+ try {
3518
+ return await readdir(dir);
3519
+ } catch (error) {
3520
+ if (isMissingFile(error)) {
3521
+ return [];
3522
+ }
3523
+ throw error;
3524
+ }
3525
+ }
2883
3526
  async function openBrowser(url) {
2884
3527
  if (process.platform === "win32") {
2885
3528
  await nodeExec("cmd", ["/c", "start", "", url]);
@@ -2901,10 +3544,10 @@ var deps = {
2901
3544
  saveConfig: (config, path) => writeOpenTeamConfigFile(config, path),
2902
3545
  readOpencodeConfig: async (path) => {
2903
3546
  try {
2904
- const content = await readFile2(path, "utf8");
3547
+ const content = await readFile(path, "utf8");
2905
3548
  return JSON.parse(content);
2906
3549
  } catch (error) {
2907
- if (isMissingFile2(error)) {
3550
+ if (isMissingFile(error)) {
2908
3551
  return;
2909
3552
  }
2910
3553
  throw error;
@@ -2924,7 +3567,7 @@ var deps = {
2924
3567
  try {
2925
3568
  entries = await readdir(dir);
2926
3569
  } catch (error) {
2927
- if (isMissingFile2(error)) {
3570
+ if (isMissingFile(error)) {
2928
3571
  return [];
2929
3572
  }
2930
3573
  throw error;
@@ -2932,7 +3575,7 @@ var deps = {
2932
3575
  const mdFiles = entries.filter((entry) => entry.endsWith(".md"));
2933
3576
  return Promise.all(mdFiles.map(async (entry) => ({
2934
3577
  name: entry.replace(/\.md$/, ""),
2935
- contents: await readFile2(join2(dir, entry), "utf8")
3578
+ contents: await readFile(join2(dir, entry), "utf8")
2936
3579
  })));
2937
3580
  },
2938
3581
  probe: (config) => {
@@ -2943,7 +3586,11 @@ var deps = {
2943
3586
  });
2944
3587
  return registry.probe(config.local.runtimes);
2945
3588
  },
2946
- readTelemetry: (path) => readCostRecords(path),
3589
+ readTelemetry: (path) => readRouteCostRecords(DEFAULT_SESSIONS_DIR, {
3590
+ listFiles: listDirFiles,
3591
+ readText: readTextOptional,
3592
+ legacyTelemetryPath: path
3593
+ }),
2947
3594
  configPath: DEFAULT_CONFIG_PATH,
2948
3595
  telemetryPath: DEFAULT_TELEMETRY_PATH,
2949
3596
  opencodeConfigPath: OPENCODE_CONFIG_PATH,
@@ -2983,14 +3630,17 @@ async function main() {
2983
3630
  }
2984
3631
  if (argv[0] === "dashboard") {
2985
3632
  const flags = parseDashboardServeArgs(argv.slice(1));
2986
- if (flags.serve) {
3633
+ if (!flags.status) {
2987
3634
  const result2 = await runDashboardServe({
2988
3635
  loadConfig,
2989
3636
  configPath: DEFAULT_CONFIG_PATH,
2990
- telemetryPath: DEFAULT_TELEMETRY_PATH,
3637
+ sessionsDir: DEFAULT_SESSIONS_DIR,
3638
+ decisionsPath: DEFAULT_DECISIONS_PATH,
2991
3639
  backlogPath: DEFAULT_BACKLOG_PATH,
2992
3640
  agentDir: AGENT_DIR,
3641
+ legacyTelemetryPath: DEFAULT_TELEMETRY_PATH,
2993
3642
  readText: readTextOptional,
3643
+ listFiles: listDirFiles,
2994
3644
  listAgentFiles: deps.listAgentFiles,
2995
3645
  now: () => new Date().toISOString(),
2996
3646
  gitLastCommit: createGitLastCommit(nodeExec),