@jmanuelcorral/openteam 0.1.31 → 0.1.33
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 +23 -0
- package/dist/cli/consoleServe.d.ts +9 -0
- package/dist/cli/consoleServe.d.ts.map +1 -1
- package/dist/cli.js +1670 -1303
- package/dist/commands/dispatch.d.ts.map +1 -1
- package/dist/config/schema.d.ts +27 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/mcp/server.d.ts +50 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/stdio.d.ts +24 -0
- package/dist/mcp/stdio.d.ts.map +1 -0
- package/dist/mcp/teamMemory.d.ts +42 -0
- package/dist/mcp/teamMemory.d.ts.map +1 -0
- package/dist/storage/index/bunSqlite.d.ts +20 -0
- package/dist/storage/index/bunSqlite.d.ts.map +1 -0
- package/dist/storage/index/sqliteIndex.d.ts +64 -0
- package/dist/storage/index/sqliteIndex.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
2
4
|
|
|
3
5
|
// src/cli.ts
|
|
4
6
|
import { execFile, spawn } from "node:child_process";
|
|
@@ -301,1381 +303,1505 @@ function tokenFromQuery(query) {
|
|
|
301
303
|
return value === null || value === undefined || value.length === 0 ? undefined : value;
|
|
302
304
|
}
|
|
303
305
|
|
|
304
|
-
// src/
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
// src/web/paths.ts
|
|
308
|
-
var DEFAULT_BACKLOG_PATH = ".opencode/openteam-backlog.md";
|
|
309
|
-
var DEFAULT_DECISIONS_PATH = ".opencode/openteam-decisions.md";
|
|
310
|
-
|
|
311
|
-
// src/web/server.ts
|
|
312
|
-
import {
|
|
313
|
-
createServer as createHttpServer
|
|
314
|
-
} from "node:http";
|
|
315
|
-
|
|
316
|
-
// src/console/render.ts
|
|
317
|
-
var TIERS = [
|
|
318
|
-
"trivial",
|
|
319
|
-
"simple",
|
|
320
|
-
"moderate",
|
|
321
|
-
"hard"
|
|
322
|
-
];
|
|
323
|
-
function escapeHtml(value) {
|
|
324
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
325
|
-
}
|
|
326
|
-
function usd(value) {
|
|
327
|
-
return `$${value.toFixed(5)}`;
|
|
328
|
-
}
|
|
329
|
-
function pct(value) {
|
|
330
|
-
return `${value.toFixed(2)}%`;
|
|
306
|
+
// src/telemetry/aggregate.ts
|
|
307
|
+
function emptyToolcallStats() {
|
|
308
|
+
return { count: 0, ok: 0, failed: 0, totalDurationMs: 0, byTool: {} };
|
|
331
309
|
}
|
|
332
|
-
function
|
|
333
|
-
return
|
|
310
|
+
function emptySession(sessionID, ts) {
|
|
311
|
+
return {
|
|
312
|
+
sessionID,
|
|
313
|
+
firstTs: ts,
|
|
314
|
+
lastTs: ts,
|
|
315
|
+
routeCount: 0,
|
|
316
|
+
localCount: 0,
|
|
317
|
+
frontierCount: 0,
|
|
318
|
+
estimatedCostUSD: 0,
|
|
319
|
+
estimatedSavingsUSD: 0,
|
|
320
|
+
realCostUSD: 0,
|
|
321
|
+
messageCount: 0,
|
|
322
|
+
tokensIn: 0,
|
|
323
|
+
tokensOut: 0,
|
|
324
|
+
tokensReasoning: 0,
|
|
325
|
+
tokensCacheRead: 0,
|
|
326
|
+
tokensCacheWrite: 0,
|
|
327
|
+
toolcalls: emptyToolcallStats(),
|
|
328
|
+
models: [],
|
|
329
|
+
agents: []
|
|
330
|
+
};
|
|
334
331
|
}
|
|
335
|
-
function
|
|
336
|
-
|
|
332
|
+
function pushUnique(list, value) {
|
|
333
|
+
if (value !== undefined && !list.includes(value)) {
|
|
334
|
+
list.push(value);
|
|
335
|
+
}
|
|
337
336
|
}
|
|
338
|
-
function
|
|
339
|
-
|
|
337
|
+
function applyRoute(session, event) {
|
|
338
|
+
session.routeCount += 1;
|
|
339
|
+
if (event.routeKind === "local") {
|
|
340
|
+
session.localCount += 1;
|
|
341
|
+
} else {
|
|
342
|
+
session.frontierCount += 1;
|
|
343
|
+
}
|
|
344
|
+
session.estimatedCostUSD += event.estimatedCostUSD;
|
|
345
|
+
session.estimatedSavingsUSD += event.estimatedSavingsUSD;
|
|
346
|
+
pushUnique(session.models, `${event.selected.providerID}/${event.selected.modelID}`);
|
|
347
|
+
pushUnique(session.agents, event.agent);
|
|
340
348
|
}
|
|
341
|
-
function
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
`<div class="stat"><span class="k">Ahorro</span><span class="v good">${usd(cost.totalSavingsUSD)} (${pct(cost.savingsPct)})</span></div>`,
|
|
352
|
-
`<div class="stat"><span class="k">Tokens in</span><span class="v">${cost.tokensIn}</span></div>`,
|
|
353
|
-
`<div class="stat"><span class="k">Tokens out</span><span class="v">${cost.tokensOut}</span></div>`,
|
|
354
|
-
"</div>",
|
|
355
|
-
"</section>"
|
|
356
|
-
].join("");
|
|
349
|
+
function applyMessage(session, event) {
|
|
350
|
+
session.messageCount += 1;
|
|
351
|
+
session.realCostUSD += event.costUSD;
|
|
352
|
+
session.tokensIn += event.tokensIn;
|
|
353
|
+
session.tokensOut += event.tokensOut;
|
|
354
|
+
session.tokensReasoning += event.tokensReasoning;
|
|
355
|
+
session.tokensCacheRead += event.tokensCacheRead;
|
|
356
|
+
session.tokensCacheWrite += event.tokensCacheWrite;
|
|
357
|
+
pushUnique(session.models, `${event.providerID}/${event.modelID}`);
|
|
358
|
+
pushUnique(session.agents, event.agent);
|
|
357
359
|
}
|
|
358
|
-
function
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
"<table><thead><tr><th>Tier</th><th>Nº</th><th>Coste</th><th>Ahorro</th></tr></thead>",
|
|
367
|
-
`<tbody>${rows}</tbody></table>`,
|
|
368
|
-
"</section>"
|
|
369
|
-
].join("");
|
|
360
|
+
function applyToolUsage(stats, event) {
|
|
361
|
+
stats.count += 1;
|
|
362
|
+
stats.totalDurationMs += event.durationMs;
|
|
363
|
+
if (event.ok) {
|
|
364
|
+
stats.ok += 1;
|
|
365
|
+
} else {
|
|
366
|
+
stats.failed += 1;
|
|
367
|
+
}
|
|
370
368
|
}
|
|
371
|
-
function
|
|
372
|
-
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
369
|
+
function applyToolcall(session, event) {
|
|
370
|
+
applyToolUsage(session.toolcalls, event);
|
|
371
|
+
const byTool = session.toolcalls.byTool[event.tool] ?? {
|
|
372
|
+
count: 0,
|
|
373
|
+
ok: 0,
|
|
374
|
+
failed: 0,
|
|
375
|
+
totalDurationMs: 0
|
|
376
|
+
};
|
|
377
|
+
applyToolUsage(byTool, event);
|
|
378
|
+
session.toolcalls.byTool[event.tool] = byTool;
|
|
379
|
+
pushUnique(session.agents, event.agent);
|
|
376
380
|
}
|
|
377
|
-
function
|
|
378
|
-
if (
|
|
379
|
-
return
|
|
380
|
-
'<section class="panel" id="panel-loop">',
|
|
381
|
-
"<h2>Loop</h2>",
|
|
382
|
-
'<p class="muted">Sin backlog activo (no hay <code>openteam-backlog.md</code>).</p>',
|
|
383
|
-
"</section>"
|
|
384
|
-
].join("");
|
|
381
|
+
function applyEndpoint(session, event) {
|
|
382
|
+
if (session.endpoint !== undefined && session.endpoint.lastSeenTs >= event.ts) {
|
|
383
|
+
return;
|
|
385
384
|
}
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
`<p class="muted">${loop.done}/${loop.total} completados · ${loop.open} abiertos · ${loop.progressPct}%</p>`,
|
|
393
|
-
`<ul class="items">${items}</ul>`,
|
|
394
|
-
commit,
|
|
395
|
-
"</section>"
|
|
396
|
-
].join("");
|
|
397
|
-
}
|
|
398
|
-
function teamPanel(team) {
|
|
399
|
-
if (team.length === 0) {
|
|
400
|
-
return [
|
|
401
|
-
'<section class="panel" id="panel-team">',
|
|
402
|
-
"<h2>Equipo</h2>",
|
|
403
|
-
'<p class="muted">Sin agentes en <code>.opencode/agent/</code>.</p>',
|
|
404
|
-
"</section>"
|
|
405
|
-
].join("");
|
|
385
|
+
const endpoint = {
|
|
386
|
+
url: event.url,
|
|
387
|
+
lastSeenTs: event.ts
|
|
388
|
+
};
|
|
389
|
+
if (event.directory !== undefined) {
|
|
390
|
+
endpoint.directory = event.directory;
|
|
406
391
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
'<section class="panel" id="panel-team">',
|
|
410
|
-
"<h2>Equipo</h2>",
|
|
411
|
-
"<table><thead><tr><th>Agente</th><th>Modo</th><th>LLM</th></tr></thead>",
|
|
412
|
-
`<tbody>${rows}</tbody></table>`,
|
|
413
|
-
"</section>"
|
|
414
|
-
].join("");
|
|
415
|
-
}
|
|
416
|
-
function routeRow(route) {
|
|
417
|
-
return `<tr><td>${route.tier}</td><td class="${route.routeKind}">${route.routeKind}</td><td>${escapeHtml(route.model)}</td><td class="hash">${escapeHtml(route.promptHash)}</td><td>${usd(route.estimatedCostUSD)}</td><td class="good">${usd(route.estimatedSavingsUSD)}</td></tr>`;
|
|
418
|
-
}
|
|
419
|
-
function routesPanel(routes) {
|
|
420
|
-
if (routes.length === 0) {
|
|
421
|
-
return [
|
|
422
|
-
'<section class="panel" id="panel-routes">',
|
|
423
|
-
"<h2>Decisiones recientes</h2>",
|
|
424
|
-
'<p class="muted">Sin decisiones de routing todavía.</p>',
|
|
425
|
-
"</section>"
|
|
426
|
-
].join("");
|
|
392
|
+
if (event.worktree !== undefined) {
|
|
393
|
+
endpoint.worktree = event.worktree;
|
|
427
394
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
"<table><thead><tr><th>Tier</th><th>Ruta</th><th>Modelo</th><th>Prompt (hash)</th><th>Coste</th><th>Ahorro</th></tr></thead>",
|
|
433
|
-
`<tbody>${rows}</tbody></table>`,
|
|
434
|
-
"</section>"
|
|
435
|
-
].join("");
|
|
395
|
+
if (event.title !== undefined) {
|
|
396
|
+
endpoint.title = event.title;
|
|
397
|
+
}
|
|
398
|
+
session.endpoint = endpoint;
|
|
436
399
|
}
|
|
437
|
-
function
|
|
438
|
-
|
|
439
|
-
|
|
400
|
+
function mergeTool(target, source) {
|
|
401
|
+
target.count += source.count;
|
|
402
|
+
target.ok += source.ok;
|
|
403
|
+
target.failed += source.failed;
|
|
404
|
+
target.totalDurationMs += source.totalDurationMs;
|
|
440
405
|
}
|
|
441
|
-
function
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
406
|
+
function accumulateTotals(totals, session) {
|
|
407
|
+
totals.sessions += 1;
|
|
408
|
+
totals.routeCount += session.routeCount;
|
|
409
|
+
totals.localCount += session.localCount;
|
|
410
|
+
totals.frontierCount += session.frontierCount;
|
|
411
|
+
totals.estimatedCostUSD += session.estimatedCostUSD;
|
|
412
|
+
totals.estimatedSavingsUSD += session.estimatedSavingsUSD;
|
|
413
|
+
totals.realCostUSD += session.realCostUSD;
|
|
414
|
+
totals.messageCount += session.messageCount;
|
|
415
|
+
totals.tokensIn += session.tokensIn;
|
|
416
|
+
totals.tokensOut += session.tokensOut;
|
|
417
|
+
totals.tokensReasoning += session.tokensReasoning;
|
|
418
|
+
totals.tokensCacheRead += session.tokensCacheRead;
|
|
419
|
+
totals.tokensCacheWrite += session.tokensCacheWrite;
|
|
420
|
+
mergeTool(totals.toolcalls, session.toolcalls);
|
|
421
|
+
for (const [tool, usage] of Object.entries(session.toolcalls.byTool)) {
|
|
422
|
+
const target = totals.toolcalls.byTool[tool] ?? {
|
|
423
|
+
count: 0,
|
|
424
|
+
ok: 0,
|
|
425
|
+
failed: 0,
|
|
426
|
+
totalDurationMs: 0
|
|
427
|
+
};
|
|
428
|
+
mergeTool(target, usage);
|
|
429
|
+
totals.toolcalls.byTool[tool] = target;
|
|
449
430
|
}
|
|
450
|
-
const rows = activity.map(activityRow).join("");
|
|
451
|
-
return [
|
|
452
|
-
'<section class="panel" id="panel-activity">',
|
|
453
|
-
"<h2>Actividad</h2>",
|
|
454
|
-
`<ul class="timeline">${rows}</ul>`,
|
|
455
|
-
"</section>"
|
|
456
|
-
].join("");
|
|
457
431
|
}
|
|
458
|
-
function
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
`<div class="stat"><span class="k">Coste real</span><span class="v">${usd(totals.realCostUSD)}</span></div>`,
|
|
467
|
-
`<div class="stat"><span class="k">Ahorro est.</span><span class="v good">${usd(totals.estimatedSavingsUSD)}</span></div>`,
|
|
468
|
-
`<div class="stat"><span class="k">Tokens in</span><span class="v">${totals.tokensIn}</span></div>`,
|
|
469
|
-
`<div class="stat"><span class="k">Tokens out</span><span class="v">${totals.tokensOut}</span></div>`,
|
|
470
|
-
`<div class="stat"><span class="k">Reasoning</span><span class="v">${totals.tokensReasoning}</span></div>`,
|
|
471
|
-
`<div class="stat"><span class="k">Cache r/w</span><span class="v">${totals.tokensCacheRead}/${totals.tokensCacheWrite}</span></div>`,
|
|
472
|
-
`<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>`,
|
|
473
|
-
`<div class="stat"><span class="k">Dur. media tool</span><span class="v">${avgMs(t)}</span></div>`,
|
|
474
|
-
"</div>",
|
|
475
|
-
"</section>"
|
|
476
|
-
].join("");
|
|
477
|
-
}
|
|
478
|
-
function sessionRow(session) {
|
|
479
|
-
const models = session.models.map(escapeHtml).join("<br>") || "—";
|
|
480
|
-
return [
|
|
481
|
-
"<tr>",
|
|
482
|
-
`<td class="hash">${escapeHtml(session.sessionID)}</td>`,
|
|
483
|
-
`<td>${models}</td>`,
|
|
484
|
-
`<td><span class="local">${session.localCount}</span>/<span class="frontier">${session.frontierCount}</span></td>`,
|
|
485
|
-
`<td>${usd(session.realCostUSD)}</td>`,
|
|
486
|
-
`<td>${session.tokensIn}/${session.tokensOut}</td>`,
|
|
487
|
-
`<td>${session.toolcalls.count} (${avgMs(session.toolcalls)})</td>`,
|
|
488
|
-
"</tr>"
|
|
489
|
-
].join("");
|
|
490
|
-
}
|
|
491
|
-
function sessionsPanel(sessions) {
|
|
492
|
-
if (sessions.length === 0) {
|
|
493
|
-
return [
|
|
494
|
-
'<section class="panel" id="panel-sessions">',
|
|
495
|
-
"<h2>Sesiones</h2>",
|
|
496
|
-
'<p class="muted">Sin sesiones registradas todavía.</p>',
|
|
497
|
-
"</section>"
|
|
498
|
-
].join("");
|
|
432
|
+
function toDecision(event) {
|
|
433
|
+
const view = {
|
|
434
|
+
ts: event.ts,
|
|
435
|
+
sessionID: event.sessionID,
|
|
436
|
+
summary: event.summary
|
|
437
|
+
};
|
|
438
|
+
if (event.agent !== undefined) {
|
|
439
|
+
view.agent = event.agent;
|
|
499
440
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
"<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>",
|
|
505
|
-
`<tbody>${rows}</tbody></table>`,
|
|
506
|
-
"</section>"
|
|
507
|
-
].join("");
|
|
441
|
+
if (event.tags !== undefined) {
|
|
442
|
+
view.tags = [...event.tags];
|
|
443
|
+
}
|
|
444
|
+
return view;
|
|
508
445
|
}
|
|
509
|
-
function
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
|
|
446
|
+
function toMeeting(event) {
|
|
447
|
+
const view = {
|
|
448
|
+
ts: event.ts,
|
|
449
|
+
sessionID: event.sessionID,
|
|
450
|
+
batchID: event.batchID,
|
|
451
|
+
roles: [...event.roles]
|
|
452
|
+
};
|
|
453
|
+
if (event.purpose !== undefined) {
|
|
454
|
+
view.purpose = event.purpose;
|
|
455
|
+
}
|
|
456
|
+
if (event.decisionIDs !== undefined) {
|
|
457
|
+
view.decisionIDs = [...event.decisionIDs];
|
|
458
|
+
}
|
|
459
|
+
return view;
|
|
513
460
|
}
|
|
514
|
-
function
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
461
|
+
function toActivity(event) {
|
|
462
|
+
const view = {
|
|
463
|
+
ts: event.ts,
|
|
464
|
+
sessionID: event.sessionID,
|
|
465
|
+
kind: event.kind,
|
|
466
|
+
summary: event.summary
|
|
467
|
+
};
|
|
468
|
+
if (event.agent !== undefined) {
|
|
469
|
+
view.agent = event.agent;
|
|
522
470
|
}
|
|
523
|
-
|
|
524
|
-
return [
|
|
525
|
-
'<section class="panel" id="panel-decisions">',
|
|
526
|
-
"<h2>Decisiones</h2>",
|
|
527
|
-
`<ul class="timeline">${rows}</ul>`,
|
|
528
|
-
"</section>"
|
|
529
|
-
].join("");
|
|
471
|
+
return view;
|
|
530
472
|
}
|
|
531
|
-
function
|
|
532
|
-
|
|
533
|
-
const purpose = meeting.purpose === undefined ? "" : `: ${escapeHtml(meeting.purpose)}`;
|
|
534
|
-
return `<li class="agent"><span class="kind">reunión</span> <span class="who">${roles}</span>${purpose}</li>`;
|
|
473
|
+
function byTsDesc(list) {
|
|
474
|
+
return [...list].sort((a, b) => b.ts - a.ts);
|
|
535
475
|
}
|
|
536
|
-
function
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
476
|
+
function aggregateEvents(events) {
|
|
477
|
+
const sessions = new Map;
|
|
478
|
+
const decisions = [];
|
|
479
|
+
const meetings = [];
|
|
480
|
+
const activity = [];
|
|
481
|
+
const sessionFor = (sessionID, ts) => {
|
|
482
|
+
const existing = sessions.get(sessionID);
|
|
483
|
+
if (existing === undefined) {
|
|
484
|
+
const created = emptySession(sessionID, ts);
|
|
485
|
+
sessions.set(sessionID, created);
|
|
486
|
+
return created;
|
|
487
|
+
}
|
|
488
|
+
existing.firstTs = Math.min(existing.firstTs, ts);
|
|
489
|
+
existing.lastTs = Math.max(existing.lastTs, ts);
|
|
490
|
+
return existing;
|
|
491
|
+
};
|
|
492
|
+
for (const event of events) {
|
|
493
|
+
switch (event.type) {
|
|
494
|
+
case "route":
|
|
495
|
+
applyRoute(sessionFor(event.sessionID, event.ts), event);
|
|
496
|
+
break;
|
|
497
|
+
case "message":
|
|
498
|
+
applyMessage(sessionFor(event.sessionID, event.ts), event);
|
|
499
|
+
break;
|
|
500
|
+
case "toolcall":
|
|
501
|
+
applyToolcall(sessionFor(event.sessionID, event.ts), event);
|
|
502
|
+
break;
|
|
503
|
+
case "session-endpoint":
|
|
504
|
+
applyEndpoint(sessionFor(event.sessionID, event.ts), event);
|
|
505
|
+
break;
|
|
506
|
+
case "decision":
|
|
507
|
+
decisions.push(toDecision(event));
|
|
508
|
+
break;
|
|
509
|
+
case "meeting":
|
|
510
|
+
meetings.push(toMeeting(event));
|
|
511
|
+
break;
|
|
512
|
+
case "activity":
|
|
513
|
+
activity.push(toActivity(event));
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
544
516
|
}
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
517
|
+
const totals = {
|
|
518
|
+
sessions: 0,
|
|
519
|
+
routeCount: 0,
|
|
520
|
+
localCount: 0,
|
|
521
|
+
frontierCount: 0,
|
|
522
|
+
estimatedCostUSD: 0,
|
|
523
|
+
estimatedSavingsUSD: 0,
|
|
524
|
+
realCostUSD: 0,
|
|
525
|
+
messageCount: 0,
|
|
526
|
+
tokensIn: 0,
|
|
527
|
+
tokensOut: 0,
|
|
528
|
+
tokensReasoning: 0,
|
|
529
|
+
tokensCacheRead: 0,
|
|
530
|
+
tokensCacheWrite: 0,
|
|
531
|
+
toolcalls: emptyToolcallStats()
|
|
532
|
+
};
|
|
533
|
+
const summaries = [...sessions.values()].sort((a, b) => b.lastTs - a.lastTs);
|
|
534
|
+
for (const summary of summaries) {
|
|
535
|
+
summary.models.sort();
|
|
536
|
+
summary.agents.sort();
|
|
537
|
+
accumulateTotals(totals, summary);
|
|
538
|
+
}
|
|
539
|
+
return {
|
|
540
|
+
sessions: summaries,
|
|
541
|
+
totals,
|
|
542
|
+
decisions: byTsDesc(decisions),
|
|
543
|
+
meetings: byTsDesc(meetings),
|
|
544
|
+
activity: byTsDesc(activity)
|
|
545
|
+
};
|
|
552
546
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
return `<span class="badge status-${status}">${STATUS_LABELS[status]}</span>`;
|
|
547
|
+
|
|
548
|
+
// src/storage/path.ts
|
|
549
|
+
class StoragePathError extends Error {
|
|
550
|
+
constructor(message) {
|
|
551
|
+
super(message);
|
|
552
|
+
this.name = "StoragePathError";
|
|
553
|
+
}
|
|
561
554
|
}
|
|
562
|
-
function
|
|
563
|
-
const
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
555
|
+
function normalizeVirtualPath(input) {
|
|
556
|
+
const segments = [];
|
|
557
|
+
for (const rawSegment of input.split(/[\\/]+/)) {
|
|
558
|
+
if (rawSegment === "" || rawSegment === ".") {
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
if (rawSegment === "..") {
|
|
562
|
+
if (segments.length === 0) {
|
|
563
|
+
throw new StoragePathError(`la ruta "${input}" escapa por encima de la raíz`);
|
|
564
|
+
}
|
|
565
|
+
segments.pop();
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
segments.push(rawSegment);
|
|
569
|
+
}
|
|
570
|
+
return segments.join("/");
|
|
570
571
|
}
|
|
571
|
-
function
|
|
572
|
-
return
|
|
572
|
+
function joinVirtualPath(...segments) {
|
|
573
|
+
return normalizeVirtualPath(segments.join("/"));
|
|
573
574
|
}
|
|
574
|
-
function
|
|
575
|
-
const
|
|
576
|
-
|
|
577
|
-
const title = tab.endpoint?.title === undefined ? "" : `<div class="stat"><span class="k">Título</span><span class="v">${escapeHtml(tab.endpoint.title)}</span></div>`;
|
|
578
|
-
const s = tab.summary;
|
|
579
|
-
return [
|
|
580
|
-
'<section class="panel">',
|
|
581
|
-
`<h2>Sesión ${escapeHtml(shortSessionID(tab.sessionID))} ${statusBadge(tab.status)}</h2>`,
|
|
582
|
-
'<div class="grid">',
|
|
583
|
-
`<div class="stat"><span class="k">Endpoint</span><span class="v">${endpoint}</span></div>`,
|
|
584
|
-
dir,
|
|
585
|
-
title,
|
|
586
|
-
`<div class="stat"><span class="k">Coste real</span><span class="v">${usd(s.realCostUSD)}</span></div>`,
|
|
587
|
-
`<div class="stat"><span class="k">Tokens in/out</span><span class="v">${s.tokensIn}/${s.tokensOut}</span></div>`,
|
|
588
|
-
`<div class="stat"><span class="k">Local/Frontier</span><span class="v"><span class="local">${s.localCount}</span>/<span class="frontier">${s.frontierCount}</span></span></div>`,
|
|
589
|
-
`<div class="stat"><span class="k">Toolcalls</span><span class="v">${s.toolcalls.count} (${avgMs(s.toolcalls)})</span></div>`,
|
|
590
|
-
"</div>",
|
|
591
|
-
"</section>"
|
|
592
|
-
].join("");
|
|
575
|
+
function splitVirtualPath(path) {
|
|
576
|
+
const normalized = normalizeVirtualPath(path);
|
|
577
|
+
return normalized === "" ? [] : normalized.split("/");
|
|
593
578
|
}
|
|
594
|
-
function terminalPanel(tab) {
|
|
595
|
-
return [
|
|
596
|
-
'<section class="panel term">',
|
|
597
|
-
"<h2>Terminal (sesión)</h2>",
|
|
598
|
-
'<pre class="termlog" aria-live="polite"></pre>',
|
|
599
|
-
'<form class="terminput" autocomplete="off">',
|
|
600
|
-
`<input type="text" name="text" placeholder="Enviar prompt a ${escapeHtml(shortSessionID(tab.sessionID))}…" aria-label="Prompt a la sesión">`,
|
|
601
|
-
'<button type="submit">Enviar</button>',
|
|
602
|
-
"</form>",
|
|
603
|
-
"</section>"
|
|
604
|
-
].join("");
|
|
605
|
-
}
|
|
606
|
-
function ptyPanel(tab) {
|
|
607
|
-
return [
|
|
608
|
-
`<section class="panel pty" data-pty-session="${escapeHtml(tab.sessionID)}">`,
|
|
609
|
-
"<h2>Terminal (PTY)</h2>",
|
|
610
|
-
'<pre class="ptylog" aria-live="polite"></pre>',
|
|
611
|
-
'<form class="ptyinput" autocomplete="off" hidden>',
|
|
612
|
-
'<input type="text" name="cmd" placeholder="Comando…" aria-label="Comando de la terminal">',
|
|
613
|
-
'<button type="submit">Ejecutar</button>',
|
|
614
|
-
"</form>",
|
|
615
|
-
'<button type="button" class="pty-start">Abrir shell</button>',
|
|
616
|
-
"</section>"
|
|
617
|
-
].join("");
|
|
618
|
-
}
|
|
619
|
-
function sessionView(tab, terminalEnabled, ptyEnabled) {
|
|
620
|
-
const body = [
|
|
621
|
-
sessionHeaderPanel(tab),
|
|
622
|
-
terminalEnabled ? terminalPanel(tab) : "",
|
|
623
|
-
terminalEnabled && ptyEnabled ? ptyPanel(tab) : "",
|
|
624
|
-
routesPanel(tab.recentRoutes),
|
|
625
|
-
decisionsPanel(tab.decisions),
|
|
626
|
-
meetingsPanel(tab.meetings),
|
|
627
|
-
activityPanel(tab.activity)
|
|
628
|
-
].join("");
|
|
629
|
-
return `<div class="view" data-view="${escapeHtml(tab.sessionID)}" hidden>${body}</div>`;
|
|
630
|
-
}
|
|
631
|
-
var STYLE = `
|
|
632
|
-
:root{color-scheme:dark;--bg:#0f1115;--panel:#171a21;--fg:#e6e8eb;--muted:#8b929c;--good:#3fb950;--local:#58a6ff;--frontier:#d29922;--line:#262b34}
|
|
633
|
-
*{box-sizing:border-box}
|
|
634
|
-
body{margin:0;background:var(--bg);color:var(--fg);font:14px/1.5 ui-sans-serif,system-ui,Segoe UI,Roboto,sans-serif}
|
|
635
|
-
header{padding:16px 24px;border-bottom:1px solid var(--line);display:flex;justify-content:space-between;align-items:baseline;gap:12px;flex-wrap:wrap}
|
|
636
|
-
header h1{font-size:18px;margin:0}
|
|
637
|
-
header .meta{color:var(--muted);font-size:12px}
|
|
638
|
-
main{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:16px;padding:24px}
|
|
639
|
-
.panel{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:16px}
|
|
640
|
-
.panel h2{font-size:14px;margin:0 0 12px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}
|
|
641
|
-
.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}
|
|
642
|
-
.stat{display:flex;flex-direction:column;gap:2px}
|
|
643
|
-
.stat .k{color:var(--muted);font-size:12px}
|
|
644
|
-
.stat .v{font-size:18px;font-weight:600}
|
|
645
|
-
.good{color:var(--good)}
|
|
646
|
-
.local{color:var(--local)}
|
|
647
|
-
.frontier{color:var(--frontier)}
|
|
648
|
-
.muted{color:var(--muted)}
|
|
649
|
-
table{width:100%;border-collapse:collapse;font-size:13px}
|
|
650
|
-
th,td{text-align:left;padding:6px 8px;border-bottom:1px solid var(--line)}
|
|
651
|
-
th{color:var(--muted);font-weight:600}
|
|
652
|
-
.hash{font-family:ui-monospace,Consolas,monospace;color:var(--muted)}
|
|
653
|
-
.progress{height:10px;background:#0b0d11;border-radius:6px;overflow:hidden;border:1px solid var(--line)}
|
|
654
|
-
.progress .bar{height:100%;background:var(--good)}
|
|
655
|
-
ul.items,ul.timeline{list-style:none;margin:8px 0 0;padding:0;max-height:320px;overflow:auto}
|
|
656
|
-
ul.items li,ul.timeline li{padding:4px 0;border-bottom:1px solid var(--line)}
|
|
657
|
-
ul.items li.done{color:var(--muted)}
|
|
658
|
-
ul.items .box{font-family:monospace}
|
|
659
|
-
.who{color:var(--local)}
|
|
660
|
-
.tag{color:var(--frontier);font-size:12px}
|
|
661
|
-
.kind{display:inline-block;min-width:66px;color:var(--muted);font-size:12px}
|
|
662
|
-
code{font-family:ui-monospace,Consolas,monospace;background:#0b0d11;padding:1px 5px;border-radius:4px}
|
|
663
|
-
.mono{font-family:ui-monospace,Consolas,monospace;font-size:12px}
|
|
664
|
-
.tabs{display:flex;gap:6px;padding:12px 24px 0;flex-wrap:wrap;border-bottom:1px solid var(--line)}
|
|
665
|
-
.tabs .tab{background:var(--panel);color:var(--fg);border:1px solid var(--line);border-bottom:none;border-radius:8px 8px 0 0;padding:8px 14px;cursor:pointer;font:inherit;display:flex;align-items:center;gap:6px}
|
|
666
|
-
.tabs .tab.active{background:var(--bg);border-color:var(--local);color:var(--fg)}
|
|
667
|
-
.dot{width:8px;height:8px;border-radius:50%;display:inline-block;background:var(--muted)}
|
|
668
|
-
.badge{font-size:11px;padding:1px 8px;border-radius:10px;border:1px solid var(--line);color:var(--muted);vertical-align:middle}
|
|
669
|
-
.status-running{background:var(--good)}
|
|
670
|
-
.badge.status-running{color:var(--good);border-color:var(--good)}
|
|
671
|
-
.status-idle{background:var(--muted)}
|
|
672
|
-
.badge.status-idle{color:var(--muted)}
|
|
673
|
-
.status-waiting-input{background:var(--frontier)}
|
|
674
|
-
.badge.status-waiting-input{color:var(--frontier);border-color:var(--frontier)}
|
|
675
|
-
.status-ended{background:#6e7681}
|
|
676
|
-
.badge.status-ended{color:#6e7681}
|
|
677
|
-
.term .termlog{background:#0b0d11;border:1px solid var(--line);border-radius:8px;padding:10px;max-height:340px;overflow:auto;font-family:ui-monospace,Consolas,monospace;font-size:12px;white-space:pre-wrap}
|
|
678
|
-
.term .tl{padding:1px 0}
|
|
679
|
-
.term .tl.input{color:var(--local)}
|
|
680
|
-
.term .tl.tool{color:var(--frontier)}
|
|
681
|
-
.term .tl.status{color:var(--muted)}
|
|
682
|
-
.term .tl.error{color:#f85149}
|
|
683
|
-
.term .tl.permission{color:var(--frontier);font-weight:600}
|
|
684
|
-
.term form.terminput{display:flex;gap:8px;margin-top:10px}
|
|
685
|
-
.term form.terminput input{flex:1;background:#0b0d11;color:var(--fg);border:1px solid var(--line);border-radius:8px;padding:8px 10px;font:inherit}
|
|
686
|
-
.term form.terminput button{background:var(--local);color:#04122b;border:none;border-radius:8px;padding:8px 16px;cursor:pointer;font:inherit;font-weight:600}
|
|
687
|
-
.pty .ptylog{background:#05070a;border:1px solid var(--line);border-radius:8px;padding:10px;max-height:340px;overflow:auto;font-family:ui-monospace,Consolas,monospace;font-size:12px;white-space:pre-wrap;color:#c8d1dc}
|
|
688
|
-
.pty form.ptyinput{display:flex;gap:8px;margin-top:10px}
|
|
689
|
-
.pty form.ptyinput input{flex:1;background:#05070a;color:var(--fg);border:1px solid var(--line);border-radius:8px;padding:8px 10px;font:inherit}
|
|
690
|
-
.pty form.ptyinput button,.pty .pty-start{background:var(--frontier);color:#1a1200;border:none;border-radius:8px;padding:8px 16px;cursor:pointer;font:inherit;font-weight:600;margin-top:8px}
|
|
691
|
-
.pty .pty-start:disabled{opacity:.5;cursor:default}
|
|
692
|
-
`;
|
|
693
|
-
var CLIENT_JS = `
|
|
694
|
-
(function(){
|
|
695
|
-
function reloadIfChanged(prev){
|
|
696
|
-
fetch('/api/state').then(function(r){return r.json()}).then(function(s){
|
|
697
|
-
if(s.generatedAt!==prev){location.reload()}
|
|
698
|
-
}).catch(function(){});
|
|
699
|
-
}
|
|
700
|
-
var current=document.documentElement.getAttribute('data-generated')||'';
|
|
701
|
-
if('EventSource' in window){
|
|
702
|
-
try{
|
|
703
|
-
var es=new EventSource('/events');
|
|
704
|
-
es.addEventListener('state',function(){location.reload()});
|
|
705
|
-
es.onerror=function(){/* fallback below */};
|
|
706
|
-
}catch(e){/* ignore */}
|
|
707
|
-
}
|
|
708
|
-
var ms=parseInt(document.documentElement.getAttribute('data-refresh')||'2000',10);
|
|
709
|
-
setInterval(function(){reloadIfChanged(current)},isNaN(ms)?2000:Math.max(250,ms));
|
|
710
579
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
for(var i=0;i<tabs.length;i++){tabs[i].classList.toggle('active',tabs[i].getAttribute('data-tab')===name)}
|
|
714
|
-
var views=document.querySelectorAll('main .view');
|
|
715
|
-
var matched=false;
|
|
716
|
-
for(var j=0;j<views.length;j++){
|
|
717
|
-
var show=views[j].getAttribute('data-view')===name;
|
|
718
|
-
views[j].hidden=!show;
|
|
719
|
-
if(show){matched=true;if(name!=='all'){connectTerminal(views[j],name)}}
|
|
720
|
-
}
|
|
721
|
-
if(!matched){return false}
|
|
722
|
-
try{sessionStorage.setItem('openteam.tab',name)}catch(e){}
|
|
723
|
-
return true;
|
|
724
|
-
}
|
|
725
|
-
function connectTerminal(view,id){
|
|
726
|
-
if(view.__wired){return}
|
|
727
|
-
var root=document.documentElement;
|
|
728
|
-
var token=root.getAttribute('data-token');
|
|
729
|
-
if(root.getAttribute('data-terminal')!=='1'||!token){return}
|
|
730
|
-
var log=view.querySelector('.termlog');
|
|
731
|
-
if(!log){return}
|
|
732
|
-
view.__wired=true;
|
|
733
|
-
function append(cls,text){
|
|
734
|
-
var line=document.createElement('div');
|
|
735
|
-
line.className='tl '+cls;line.textContent=text;
|
|
736
|
-
log.appendChild(line);log.scrollTop=log.scrollHeight;
|
|
737
|
-
}
|
|
738
|
-
var q='/console/session/'+encodeURIComponent(id);
|
|
739
|
-
var tk='token='+encodeURIComponent(token);
|
|
740
|
-
if('EventSource' in window){
|
|
741
|
-
try{
|
|
742
|
-
var es=new EventSource(q+'/stream?'+tk);
|
|
743
|
-
es.addEventListener('frame',function(e){try{var f=JSON.parse(e.data);append(f.kind,f.text)}catch(_){}});
|
|
744
|
-
es.onerror=function(){/* reconecta solo */};
|
|
745
|
-
}catch(_){/* ignore */}
|
|
746
|
-
}
|
|
747
|
-
var form=view.querySelector('form.terminput');
|
|
748
|
-
if(form){
|
|
749
|
-
form.addEventListener('submit',function(e){
|
|
750
|
-
e.preventDefault();
|
|
751
|
-
var inp=form.querySelector('input[name=text]');
|
|
752
|
-
var text=inp.value;
|
|
753
|
-
if(!text){return}
|
|
754
|
-
inp.value='';append('input','› '+text);
|
|
755
|
-
fetch(q+'/input?'+tk,{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({text:text})})
|
|
756
|
-
.then(function(r){if(!r.ok){append('error','✖ envío rechazado ('+r.status+')')}})
|
|
757
|
-
.catch(function(){append('error','✖ no se pudo enviar')});
|
|
758
|
-
});
|
|
759
|
-
}
|
|
760
|
-
connectPty(view,id,q,tk);
|
|
761
|
-
}
|
|
762
|
-
function connectPty(view,id,q,tk){
|
|
763
|
-
var root=document.documentElement;
|
|
764
|
-
if(root.getAttribute('data-pty')!=='1'){return}
|
|
765
|
-
var pane=view.querySelector('.pty');
|
|
766
|
-
if(!pane){return}
|
|
767
|
-
var log=pane.querySelector('.ptylog');
|
|
768
|
-
var startBtn=pane.querySelector('.pty-start');
|
|
769
|
-
var input=pane.querySelector('form.ptyinput');
|
|
770
|
-
function plog(text){
|
|
771
|
-
var d=document.createElement('span');d.textContent=text;
|
|
772
|
-
log.appendChild(d);log.scrollTop=log.scrollHeight;
|
|
773
|
-
}
|
|
774
|
-
if(!startBtn){return}
|
|
775
|
-
startBtn.addEventListener('click',function(){
|
|
776
|
-
startBtn.disabled=true;plog('Abriendo shell…
|
|
777
|
-
');
|
|
778
|
-
fetch(q+'/pty?'+tk,{method:'POST',headers:{'content-type':'application/json'},body:'{}'})
|
|
779
|
-
.then(function(r){return r.ok?r.json():Promise.reject(r.status)})
|
|
780
|
-
.then(function(info){openPtyWs(info.wsUrl)})
|
|
781
|
-
.catch(function(s){plog('✖ no se pudo abrir PTY ('+s+')
|
|
782
|
-
');startBtn.disabled=false});
|
|
783
|
-
});
|
|
784
|
-
function openPtyWs(url){
|
|
785
|
-
try{
|
|
786
|
-
var ws=new WebSocket(url);
|
|
787
|
-
ws.binaryType='arraybuffer';
|
|
788
|
-
ws.onmessage=function(e){
|
|
789
|
-
var data=e.data;
|
|
790
|
-
if(data instanceof ArrayBuffer){data=new TextDecoder().decode(new Uint8Array(data))}
|
|
791
|
-
plog(data);
|
|
792
|
-
};
|
|
793
|
-
ws.onclose=function(){plog('
|
|
794
|
-
[shell cerrada]
|
|
795
|
-
');startBtn.disabled=false};
|
|
796
|
-
ws.onerror=function(){plog('
|
|
797
|
-
✖ error de WebSocket
|
|
798
|
-
')};
|
|
799
|
-
if(input){
|
|
800
|
-
input.hidden=false;
|
|
801
|
-
input.addEventListener('submit',function(e){
|
|
802
|
-
e.preventDefault();
|
|
803
|
-
var c=input.querySelector('input[name=cmd]');
|
|
804
|
-
if(!c.value){return}
|
|
805
|
-
ws.send(c.value+'\r');c.value='';
|
|
806
|
-
});
|
|
807
|
-
}
|
|
808
|
-
}catch(_){plog('
|
|
809
|
-
✖ WebSocket no disponible
|
|
810
|
-
');startBtn.disabled=false}
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
var bar=document.getElementById('tabbar');
|
|
814
|
-
if(bar){
|
|
815
|
-
bar.addEventListener('click',function(e){
|
|
816
|
-
var t=e.target;
|
|
817
|
-
while(t&&t!==bar&&!t.getAttribute('data-tab')){t=t.parentNode}
|
|
818
|
-
if(t&&t.getAttribute('data-tab')){selectTab(t.getAttribute('data-tab'))}
|
|
819
|
-
});
|
|
820
|
-
}
|
|
821
|
-
var saved='all';
|
|
822
|
-
try{saved=sessionStorage.getItem('openteam.tab')||'all'}catch(e){}
|
|
823
|
-
if(!selectTab(saved)){selectTab('all')}
|
|
824
|
-
})();
|
|
825
|
-
`;
|
|
826
|
-
function renderConsoleHtml(state, options = {}) {
|
|
827
|
-
const refreshMs = options.refreshMs ?? 2000;
|
|
828
|
-
const terminalEnabled = options.terminalEnabled === true;
|
|
829
|
-
const ptyEnabled = options.ptyEnabled === true;
|
|
830
|
-
const allBody = [
|
|
831
|
-
summaryPanel(state.cost),
|
|
832
|
-
totalsPanel(state.totals),
|
|
833
|
-
sessionsPanel(state.sessions),
|
|
834
|
-
loopPanel(state.loop),
|
|
835
|
-
teamPanel(state.team),
|
|
836
|
-
tierPanel(state.cost),
|
|
837
|
-
routesPanel(state.recentRoutes),
|
|
838
|
-
decisionsPanel(state.decisions),
|
|
839
|
-
meetingsPanel(state.meetings),
|
|
840
|
-
activityPanel(state.activity)
|
|
841
|
-
].join("");
|
|
842
|
-
const allView = `<div class="view" data-view="all">${allBody}</div>`;
|
|
843
|
-
const sessionViews = state.sessionsDetail.map((tab) => sessionView(tab, terminalEnabled, ptyEnabled)).join("");
|
|
844
|
-
const body = allView + sessionViews;
|
|
845
|
-
const session = state.session.id === undefined ? "" : ` · sesión ${escapeHtml(state.session.id)}`;
|
|
846
|
-
const tokenAttr = terminalEnabled && options.consoleToken !== undefined ? ` data-token="${escapeHtml(options.consoleToken)}" data-terminal="1"${ptyEnabled ? ' data-pty="1"' : ""}` : "";
|
|
847
|
-
return [
|
|
848
|
-
"<!doctype html>",
|
|
849
|
-
`<html lang="es" data-generated="${escapeHtml(state.generatedAt)}" data-refresh="${refreshMs}"${tokenAttr}>`,
|
|
850
|
-
"<head>",
|
|
851
|
-
'<meta charset="utf-8">',
|
|
852
|
-
'<meta name="viewport" content="width=device-width,initial-scale=1">',
|
|
853
|
-
"<title>openteam console</title>",
|
|
854
|
-
`<style>${STYLE}</style>`,
|
|
855
|
-
"</head>",
|
|
856
|
-
"<body>",
|
|
857
|
-
"<header>",
|
|
858
|
-
"<h1>openteam console</h1>",
|
|
859
|
-
`<span class="meta">actualizado ${escapeHtml(state.generatedAt)}${session}</span>`,
|
|
860
|
-
"</header>",
|
|
861
|
-
tabBar(state.sessionsDetail),
|
|
862
|
-
`<main>${body}</main>`,
|
|
863
|
-
`<script>${CLIENT_JS}</script>`,
|
|
864
|
-
"</body>",
|
|
865
|
-
"</html>"
|
|
866
|
-
].join("");
|
|
867
|
-
}
|
|
580
|
+
// src/telemetry/events.ts
|
|
581
|
+
import { z as z4 } from "zod";
|
|
868
582
|
|
|
869
|
-
// src/
|
|
870
|
-
|
|
583
|
+
// src/capabilities/types.ts
|
|
584
|
+
import { z as z2 } from "zod";
|
|
585
|
+
var CapabilityTierSchema = z2.union([
|
|
586
|
+
z2.literal(0),
|
|
587
|
+
z2.literal(1),
|
|
588
|
+
z2.literal(2),
|
|
589
|
+
z2.literal(3),
|
|
590
|
+
z2.literal(4),
|
|
591
|
+
z2.literal(5)
|
|
592
|
+
]);
|
|
593
|
+
var ComplexityTierSchema = z2.enum([
|
|
871
594
|
"trivial",
|
|
872
595
|
"simple",
|
|
873
596
|
"moderate",
|
|
874
597
|
"hard"
|
|
875
|
-
];
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
598
|
+
]);
|
|
599
|
+
var ModelCapabilityProfileSchema = z2.object({
|
|
600
|
+
ref: z2.object({
|
|
601
|
+
providerID: z2.string().min(1),
|
|
602
|
+
modelID: z2.string().min(1)
|
|
603
|
+
}),
|
|
604
|
+
kind: z2.enum(["local", "frontier", "router"]),
|
|
605
|
+
contextWindow: z2.number().int().positive(),
|
|
606
|
+
maxOutputTokens: z2.number().int().positive(),
|
|
607
|
+
supportsToolCalling: z2.boolean(),
|
|
608
|
+
supportsVision: z2.boolean(),
|
|
609
|
+
reasoningTier: CapabilityTierSchema,
|
|
610
|
+
codeQualityTier: CapabilityTierSchema,
|
|
611
|
+
costPer1M: z2.object({
|
|
612
|
+
inputUSD: z2.number().min(0),
|
|
613
|
+
outputUSD: z2.number().min(0)
|
|
614
|
+
}),
|
|
615
|
+
availability: z2.enum(["available", "degraded", "unavailable"])
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
// src/config/schema.ts
|
|
619
|
+
import { z as z3 } from "zod";
|
|
620
|
+
var ModelRefSchema = z3.object({
|
|
621
|
+
providerID: z3.string().min(1),
|
|
622
|
+
modelID: z3.string().min(1)
|
|
623
|
+
});
|
|
624
|
+
var RouterModeSchema = z3.enum(["economy", "balanced", "quality"]);
|
|
625
|
+
var PrivacyModeSchema = z3.enum([
|
|
626
|
+
"forceLocalOnSensitive",
|
|
627
|
+
"consentBeforeFrontier",
|
|
628
|
+
"off"
|
|
629
|
+
]);
|
|
630
|
+
var BaselineModeSchema = z3.enum(["auto", "pinned"]);
|
|
631
|
+
var ConsoleHostSchema = z3.enum(["127.0.0.1", "localhost"]);
|
|
632
|
+
var ConsoleConfigSchema = z3.object({
|
|
633
|
+
host: ConsoleHostSchema.default("127.0.0.1"),
|
|
634
|
+
port: z3.number().int().min(1024).max(65535).default(4599),
|
|
635
|
+
autoPortFallback: z3.boolean().default(true),
|
|
636
|
+
refreshMs: z3.number().int().min(250).default(2000),
|
|
637
|
+
recentRoutes: z3.number().int().positive().default(50),
|
|
638
|
+
openBrowser: z3.boolean().default(false),
|
|
639
|
+
remoteStorage: z3.boolean().default(false),
|
|
640
|
+
terminal: z3.object({
|
|
641
|
+
enabled: z3.boolean().default(true),
|
|
642
|
+
pty: z3.boolean().optional()
|
|
643
|
+
}).default({ enabled: true })
|
|
644
|
+
}).default({
|
|
645
|
+
host: "127.0.0.1",
|
|
646
|
+
port: 4599,
|
|
647
|
+
autoPortFallback: true,
|
|
648
|
+
refreshMs: 2000,
|
|
649
|
+
recentRoutes: 50,
|
|
650
|
+
openBrowser: false,
|
|
651
|
+
remoteStorage: false,
|
|
652
|
+
terminal: { enabled: true }
|
|
653
|
+
});
|
|
654
|
+
var StorageConfigSchema = z3.object({
|
|
655
|
+
sqliteIndex: z3.object({
|
|
656
|
+
enabled: z3.boolean().default(false),
|
|
657
|
+
path: z3.string().min(1).default(".opencode/openteam/index.sqlite")
|
|
658
|
+
}).default({ enabled: false, path: ".opencode/openteam/index.sqlite" })
|
|
659
|
+
}).default({
|
|
660
|
+
sqliteIndex: { enabled: false, path: ".opencode/openteam/index.sqlite" }
|
|
661
|
+
});
|
|
662
|
+
var defaultLocalModel = {
|
|
663
|
+
providerID: "ollama",
|
|
664
|
+
modelID: "qwen3:8b"
|
|
665
|
+
};
|
|
666
|
+
var defaultFrontierModel = {
|
|
667
|
+
providerID: "anthropic",
|
|
668
|
+
modelID: "claude-sonnet-4-5"
|
|
669
|
+
};
|
|
670
|
+
var LocalRuntimeSchema = z3.object({
|
|
671
|
+
id: z3.enum(["ollama", "lmstudio", "foundry-local"]),
|
|
672
|
+
enabled: z3.boolean().default(true),
|
|
673
|
+
baseURL: z3.string().url().optional(),
|
|
674
|
+
discovery: z3.enum(["cli", "sdk", "manual"]).optional(),
|
|
675
|
+
defaultModel: ModelRefSchema
|
|
676
|
+
});
|
|
677
|
+
var OpenTeamConfigObjectSchema = z3.object({
|
|
678
|
+
baseline: z3.object({
|
|
679
|
+
mode: BaselineModeSchema.default("auto"),
|
|
680
|
+
pinnedModel: ModelRefSchema.nullable().default(null),
|
|
681
|
+
hardDefault: ModelRefSchema.default(defaultFrontierModel)
|
|
682
|
+
}).default({
|
|
683
|
+
mode: "auto",
|
|
684
|
+
pinnedModel: null,
|
|
685
|
+
hardDefault: defaultFrontierModel
|
|
686
|
+
}),
|
|
687
|
+
router: z3.object({
|
|
688
|
+
mode: RouterModeSchema.default("balanced"),
|
|
689
|
+
localDefault: ModelRefSchema.default(defaultLocalModel),
|
|
690
|
+
trivialPromptMaxChars: z3.number().int().positive().default(280),
|
|
691
|
+
frontierPromptMinChars: z3.number().int().positive().default(2000)
|
|
692
|
+
}).default({
|
|
693
|
+
mode: "balanced",
|
|
694
|
+
localDefault: defaultLocalModel,
|
|
695
|
+
trivialPromptMaxChars: 280,
|
|
696
|
+
frontierPromptMinChars: 2000
|
|
697
|
+
}),
|
|
698
|
+
local: z3.object({
|
|
699
|
+
runtimes: z3.array(LocalRuntimeSchema).min(1).default([
|
|
700
|
+
{
|
|
701
|
+
id: "ollama",
|
|
702
|
+
enabled: true,
|
|
703
|
+
baseURL: "http://localhost:11434/v1",
|
|
704
|
+
defaultModel: { providerID: "ollama", modelID: "qwen3:8b" }
|
|
705
|
+
}
|
|
706
|
+
])
|
|
707
|
+
}).default({
|
|
708
|
+
runtimes: [
|
|
709
|
+
{
|
|
710
|
+
id: "ollama",
|
|
711
|
+
enabled: true,
|
|
712
|
+
baseURL: "http://localhost:11434/v1",
|
|
713
|
+
defaultModel: defaultLocalModel
|
|
714
|
+
}
|
|
715
|
+
]
|
|
716
|
+
}),
|
|
717
|
+
budgets: z3.object({
|
|
718
|
+
sessionUSD: z3.number().positive().optional(),
|
|
719
|
+
monthlyUSD: z3.number().positive().optional(),
|
|
720
|
+
frontierTokensPerSession: z3.number().int().positive().optional(),
|
|
721
|
+
hardStopOnBudgetExhaustion: z3.boolean().default(false)
|
|
722
|
+
}).default({ hardStopOnBudgetExhaustion: false }),
|
|
723
|
+
privacyMode: PrivacyModeSchema.default("forceLocalOnSensitive"),
|
|
724
|
+
console: ConsoleConfigSchema,
|
|
725
|
+
storage: StorageConfigSchema
|
|
726
|
+
});
|
|
727
|
+
var OpenTeamConfigSchema = OpenTeamConfigObjectSchema;
|
|
940
728
|
|
|
941
|
-
// src/telemetry/
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
session
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
totals.sessions += 1;
|
|
1043
|
-
totals.routeCount += session.routeCount;
|
|
1044
|
-
totals.localCount += session.localCount;
|
|
1045
|
-
totals.frontierCount += session.frontierCount;
|
|
1046
|
-
totals.estimatedCostUSD += session.estimatedCostUSD;
|
|
1047
|
-
totals.estimatedSavingsUSD += session.estimatedSavingsUSD;
|
|
1048
|
-
totals.realCostUSD += session.realCostUSD;
|
|
1049
|
-
totals.messageCount += session.messageCount;
|
|
1050
|
-
totals.tokensIn += session.tokensIn;
|
|
1051
|
-
totals.tokensOut += session.tokensOut;
|
|
1052
|
-
totals.tokensReasoning += session.tokensReasoning;
|
|
1053
|
-
totals.tokensCacheRead += session.tokensCacheRead;
|
|
1054
|
-
totals.tokensCacheWrite += session.tokensCacheWrite;
|
|
1055
|
-
mergeTool(totals.toolcalls, session.toolcalls);
|
|
1056
|
-
for (const [tool, usage] of Object.entries(session.toolcalls.byTool)) {
|
|
1057
|
-
const target = totals.toolcalls.byTool[tool] ?? {
|
|
1058
|
-
count: 0,
|
|
1059
|
-
ok: 0,
|
|
1060
|
-
failed: 0,
|
|
1061
|
-
totalDurationMs: 0
|
|
1062
|
-
};
|
|
1063
|
-
mergeTool(target, usage);
|
|
1064
|
-
totals.toolcalls.byTool[tool] = target;
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
function toDecision(event) {
|
|
1068
|
-
const view = {
|
|
1069
|
-
ts: event.ts,
|
|
1070
|
-
sessionID: event.sessionID,
|
|
1071
|
-
summary: event.summary
|
|
1072
|
-
};
|
|
1073
|
-
if (event.agent !== undefined) {
|
|
1074
|
-
view.agent = event.agent;
|
|
1075
|
-
}
|
|
1076
|
-
if (event.tags !== undefined) {
|
|
1077
|
-
view.tags = [...event.tags];
|
|
1078
|
-
}
|
|
1079
|
-
return view;
|
|
1080
|
-
}
|
|
1081
|
-
function toMeeting(event) {
|
|
1082
|
-
const view = {
|
|
1083
|
-
ts: event.ts,
|
|
1084
|
-
sessionID: event.sessionID,
|
|
1085
|
-
batchID: event.batchID,
|
|
1086
|
-
roles: [...event.roles]
|
|
1087
|
-
};
|
|
1088
|
-
if (event.purpose !== undefined) {
|
|
1089
|
-
view.purpose = event.purpose;
|
|
1090
|
-
}
|
|
1091
|
-
if (event.decisionIDs !== undefined) {
|
|
1092
|
-
view.decisionIDs = [...event.decisionIDs];
|
|
1093
|
-
}
|
|
1094
|
-
return view;
|
|
1095
|
-
}
|
|
1096
|
-
function toActivity(event) {
|
|
1097
|
-
const view = {
|
|
1098
|
-
ts: event.ts,
|
|
1099
|
-
sessionID: event.sessionID,
|
|
1100
|
-
kind: event.kind,
|
|
1101
|
-
summary: event.summary
|
|
1102
|
-
};
|
|
1103
|
-
if (event.agent !== undefined) {
|
|
1104
|
-
view.agent = event.agent;
|
|
1105
|
-
}
|
|
1106
|
-
return view;
|
|
1107
|
-
}
|
|
1108
|
-
function byTsDesc(list) {
|
|
1109
|
-
return [...list].sort((a, b) => b.ts - a.ts);
|
|
729
|
+
// src/telemetry/events.ts
|
|
730
|
+
var EVENT_SCHEMA_VERSION = 1;
|
|
731
|
+
var EventBaseSchema = z4.object({
|
|
732
|
+
v: z4.literal(EVENT_SCHEMA_VERSION),
|
|
733
|
+
ts: z4.number().finite(),
|
|
734
|
+
sessionID: z4.string().min(1)
|
|
735
|
+
});
|
|
736
|
+
var RouteEventSchema = EventBaseSchema.extend({
|
|
737
|
+
type: z4.literal("route"),
|
|
738
|
+
promptHash: z4.string().min(1),
|
|
739
|
+
promptChars: z4.number().int().min(0),
|
|
740
|
+
tier: ComplexityTierSchema,
|
|
741
|
+
routeKind: z4.enum(["local", "frontier"]),
|
|
742
|
+
selected: ModelRefSchema,
|
|
743
|
+
rationale: z4.string(),
|
|
744
|
+
estimatedCostUSD: z4.number().finite().min(0),
|
|
745
|
+
baselineCostUSD: z4.number().finite().min(0),
|
|
746
|
+
estimatedSavingsUSD: z4.number().finite(),
|
|
747
|
+
budgetAction: z4.string().min(1),
|
|
748
|
+
tokensIn: z4.number().int().min(0).optional(),
|
|
749
|
+
tokensOut: z4.number().int().min(0).optional(),
|
|
750
|
+
decisionID: z4.string().min(1).optional(),
|
|
751
|
+
agent: z4.string().min(1).optional(),
|
|
752
|
+
success: z4.boolean().optional(),
|
|
753
|
+
batchID: z4.string().min(1).optional(),
|
|
754
|
+
failureReason: z4.string().min(1).optional(),
|
|
755
|
+
failureStage: z4.string().min(1).optional()
|
|
756
|
+
});
|
|
757
|
+
var MessageEventSchema = EventBaseSchema.extend({
|
|
758
|
+
type: z4.literal("message"),
|
|
759
|
+
providerID: z4.string().min(1),
|
|
760
|
+
modelID: z4.string().min(1),
|
|
761
|
+
agent: z4.string().min(1).optional(),
|
|
762
|
+
mode: z4.string().min(1).optional(),
|
|
763
|
+
messageID: z4.string().min(1).optional(),
|
|
764
|
+
costUSD: z4.number().finite().min(0),
|
|
765
|
+
tokensIn: z4.number().int().min(0),
|
|
766
|
+
tokensOut: z4.number().int().min(0),
|
|
767
|
+
tokensReasoning: z4.number().int().min(0),
|
|
768
|
+
tokensCacheRead: z4.number().int().min(0),
|
|
769
|
+
tokensCacheWrite: z4.number().int().min(0),
|
|
770
|
+
durationMs: z4.number().int().min(0)
|
|
771
|
+
});
|
|
772
|
+
var ToolcallEventSchema = EventBaseSchema.extend({
|
|
773
|
+
type: z4.literal("toolcall"),
|
|
774
|
+
tool: z4.string().min(1),
|
|
775
|
+
callID: z4.string().min(1),
|
|
776
|
+
agent: z4.string().min(1).optional(),
|
|
777
|
+
durationMs: z4.number().int().min(0),
|
|
778
|
+
ok: z4.boolean(),
|
|
779
|
+
title: z4.string().optional()
|
|
780
|
+
});
|
|
781
|
+
var MeetingEventSchema = EventBaseSchema.extend({
|
|
782
|
+
type: z4.literal("meeting"),
|
|
783
|
+
batchID: z4.string().min(1),
|
|
784
|
+
purpose: z4.string().optional(),
|
|
785
|
+
roles: z4.array(z4.string().min(1)),
|
|
786
|
+
decisionIDs: z4.array(z4.string().min(1)).optional()
|
|
787
|
+
});
|
|
788
|
+
var DecisionEventSchema = EventBaseSchema.extend({
|
|
789
|
+
type: z4.literal("decision"),
|
|
790
|
+
agent: z4.string().min(1).optional(),
|
|
791
|
+
summary: z4.string().min(1),
|
|
792
|
+
tags: z4.array(z4.string().min(1)).optional()
|
|
793
|
+
});
|
|
794
|
+
var ActivityEventSchema = EventBaseSchema.extend({
|
|
795
|
+
type: z4.literal("activity"),
|
|
796
|
+
kind: z4.enum(["route", "agent", "decision", "commit"]),
|
|
797
|
+
agent: z4.string().min(1).optional(),
|
|
798
|
+
summary: z4.string().min(1)
|
|
799
|
+
});
|
|
800
|
+
var SessionEndpointEventSchema = EventBaseSchema.extend({
|
|
801
|
+
type: z4.literal("session-endpoint"),
|
|
802
|
+
url: z4.string().url(),
|
|
803
|
+
directory: z4.string().min(1).optional(),
|
|
804
|
+
worktree: z4.string().min(1).optional(),
|
|
805
|
+
title: z4.string().min(1).optional()
|
|
806
|
+
});
|
|
807
|
+
var OpenTeamEventSchema = z4.discriminatedUnion("type", [
|
|
808
|
+
RouteEventSchema,
|
|
809
|
+
MessageEventSchema,
|
|
810
|
+
ToolcallEventSchema,
|
|
811
|
+
MeetingEventSchema,
|
|
812
|
+
DecisionEventSchema,
|
|
813
|
+
ActivityEventSchema,
|
|
814
|
+
SessionEndpointEventSchema
|
|
815
|
+
]);
|
|
816
|
+
|
|
817
|
+
// src/storage/fsStorageProvider.ts
|
|
818
|
+
import {
|
|
819
|
+
appendFile,
|
|
820
|
+
mkdir,
|
|
821
|
+
readdir,
|
|
822
|
+
readFile,
|
|
823
|
+
rm,
|
|
824
|
+
stat,
|
|
825
|
+
writeFile
|
|
826
|
+
} from "node:fs/promises";
|
|
827
|
+
import { dirname, join } from "node:path";
|
|
828
|
+
function isMissing(error) {
|
|
829
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
1110
830
|
}
|
|
1111
|
-
function
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
const meetings = [];
|
|
1115
|
-
const activity = [];
|
|
1116
|
-
const sessionFor = (sessionID, ts) => {
|
|
1117
|
-
const existing = sessions.get(sessionID);
|
|
1118
|
-
if (existing === undefined) {
|
|
1119
|
-
const created = emptySession(sessionID, ts);
|
|
1120
|
-
sessions.set(sessionID, created);
|
|
1121
|
-
return created;
|
|
1122
|
-
}
|
|
1123
|
-
existing.firstTs = Math.min(existing.firstTs, ts);
|
|
1124
|
-
existing.lastTs = Math.max(existing.lastTs, ts);
|
|
1125
|
-
return existing;
|
|
1126
|
-
};
|
|
1127
|
-
for (const event of events) {
|
|
1128
|
-
switch (event.type) {
|
|
1129
|
-
case "route":
|
|
1130
|
-
applyRoute(sessionFor(event.sessionID, event.ts), event);
|
|
1131
|
-
break;
|
|
1132
|
-
case "message":
|
|
1133
|
-
applyMessage(sessionFor(event.sessionID, event.ts), event);
|
|
1134
|
-
break;
|
|
1135
|
-
case "toolcall":
|
|
1136
|
-
applyToolcall(sessionFor(event.sessionID, event.ts), event);
|
|
1137
|
-
break;
|
|
1138
|
-
case "session-endpoint":
|
|
1139
|
-
applyEndpoint(sessionFor(event.sessionID, event.ts), event);
|
|
1140
|
-
break;
|
|
1141
|
-
case "decision":
|
|
1142
|
-
decisions.push(toDecision(event));
|
|
1143
|
-
break;
|
|
1144
|
-
case "meeting":
|
|
1145
|
-
meetings.push(toMeeting(event));
|
|
1146
|
-
break;
|
|
1147
|
-
case "activity":
|
|
1148
|
-
activity.push(toActivity(event));
|
|
1149
|
-
break;
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
const totals = {
|
|
1153
|
-
sessions: 0,
|
|
1154
|
-
routeCount: 0,
|
|
1155
|
-
localCount: 0,
|
|
1156
|
-
frontierCount: 0,
|
|
1157
|
-
estimatedCostUSD: 0,
|
|
1158
|
-
estimatedSavingsUSD: 0,
|
|
1159
|
-
realCostUSD: 0,
|
|
1160
|
-
messageCount: 0,
|
|
1161
|
-
tokensIn: 0,
|
|
1162
|
-
tokensOut: 0,
|
|
1163
|
-
tokensReasoning: 0,
|
|
1164
|
-
tokensCacheRead: 0,
|
|
1165
|
-
tokensCacheWrite: 0,
|
|
1166
|
-
toolcalls: emptyToolcallStats()
|
|
1167
|
-
};
|
|
1168
|
-
const summaries = [...sessions.values()].sort((a, b) => b.lastTs - a.lastTs);
|
|
1169
|
-
for (const summary of summaries) {
|
|
1170
|
-
summary.models.sort();
|
|
1171
|
-
summary.agents.sort();
|
|
1172
|
-
accumulateTotals(totals, summary);
|
|
831
|
+
function createFsStorageProvider(root) {
|
|
832
|
+
function toDisk(path) {
|
|
833
|
+
return join(root, ...splitVirtualPath(path));
|
|
1173
834
|
}
|
|
1174
835
|
return {
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
836
|
+
async read(path) {
|
|
837
|
+
try {
|
|
838
|
+
return await readFile(toDisk(path), "utf8");
|
|
839
|
+
} catch (error) {
|
|
840
|
+
if (isMissing(error)) {
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
throw error;
|
|
844
|
+
}
|
|
845
|
+
},
|
|
846
|
+
async write(path, content) {
|
|
847
|
+
const disk = toDisk(path);
|
|
848
|
+
await mkdir(dirname(disk), { recursive: true });
|
|
849
|
+
await writeFile(disk, content, "utf8");
|
|
850
|
+
},
|
|
851
|
+
async append(path, content) {
|
|
852
|
+
const disk = toDisk(path);
|
|
853
|
+
await mkdir(dirname(disk), { recursive: true });
|
|
854
|
+
await appendFile(disk, content, "utf8");
|
|
855
|
+
},
|
|
856
|
+
async exists(path) {
|
|
857
|
+
try {
|
|
858
|
+
await stat(toDisk(path));
|
|
859
|
+
return true;
|
|
860
|
+
} catch (error) {
|
|
861
|
+
if (isMissing(error)) {
|
|
862
|
+
return false;
|
|
863
|
+
}
|
|
864
|
+
throw error;
|
|
865
|
+
}
|
|
866
|
+
},
|
|
867
|
+
async list(dir) {
|
|
868
|
+
try {
|
|
869
|
+
const names = await readdir(toDisk(dir));
|
|
870
|
+
return names.sort();
|
|
871
|
+
} catch (error) {
|
|
872
|
+
if (isMissing(error)) {
|
|
873
|
+
return [];
|
|
874
|
+
}
|
|
875
|
+
throw error;
|
|
876
|
+
}
|
|
877
|
+
},
|
|
878
|
+
async isDirectory(path) {
|
|
879
|
+
try {
|
|
880
|
+
return (await stat(toDisk(path))).isDirectory();
|
|
881
|
+
} catch (error) {
|
|
882
|
+
if (isMissing(error)) {
|
|
883
|
+
return false;
|
|
884
|
+
}
|
|
885
|
+
throw error;
|
|
886
|
+
}
|
|
887
|
+
},
|
|
888
|
+
async mkdir(dir) {
|
|
889
|
+
await mkdir(toDisk(dir), { recursive: true });
|
|
890
|
+
},
|
|
891
|
+
async delete(path) {
|
|
892
|
+
await rm(toDisk(path), { force: true });
|
|
893
|
+
},
|
|
894
|
+
async stat(path) {
|
|
895
|
+
try {
|
|
896
|
+
const stats = await stat(toDisk(path));
|
|
897
|
+
return {
|
|
898
|
+
size: stats.size,
|
|
899
|
+
mtimeMs: stats.mtimeMs,
|
|
900
|
+
isDirectory: stats.isDirectory()
|
|
901
|
+
};
|
|
902
|
+
} catch (error) {
|
|
903
|
+
if (isMissing(error)) {
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
throw error;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
1180
909
|
};
|
|
1181
910
|
}
|
|
1182
911
|
|
|
1183
|
-
// src/
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
912
|
+
// src/telemetry/types.ts
|
|
913
|
+
import { z as z5 } from "zod";
|
|
914
|
+
var CostRecordSchema = z5.object({
|
|
915
|
+
ts: z5.number().finite(),
|
|
916
|
+
sessionID: z5.string().min(1).optional(),
|
|
917
|
+
promptHash: z5.string().min(1),
|
|
918
|
+
promptChars: z5.number().int().min(0),
|
|
919
|
+
tier: ComplexityTierSchema,
|
|
920
|
+
routeKind: z5.enum(["local", "frontier"]),
|
|
921
|
+
selected: ModelRefSchema,
|
|
922
|
+
rationale: z5.string(),
|
|
923
|
+
estimatedCostUSD: z5.number().finite().min(0),
|
|
924
|
+
baselineCostUSD: z5.number().finite().min(0),
|
|
925
|
+
estimatedSavingsUSD: z5.number().finite(),
|
|
926
|
+
budgetAction: z5.string().min(1),
|
|
927
|
+
tokensIn: z5.number().int().min(0).optional(),
|
|
928
|
+
tokensOut: z5.number().int().min(0).optional()
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
// src/telemetry/read.ts
|
|
932
|
+
var DEFAULT_TELEMETRY_PATH = ".opencode/openteam-telemetry.jsonl";
|
|
933
|
+
function parseCostRecordsJsonl(text) {
|
|
934
|
+
const records = [];
|
|
935
|
+
for (const line of text.split(`
|
|
936
|
+
`)) {
|
|
937
|
+
const trimmed = line.trim();
|
|
938
|
+
if (trimmed.length === 0) {
|
|
939
|
+
continue;
|
|
940
|
+
}
|
|
941
|
+
let candidate;
|
|
942
|
+
try {
|
|
943
|
+
candidate = JSON.parse(trimmed);
|
|
944
|
+
} catch {
|
|
945
|
+
continue;
|
|
946
|
+
}
|
|
947
|
+
const parsed = CostRecordSchema.safeParse(candidate);
|
|
948
|
+
if (parsed.success) {
|
|
949
|
+
records.push(parsed.data);
|
|
950
|
+
}
|
|
1188
951
|
}
|
|
952
|
+
return records;
|
|
1189
953
|
}
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
954
|
+
|
|
955
|
+
// src/telemetry/eventLog.ts
|
|
956
|
+
var DEFAULT_SESSIONS_DIR = ".opencode/openteam/sessions";
|
|
957
|
+
var LEGACY_SESSION_ID = "legacy";
|
|
958
|
+
function parseEventsJsonl(text) {
|
|
959
|
+
const events = [];
|
|
960
|
+
for (const line of text.split(`
|
|
961
|
+
`)) {
|
|
962
|
+
const trimmed = line.trim();
|
|
963
|
+
if (trimmed.length === 0) {
|
|
1194
964
|
continue;
|
|
1195
965
|
}
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
segments.pop();
|
|
966
|
+
let candidate;
|
|
967
|
+
try {
|
|
968
|
+
candidate = JSON.parse(trimmed);
|
|
969
|
+
} catch {
|
|
1201
970
|
continue;
|
|
1202
971
|
}
|
|
1203
|
-
|
|
972
|
+
const parsed = OpenTeamEventSchema.safeParse(candidate);
|
|
973
|
+
if (parsed.success) {
|
|
974
|
+
events.push(parsed.data);
|
|
975
|
+
}
|
|
1204
976
|
}
|
|
1205
|
-
return
|
|
977
|
+
return events;
|
|
1206
978
|
}
|
|
1207
|
-
function
|
|
1208
|
-
|
|
979
|
+
function costRecordToRouteEvent(record) {
|
|
980
|
+
const event = {
|
|
981
|
+
v: EVENT_SCHEMA_VERSION,
|
|
982
|
+
type: "route",
|
|
983
|
+
ts: record.ts,
|
|
984
|
+
sessionID: record.sessionID ?? LEGACY_SESSION_ID,
|
|
985
|
+
promptHash: record.promptHash,
|
|
986
|
+
promptChars: record.promptChars,
|
|
987
|
+
tier: record.tier,
|
|
988
|
+
routeKind: record.routeKind,
|
|
989
|
+
selected: record.selected,
|
|
990
|
+
rationale: record.rationale,
|
|
991
|
+
estimatedCostUSD: record.estimatedCostUSD,
|
|
992
|
+
baselineCostUSD: record.baselineCostUSD,
|
|
993
|
+
estimatedSavingsUSD: record.estimatedSavingsUSD,
|
|
994
|
+
budgetAction: record.budgetAction
|
|
995
|
+
};
|
|
996
|
+
if (record.tokensIn !== undefined) {
|
|
997
|
+
event.tokensIn = record.tokensIn;
|
|
998
|
+
}
|
|
999
|
+
if (record.tokensOut !== undefined) {
|
|
1000
|
+
event.tokensOut = record.tokensOut;
|
|
1001
|
+
}
|
|
1002
|
+
return event;
|
|
1209
1003
|
}
|
|
1210
|
-
function
|
|
1211
|
-
const
|
|
1212
|
-
|
|
1004
|
+
function routeEventToCostRecord(event) {
|
|
1005
|
+
const record = {
|
|
1006
|
+
ts: event.ts,
|
|
1007
|
+
sessionID: event.sessionID,
|
|
1008
|
+
promptHash: event.promptHash,
|
|
1009
|
+
promptChars: event.promptChars,
|
|
1010
|
+
tier: event.tier,
|
|
1011
|
+
routeKind: event.routeKind,
|
|
1012
|
+
selected: event.selected,
|
|
1013
|
+
rationale: event.rationale,
|
|
1014
|
+
estimatedCostUSD: event.estimatedCostUSD,
|
|
1015
|
+
baselineCostUSD: event.baselineCostUSD,
|
|
1016
|
+
estimatedSavingsUSD: event.estimatedSavingsUSD,
|
|
1017
|
+
budgetAction: event.budgetAction
|
|
1018
|
+
};
|
|
1019
|
+
if (event.tokensIn !== undefined) {
|
|
1020
|
+
record.tokensIn = event.tokensIn;
|
|
1021
|
+
}
|
|
1022
|
+
if (event.tokensOut !== undefined) {
|
|
1023
|
+
record.tokensOut = event.tokensOut;
|
|
1024
|
+
}
|
|
1025
|
+
return record;
|
|
1213
1026
|
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
providerID: z2.string().min(1),
|
|
1237
|
-
modelID: z2.string().min(1)
|
|
1238
|
-
}),
|
|
1239
|
-
kind: z2.enum(["local", "frontier", "router"]),
|
|
1240
|
-
contextWindow: z2.number().int().positive(),
|
|
1241
|
-
maxOutputTokens: z2.number().int().positive(),
|
|
1242
|
-
supportsToolCalling: z2.boolean(),
|
|
1243
|
-
supportsVision: z2.boolean(),
|
|
1244
|
-
reasoningTier: CapabilityTierSchema,
|
|
1245
|
-
codeQualityTier: CapabilityTierSchema,
|
|
1246
|
-
costPer1M: z2.object({
|
|
1247
|
-
inputUSD: z2.number().min(0),
|
|
1248
|
-
outputUSD: z2.number().min(0)
|
|
1249
|
-
}),
|
|
1250
|
-
availability: z2.enum(["available", "degraded", "unavailable"])
|
|
1251
|
-
});
|
|
1252
|
-
|
|
1253
|
-
// src/config/schema.ts
|
|
1254
|
-
import { z as z3 } from "zod";
|
|
1255
|
-
var ModelRefSchema = z3.object({
|
|
1256
|
-
providerID: z3.string().min(1),
|
|
1257
|
-
modelID: z3.string().min(1)
|
|
1258
|
-
});
|
|
1259
|
-
var RouterModeSchema = z3.enum(["economy", "balanced", "quality"]);
|
|
1260
|
-
var PrivacyModeSchema = z3.enum([
|
|
1261
|
-
"forceLocalOnSensitive",
|
|
1262
|
-
"consentBeforeFrontier",
|
|
1263
|
-
"off"
|
|
1264
|
-
]);
|
|
1265
|
-
var BaselineModeSchema = z3.enum(["auto", "pinned"]);
|
|
1266
|
-
var ConsoleHostSchema = z3.enum(["127.0.0.1", "localhost"]);
|
|
1267
|
-
var ConsoleConfigSchema = z3.object({
|
|
1268
|
-
host: ConsoleHostSchema.default("127.0.0.1"),
|
|
1269
|
-
port: z3.number().int().min(1024).max(65535).default(4599),
|
|
1270
|
-
autoPortFallback: z3.boolean().default(true),
|
|
1271
|
-
refreshMs: z3.number().int().min(250).default(2000),
|
|
1272
|
-
recentRoutes: z3.number().int().positive().default(50),
|
|
1273
|
-
openBrowser: z3.boolean().default(false),
|
|
1274
|
-
remoteStorage: z3.boolean().default(false),
|
|
1275
|
-
terminal: z3.object({
|
|
1276
|
-
enabled: z3.boolean().default(true),
|
|
1277
|
-
pty: z3.boolean().optional()
|
|
1278
|
-
}).default({ enabled: true })
|
|
1279
|
-
}).default({
|
|
1280
|
-
host: "127.0.0.1",
|
|
1281
|
-
port: 4599,
|
|
1282
|
-
autoPortFallback: true,
|
|
1283
|
-
refreshMs: 2000,
|
|
1284
|
-
recentRoutes: 50,
|
|
1285
|
-
openBrowser: false,
|
|
1286
|
-
remoteStorage: false,
|
|
1287
|
-
terminal: { enabled: true }
|
|
1288
|
-
});
|
|
1289
|
-
var defaultLocalModel = {
|
|
1290
|
-
providerID: "ollama",
|
|
1291
|
-
modelID: "qwen3:8b"
|
|
1292
|
-
};
|
|
1293
|
-
var defaultFrontierModel = {
|
|
1294
|
-
providerID: "anthropic",
|
|
1295
|
-
modelID: "claude-sonnet-4-5"
|
|
1296
|
-
};
|
|
1297
|
-
var LocalRuntimeSchema = z3.object({
|
|
1298
|
-
id: z3.enum(["ollama", "lmstudio", "foundry-local"]),
|
|
1299
|
-
enabled: z3.boolean().default(true),
|
|
1300
|
-
baseURL: z3.string().url().optional(),
|
|
1301
|
-
discovery: z3.enum(["cli", "sdk", "manual"]).optional(),
|
|
1302
|
-
defaultModel: ModelRefSchema
|
|
1303
|
-
});
|
|
1304
|
-
var OpenTeamConfigObjectSchema = z3.object({
|
|
1305
|
-
baseline: z3.object({
|
|
1306
|
-
mode: BaselineModeSchema.default("auto"),
|
|
1307
|
-
pinnedModel: ModelRefSchema.nullable().default(null),
|
|
1308
|
-
hardDefault: ModelRefSchema.default(defaultFrontierModel)
|
|
1309
|
-
}).default({
|
|
1310
|
-
mode: "auto",
|
|
1311
|
-
pinnedModel: null,
|
|
1312
|
-
hardDefault: defaultFrontierModel
|
|
1313
|
-
}),
|
|
1314
|
-
router: z3.object({
|
|
1315
|
-
mode: RouterModeSchema.default("balanced"),
|
|
1316
|
-
localDefault: ModelRefSchema.default(defaultLocalModel),
|
|
1317
|
-
trivialPromptMaxChars: z3.number().int().positive().default(280),
|
|
1318
|
-
frontierPromptMinChars: z3.number().int().positive().default(2000)
|
|
1319
|
-
}).default({
|
|
1320
|
-
mode: "balanced",
|
|
1321
|
-
localDefault: defaultLocalModel,
|
|
1322
|
-
trivialPromptMaxChars: 280,
|
|
1323
|
-
frontierPromptMinChars: 2000
|
|
1324
|
-
}),
|
|
1325
|
-
local: z3.object({
|
|
1326
|
-
runtimes: z3.array(LocalRuntimeSchema).min(1).default([
|
|
1327
|
-
{
|
|
1328
|
-
id: "ollama",
|
|
1329
|
-
enabled: true,
|
|
1330
|
-
baseURL: "http://localhost:11434/v1",
|
|
1331
|
-
defaultModel: { providerID: "ollama", modelID: "qwen3:8b" }
|
|
1332
|
-
}
|
|
1333
|
-
])
|
|
1334
|
-
}).default({
|
|
1335
|
-
runtimes: [
|
|
1336
|
-
{
|
|
1337
|
-
id: "ollama",
|
|
1338
|
-
enabled: true,
|
|
1339
|
-
baseURL: "http://localhost:11434/v1",
|
|
1340
|
-
defaultModel: defaultLocalModel
|
|
1027
|
+
async function readRouteCostRecords(dir, deps) {
|
|
1028
|
+
const events = await readSessionEvents(dir, deps);
|
|
1029
|
+
const records = [];
|
|
1030
|
+
for (const event of events) {
|
|
1031
|
+
if (event.type === "route") {
|
|
1032
|
+
records.push(routeEventToCostRecord(event));
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
return records;
|
|
1036
|
+
}
|
|
1037
|
+
async function readSessionEvents(dir, deps) {
|
|
1038
|
+
const files = (await deps.storage.list(dir)).filter((file) => file.endsWith(".jsonl"));
|
|
1039
|
+
const perFile = await Promise.all(files.map(async (file) => {
|
|
1040
|
+
const text = await deps.storage.read(joinVirtualPath(dir, file));
|
|
1041
|
+
return text === undefined ? [] : parseEventsJsonl(text);
|
|
1042
|
+
}));
|
|
1043
|
+
const events = perFile.flat();
|
|
1044
|
+
if (deps.legacyTelemetryPath !== undefined) {
|
|
1045
|
+
const legacyText = await deps.storage.read(deps.legacyTelemetryPath);
|
|
1046
|
+
if (legacyText !== undefined) {
|
|
1047
|
+
for (const record of parseCostRecordsJsonl(legacyText)) {
|
|
1048
|
+
events.push(costRecordToRouteEvent(record));
|
|
1341
1049
|
}
|
|
1342
|
-
|
|
1343
|
-
}
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
return events.sort((a, b) => a.ts - b.ts);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// src/storage/index/sqliteIndex.ts
|
|
1056
|
+
var SCHEMA = `
|
|
1057
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
1058
|
+
session_id TEXT NOT NULL,
|
|
1059
|
+
ts INTEGER NOT NULL,
|
|
1060
|
+
type TEXT NOT NULL,
|
|
1061
|
+
payload TEXT NOT NULL
|
|
1062
|
+
);
|
|
1063
|
+
CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id, ts);
|
|
1064
|
+
`;
|
|
1065
|
+
var INSERT_SQL = "INSERT INTO events (session_id, ts, type, payload) VALUES (?, ?, ?, ?)";
|
|
1066
|
+
var ROLLUP_SQL = `
|
|
1067
|
+
SELECT
|
|
1068
|
+
session_id AS sessionID,
|
|
1069
|
+
COUNT(*) AS eventCount,
|
|
1070
|
+
SUM(CASE WHEN type = 'route' THEN 1 ELSE 0 END) AS routeCount,
|
|
1071
|
+
SUM(CASE WHEN type = 'message' THEN 1 ELSE 0 END) AS messageCount,
|
|
1072
|
+
SUM(CASE WHEN type = 'toolcall' THEN 1 ELSE 0 END) AS toolcallCount,
|
|
1073
|
+
COALESCE(SUM(CASE WHEN type = 'message'
|
|
1074
|
+
THEN json_extract(payload, '$.costUSD') ELSE 0 END), 0) AS realCostUSD,
|
|
1075
|
+
COALESCE(SUM(CASE WHEN type = 'message'
|
|
1076
|
+
THEN json_extract(payload, '$.tokensIn') ELSE 0 END), 0) AS tokensIn,
|
|
1077
|
+
COALESCE(SUM(CASE WHEN type = 'message'
|
|
1078
|
+
THEN json_extract(payload, '$.tokensOut') ELSE 0 END), 0) AS tokensOut,
|
|
1079
|
+
MIN(ts) AS firstTs,
|
|
1080
|
+
MAX(ts) AS lastTs
|
|
1081
|
+
FROM events
|
|
1082
|
+
GROUP BY session_id
|
|
1083
|
+
ORDER BY lastTs DESC, session_id ASC
|
|
1084
|
+
`;
|
|
1085
|
+
function insertEvents(db, events) {
|
|
1086
|
+
const stmt = db.query(INSERT_SQL);
|
|
1087
|
+
db.run("BEGIN");
|
|
1088
|
+
try {
|
|
1089
|
+
for (const event of events) {
|
|
1090
|
+
stmt.run(event.sessionID, event.ts, event.type, JSON.stringify(event));
|
|
1091
|
+
}
|
|
1092
|
+
db.run("COMMIT");
|
|
1093
|
+
} catch (error) {
|
|
1094
|
+
db.run("ROLLBACK");
|
|
1095
|
+
throw error;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
function toNumber(value) {
|
|
1099
|
+
return typeof value === "number" ? value : Number(value ?? 0);
|
|
1100
|
+
}
|
|
1101
|
+
function createSqliteEventIndex(db) {
|
|
1102
|
+
db.exec(SCHEMA);
|
|
1103
|
+
const allEvents = () => {
|
|
1104
|
+
const rows = db.query("SELECT payload FROM events ORDER BY ts ASC").all();
|
|
1105
|
+
const events = [];
|
|
1106
|
+
for (const row of rows) {
|
|
1107
|
+
let candidate;
|
|
1108
|
+
try {
|
|
1109
|
+
candidate = JSON.parse(row.payload);
|
|
1110
|
+
} catch {
|
|
1111
|
+
continue;
|
|
1112
|
+
}
|
|
1113
|
+
const parsed = OpenTeamEventSchema.safeParse(candidate);
|
|
1114
|
+
if (parsed.success) {
|
|
1115
|
+
events.push(parsed.data);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
return events;
|
|
1119
|
+
};
|
|
1120
|
+
return {
|
|
1121
|
+
rebuild(events) {
|
|
1122
|
+
db.run("DELETE FROM events");
|
|
1123
|
+
insertEvents(db, events);
|
|
1124
|
+
},
|
|
1125
|
+
append(events) {
|
|
1126
|
+
insertEvents(db, events);
|
|
1127
|
+
},
|
|
1128
|
+
allEvents,
|
|
1129
|
+
sessionRollups() {
|
|
1130
|
+
const rows = db.query(ROLLUP_SQL).all();
|
|
1131
|
+
return rows.map((row) => ({
|
|
1132
|
+
sessionID: String(row.sessionID),
|
|
1133
|
+
eventCount: toNumber(row.eventCount),
|
|
1134
|
+
routeCount: toNumber(row.routeCount),
|
|
1135
|
+
messageCount: toNumber(row.messageCount),
|
|
1136
|
+
toolcallCount: toNumber(row.toolcallCount),
|
|
1137
|
+
realCostUSD: toNumber(row.realCostUSD),
|
|
1138
|
+
tokensIn: toNumber(row.tokensIn),
|
|
1139
|
+
tokensOut: toNumber(row.tokensOut),
|
|
1140
|
+
firstTs: toNumber(row.firstTs),
|
|
1141
|
+
lastTs: toNumber(row.lastTs)
|
|
1142
|
+
}));
|
|
1143
|
+
},
|
|
1144
|
+
aggregate() {
|
|
1145
|
+
return aggregateEvents(allEvents());
|
|
1146
|
+
},
|
|
1147
|
+
close() {
|
|
1148
|
+
db.close();
|
|
1149
|
+
}
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
async function rebuildSqliteIndexFromStorage(index, dir, deps) {
|
|
1153
|
+
const events = await readSessionEvents(dir, deps);
|
|
1154
|
+
index.rebuild(events);
|
|
1155
|
+
return events.length;
|
|
1156
|
+
}
|
|
1354
1157
|
|
|
1355
|
-
// src/
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
tier: ComplexityTierSchema,
|
|
1367
|
-
routeKind: z4.enum(["local", "frontier"]),
|
|
1368
|
-
selected: ModelRefSchema,
|
|
1369
|
-
rationale: z4.string(),
|
|
1370
|
-
estimatedCostUSD: z4.number().finite().min(0),
|
|
1371
|
-
baselineCostUSD: z4.number().finite().min(0),
|
|
1372
|
-
estimatedSavingsUSD: z4.number().finite(),
|
|
1373
|
-
budgetAction: z4.string().min(1),
|
|
1374
|
-
tokensIn: z4.number().int().min(0).optional(),
|
|
1375
|
-
tokensOut: z4.number().int().min(0).optional(),
|
|
1376
|
-
decisionID: z4.string().min(1).optional(),
|
|
1377
|
-
agent: z4.string().min(1).optional(),
|
|
1378
|
-
success: z4.boolean().optional(),
|
|
1379
|
-
batchID: z4.string().min(1).optional(),
|
|
1380
|
-
failureReason: z4.string().min(1).optional(),
|
|
1381
|
-
failureStage: z4.string().min(1).optional()
|
|
1382
|
-
});
|
|
1383
|
-
var MessageEventSchema = EventBaseSchema.extend({
|
|
1384
|
-
type: z4.literal("message"),
|
|
1385
|
-
providerID: z4.string().min(1),
|
|
1386
|
-
modelID: z4.string().min(1),
|
|
1387
|
-
agent: z4.string().min(1).optional(),
|
|
1388
|
-
mode: z4.string().min(1).optional(),
|
|
1389
|
-
messageID: z4.string().min(1).optional(),
|
|
1390
|
-
costUSD: z4.number().finite().min(0),
|
|
1391
|
-
tokensIn: z4.number().int().min(0),
|
|
1392
|
-
tokensOut: z4.number().int().min(0),
|
|
1393
|
-
tokensReasoning: z4.number().int().min(0),
|
|
1394
|
-
tokensCacheRead: z4.number().int().min(0),
|
|
1395
|
-
tokensCacheWrite: z4.number().int().min(0),
|
|
1396
|
-
durationMs: z4.number().int().min(0)
|
|
1397
|
-
});
|
|
1398
|
-
var ToolcallEventSchema = EventBaseSchema.extend({
|
|
1399
|
-
type: z4.literal("toolcall"),
|
|
1400
|
-
tool: z4.string().min(1),
|
|
1401
|
-
callID: z4.string().min(1),
|
|
1402
|
-
agent: z4.string().min(1).optional(),
|
|
1403
|
-
durationMs: z4.number().int().min(0),
|
|
1404
|
-
ok: z4.boolean(),
|
|
1405
|
-
title: z4.string().optional()
|
|
1406
|
-
});
|
|
1407
|
-
var MeetingEventSchema = EventBaseSchema.extend({
|
|
1408
|
-
type: z4.literal("meeting"),
|
|
1409
|
-
batchID: z4.string().min(1),
|
|
1410
|
-
purpose: z4.string().optional(),
|
|
1411
|
-
roles: z4.array(z4.string().min(1)),
|
|
1412
|
-
decisionIDs: z4.array(z4.string().min(1)).optional()
|
|
1413
|
-
});
|
|
1414
|
-
var DecisionEventSchema = EventBaseSchema.extend({
|
|
1415
|
-
type: z4.literal("decision"),
|
|
1416
|
-
agent: z4.string().min(1).optional(),
|
|
1417
|
-
summary: z4.string().min(1),
|
|
1418
|
-
tags: z4.array(z4.string().min(1)).optional()
|
|
1419
|
-
});
|
|
1420
|
-
var ActivityEventSchema = EventBaseSchema.extend({
|
|
1421
|
-
type: z4.literal("activity"),
|
|
1422
|
-
kind: z4.enum(["route", "agent", "decision", "commit"]),
|
|
1423
|
-
agent: z4.string().min(1).optional(),
|
|
1424
|
-
summary: z4.string().min(1)
|
|
1425
|
-
});
|
|
1426
|
-
var SessionEndpointEventSchema = EventBaseSchema.extend({
|
|
1427
|
-
type: z4.literal("session-endpoint"),
|
|
1428
|
-
url: z4.string().url(),
|
|
1429
|
-
directory: z4.string().min(1).optional(),
|
|
1430
|
-
worktree: z4.string().min(1).optional(),
|
|
1431
|
-
title: z4.string().min(1).optional()
|
|
1432
|
-
});
|
|
1433
|
-
var OpenTeamEventSchema = z4.discriminatedUnion("type", [
|
|
1434
|
-
RouteEventSchema,
|
|
1435
|
-
MessageEventSchema,
|
|
1436
|
-
ToolcallEventSchema,
|
|
1437
|
-
MeetingEventSchema,
|
|
1438
|
-
DecisionEventSchema,
|
|
1439
|
-
ActivityEventSchema,
|
|
1440
|
-
SessionEndpointEventSchema
|
|
1441
|
-
]);
|
|
1158
|
+
// src/storage/index/bunSqlite.ts
|
|
1159
|
+
async function createBunSqliteDatabase(path) {
|
|
1160
|
+
const mod = await import("bun:sqlite");
|
|
1161
|
+
return new mod.Database(path);
|
|
1162
|
+
}
|
|
1163
|
+
async function buildSqliteIndexFromConfig(path, sessionsDir, deps) {
|
|
1164
|
+
const db = await createBunSqliteDatabase(path);
|
|
1165
|
+
const index = createSqliteEventIndex(db);
|
|
1166
|
+
const events = await rebuildSqliteIndexFromStorage(index, sessionsDir, deps);
|
|
1167
|
+
return { index, events };
|
|
1168
|
+
}
|
|
1442
1169
|
|
|
1443
|
-
// src/
|
|
1170
|
+
// src/web/start.ts
|
|
1171
|
+
import { watch as fsWatch } from "node:fs";
|
|
1172
|
+
|
|
1173
|
+
// src/web/paths.ts
|
|
1174
|
+
var DEFAULT_BACKLOG_PATH = ".opencode/openteam-backlog.md";
|
|
1175
|
+
var DEFAULT_DECISIONS_PATH = ".opencode/openteam-decisions.md";
|
|
1176
|
+
|
|
1177
|
+
// src/web/server.ts
|
|
1444
1178
|
import {
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1179
|
+
createServer as createHttpServer
|
|
1180
|
+
} from "node:http";
|
|
1181
|
+
|
|
1182
|
+
// src/console/render.ts
|
|
1183
|
+
var TIERS = [
|
|
1184
|
+
"trivial",
|
|
1185
|
+
"simple",
|
|
1186
|
+
"moderate",
|
|
1187
|
+
"hard"
|
|
1188
|
+
];
|
|
1189
|
+
function escapeHtml(value) {
|
|
1190
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1191
|
+
}
|
|
1192
|
+
function usd(value) {
|
|
1193
|
+
return `$${value.toFixed(5)}`;
|
|
1194
|
+
}
|
|
1195
|
+
function pct(value) {
|
|
1196
|
+
return `${value.toFixed(2)}%`;
|
|
1197
|
+
}
|
|
1198
|
+
function ms(value) {
|
|
1199
|
+
return value >= 1000 ? `${(value / 1000).toFixed(2)}s` : `${Math.round(value)}ms`;
|
|
1200
|
+
}
|
|
1201
|
+
function avgMs(stats) {
|
|
1202
|
+
return stats.count === 0 ? "—" : ms(stats.totalDurationMs / stats.count);
|
|
1203
|
+
}
|
|
1204
|
+
function agentModel(agent) {
|
|
1205
|
+
return agent.model === undefined ? "hereda default" : `${agent.model.providerID}/${agent.model.modelID}`;
|
|
1206
|
+
}
|
|
1207
|
+
function summaryPanel(cost) {
|
|
1208
|
+
return [
|
|
1209
|
+
'<section class="panel" id="panel-summary">',
|
|
1210
|
+
"<h2>Resumen de routing</h2>",
|
|
1211
|
+
'<div class="grid">',
|
|
1212
|
+
`<div class="stat"><span class="k">Decisiones</span><span class="v">${cost.count}</span></div>`,
|
|
1213
|
+
`<div class="stat"><span class="k">Local</span><span class="v">${cost.localCount}</span></div>`,
|
|
1214
|
+
`<div class="stat"><span class="k">Frontier</span><span class="v">${cost.frontierCount}</span></div>`,
|
|
1215
|
+
`<div class="stat"><span class="k">Coste real</span><span class="v">${usd(cost.totalEstimatedUSD)}</span></div>`,
|
|
1216
|
+
`<div class="stat"><span class="k">Baseline</span><span class="v">${usd(cost.totalBaselineUSD)}</span></div>`,
|
|
1217
|
+
`<div class="stat"><span class="k">Ahorro</span><span class="v good">${usd(cost.totalSavingsUSD)} (${pct(cost.savingsPct)})</span></div>`,
|
|
1218
|
+
`<div class="stat"><span class="k">Tokens in</span><span class="v">${cost.tokensIn}</span></div>`,
|
|
1219
|
+
`<div class="stat"><span class="k">Tokens out</span><span class="v">${cost.tokensOut}</span></div>`,
|
|
1220
|
+
"</div>",
|
|
1221
|
+
"</section>"
|
|
1222
|
+
].join("");
|
|
1223
|
+
}
|
|
1224
|
+
function tierPanel(cost) {
|
|
1225
|
+
const rows = TIERS.map((tier) => {
|
|
1226
|
+
const summary = cost.byTier[tier];
|
|
1227
|
+
return `<tr><td>${tier}</td><td>${summary.count}</td><td>${usd(summary.estimatedUSD)}</td><td class="good">${usd(summary.savingsUSD)}</td></tr>`;
|
|
1228
|
+
}).join("");
|
|
1229
|
+
return [
|
|
1230
|
+
'<section class="panel" id="panel-tier">',
|
|
1231
|
+
"<h2>Por tier</h2>",
|
|
1232
|
+
"<table><thead><tr><th>Tier</th><th>Nº</th><th>Coste</th><th>Ahorro</th></tr></thead>",
|
|
1233
|
+
`<tbody>${rows}</tbody></table>`,
|
|
1234
|
+
"</section>"
|
|
1235
|
+
].join("");
|
|
1236
|
+
}
|
|
1237
|
+
function loopItemRow(item) {
|
|
1238
|
+
const box = item.done ? "☑" : "☐";
|
|
1239
|
+
const cls = item.done ? "done" : "open";
|
|
1240
|
+
const who = item.assignee === undefined ? "" : `<span class="who">@${escapeHtml(item.assignee)}</span> `;
|
|
1241
|
+
return `<li class="${cls}"><span class="box">${box}</span> ${who}${escapeHtml(item.text)}</li>`;
|
|
1242
|
+
}
|
|
1243
|
+
function loopPanel(loop) {
|
|
1244
|
+
if (loop === undefined) {
|
|
1245
|
+
return [
|
|
1246
|
+
'<section class="panel" id="panel-loop">',
|
|
1247
|
+
"<h2>Loop</h2>",
|
|
1248
|
+
'<p class="muted">Sin backlog activo (no hay <code>openteam-backlog.md</code>).</p>',
|
|
1249
|
+
"</section>"
|
|
1250
|
+
].join("");
|
|
1251
|
+
}
|
|
1252
|
+
const items = loop.items.map(loopItemRow).join("");
|
|
1253
|
+
const commit = loop.lastCommit === undefined ? "" : `<p class="muted">Último commit: <code>${escapeHtml(loop.lastCommit.hash)}</code> ${escapeHtml(loop.lastCommit.subject)}</p>`;
|
|
1254
|
+
return [
|
|
1255
|
+
'<section class="panel" id="panel-loop">',
|
|
1256
|
+
"<h2>Loop</h2>",
|
|
1257
|
+
`<div class="progress" role="progressbar" aria-valuenow="${loop.progressPct}" aria-valuemin="0" aria-valuemax="100"><div class="bar" style="width:${loop.progressPct}%"></div></div>`,
|
|
1258
|
+
`<p class="muted">${loop.done}/${loop.total} completados · ${loop.open} abiertos · ${loop.progressPct}%</p>`,
|
|
1259
|
+
`<ul class="items">${items}</ul>`,
|
|
1260
|
+
commit,
|
|
1261
|
+
"</section>"
|
|
1262
|
+
].join("");
|
|
1263
|
+
}
|
|
1264
|
+
function teamPanel(team) {
|
|
1265
|
+
if (team.length === 0) {
|
|
1266
|
+
return [
|
|
1267
|
+
'<section class="panel" id="panel-team">',
|
|
1268
|
+
"<h2>Equipo</h2>",
|
|
1269
|
+
'<p class="muted">Sin agentes en <code>.opencode/agent/</code>.</p>',
|
|
1270
|
+
"</section>"
|
|
1271
|
+
].join("");
|
|
1272
|
+
}
|
|
1273
|
+
const rows = team.map((agent) => `<tr><td>${escapeHtml(agent.name)}</td><td>${escapeHtml(agent.mode)}</td><td>${escapeHtml(agentModel(agent))}</td></tr>`).join("");
|
|
1274
|
+
return [
|
|
1275
|
+
'<section class="panel" id="panel-team">',
|
|
1276
|
+
"<h2>Equipo</h2>",
|
|
1277
|
+
"<table><thead><tr><th>Agente</th><th>Modo</th><th>LLM</th></tr></thead>",
|
|
1278
|
+
`<tbody>${rows}</tbody></table>`,
|
|
1279
|
+
"</section>"
|
|
1280
|
+
].join("");
|
|
1281
|
+
}
|
|
1282
|
+
function routeRow(route) {
|
|
1283
|
+
return `<tr><td>${route.tier}</td><td class="${route.routeKind}">${route.routeKind}</td><td>${escapeHtml(route.model)}</td><td class="hash">${escapeHtml(route.promptHash)}</td><td>${usd(route.estimatedCostUSD)}</td><td class="good">${usd(route.estimatedSavingsUSD)}</td></tr>`;
|
|
1284
|
+
}
|
|
1285
|
+
function routesPanel(routes) {
|
|
1286
|
+
if (routes.length === 0) {
|
|
1287
|
+
return [
|
|
1288
|
+
'<section class="panel" id="panel-routes">',
|
|
1289
|
+
"<h2>Decisiones recientes</h2>",
|
|
1290
|
+
'<p class="muted">Sin decisiones de routing todavía.</p>',
|
|
1291
|
+
"</section>"
|
|
1292
|
+
].join("");
|
|
1293
|
+
}
|
|
1294
|
+
const rows = routes.map(routeRow).join("");
|
|
1295
|
+
return [
|
|
1296
|
+
'<section class="panel" id="panel-routes">',
|
|
1297
|
+
"<h2>Decisiones recientes</h2>",
|
|
1298
|
+
"<table><thead><tr><th>Tier</th><th>Ruta</th><th>Modelo</th><th>Prompt (hash)</th><th>Coste</th><th>Ahorro</th></tr></thead>",
|
|
1299
|
+
`<tbody>${rows}</tbody></table>`,
|
|
1300
|
+
"</section>"
|
|
1301
|
+
].join("");
|
|
1302
|
+
}
|
|
1303
|
+
function activityRow(entry) {
|
|
1304
|
+
const who = entry.agent === undefined ? "" : `<span class="who">${escapeHtml(entry.agent)}</span> `;
|
|
1305
|
+
return `<li class="${entry.kind}"><span class="kind">${entry.kind}</span> ${who}${escapeHtml(entry.summary)}</li>`;
|
|
1456
1306
|
}
|
|
1457
|
-
function
|
|
1458
|
-
|
|
1459
|
-
return
|
|
1307
|
+
function activityPanel(activity) {
|
|
1308
|
+
if (activity.length === 0) {
|
|
1309
|
+
return [
|
|
1310
|
+
'<section class="panel" id="panel-activity">',
|
|
1311
|
+
"<h2>Actividad</h2>",
|
|
1312
|
+
'<p class="muted">Sin actividad registrada.</p>',
|
|
1313
|
+
"</section>"
|
|
1314
|
+
].join("");
|
|
1460
1315
|
}
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
}
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1316
|
+
const rows = activity.map(activityRow).join("");
|
|
1317
|
+
return [
|
|
1318
|
+
'<section class="panel" id="panel-activity">',
|
|
1319
|
+
"<h2>Actividad</h2>",
|
|
1320
|
+
`<ul class="timeline">${rows}</ul>`,
|
|
1321
|
+
"</section>"
|
|
1322
|
+
].join("");
|
|
1323
|
+
}
|
|
1324
|
+
function totalsPanel(totals) {
|
|
1325
|
+
const t = totals.toolcalls;
|
|
1326
|
+
return [
|
|
1327
|
+
'<section class="panel" id="panel-totals">',
|
|
1328
|
+
"<h2>Telemetría real (todas las sesiones)</h2>",
|
|
1329
|
+
'<div class="grid">',
|
|
1330
|
+
`<div class="stat"><span class="k">Sesiones</span><span class="v">${totals.sessions}</span></div>`,
|
|
1331
|
+
`<div class="stat"><span class="k">Mensajes</span><span class="v">${totals.messageCount}</span></div>`,
|
|
1332
|
+
`<div class="stat"><span class="k">Coste real</span><span class="v">${usd(totals.realCostUSD)}</span></div>`,
|
|
1333
|
+
`<div class="stat"><span class="k">Ahorro est.</span><span class="v good">${usd(totals.estimatedSavingsUSD)}</span></div>`,
|
|
1334
|
+
`<div class="stat"><span class="k">Tokens in</span><span class="v">${totals.tokensIn}</span></div>`,
|
|
1335
|
+
`<div class="stat"><span class="k">Tokens out</span><span class="v">${totals.tokensOut}</span></div>`,
|
|
1336
|
+
`<div class="stat"><span class="k">Reasoning</span><span class="v">${totals.tokensReasoning}</span></div>`,
|
|
1337
|
+
`<div class="stat"><span class="k">Cache r/w</span><span class="v">${totals.tokensCacheRead}/${totals.tokensCacheWrite}</span></div>`,
|
|
1338
|
+
`<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>`,
|
|
1339
|
+
`<div class="stat"><span class="k">Dur. media tool</span><span class="v">${avgMs(t)}</span></div>`,
|
|
1340
|
+
"</div>",
|
|
1341
|
+
"</section>"
|
|
1342
|
+
].join("");
|
|
1343
|
+
}
|
|
1344
|
+
function sessionRow(session) {
|
|
1345
|
+
const models = session.models.map(escapeHtml).join("<br>") || "—";
|
|
1346
|
+
return [
|
|
1347
|
+
"<tr>",
|
|
1348
|
+
`<td class="hash">${escapeHtml(session.sessionID)}</td>`,
|
|
1349
|
+
`<td>${models}</td>`,
|
|
1350
|
+
`<td><span class="local">${session.localCount}</span>/<span class="frontier">${session.frontierCount}</span></td>`,
|
|
1351
|
+
`<td>${usd(session.realCostUSD)}</td>`,
|
|
1352
|
+
`<td>${session.tokensIn}/${session.tokensOut}</td>`,
|
|
1353
|
+
`<td>${session.toolcalls.count} (${avgMs(session.toolcalls)})</td>`,
|
|
1354
|
+
"</tr>"
|
|
1355
|
+
].join("");
|
|
1356
|
+
}
|
|
1357
|
+
function sessionsPanel(sessions) {
|
|
1358
|
+
if (sessions.length === 0) {
|
|
1359
|
+
return [
|
|
1360
|
+
'<section class="panel" id="panel-sessions">',
|
|
1361
|
+
"<h2>Sesiones</h2>",
|
|
1362
|
+
'<p class="muted">Sin sesiones registradas todavía.</p>',
|
|
1363
|
+
"</section>"
|
|
1364
|
+
].join("");
|
|
1365
|
+
}
|
|
1366
|
+
const rows = sessions.map(sessionRow).join("");
|
|
1367
|
+
return [
|
|
1368
|
+
'<section class="panel" id="panel-sessions">',
|
|
1369
|
+
"<h2>Sesiones</h2>",
|
|
1370
|
+
"<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>",
|
|
1371
|
+
`<tbody>${rows}</tbody></table>`,
|
|
1372
|
+
"</section>"
|
|
1373
|
+
].join("");
|
|
1374
|
+
}
|
|
1375
|
+
function decisionRow(decision) {
|
|
1376
|
+
const who = decision.agent === undefined ? "" : `<span class="who">${escapeHtml(decision.agent)}</span> `;
|
|
1377
|
+
const tags = decision.tags === undefined || decision.tags.length === 0 ? "" : ` ${decision.tags.map((tag) => `<span class="tag">#${escapeHtml(tag)}</span>`).join(" ")}`;
|
|
1378
|
+
return `<li class="decision"><span class="kind">decision</span> ${who}${escapeHtml(decision.summary)}${tags}</li>`;
|
|
1379
|
+
}
|
|
1380
|
+
function decisionsPanel(decisions) {
|
|
1381
|
+
if (decisions.length === 0) {
|
|
1382
|
+
return [
|
|
1383
|
+
'<section class="panel" id="panel-decisions">',
|
|
1384
|
+
"<h2>Decisiones</h2>",
|
|
1385
|
+
'<p class="muted">Sin decisiones registradas (el scribe escribe en <code>openteam-decisions.md</code>).</p>',
|
|
1386
|
+
"</section>"
|
|
1387
|
+
].join("");
|
|
1388
|
+
}
|
|
1389
|
+
const rows = decisions.map(decisionRow).join("");
|
|
1390
|
+
return [
|
|
1391
|
+
'<section class="panel" id="panel-decisions">',
|
|
1392
|
+
"<h2>Decisiones</h2>",
|
|
1393
|
+
`<ul class="timeline">${rows}</ul>`,
|
|
1394
|
+
"</section>"
|
|
1395
|
+
].join("");
|
|
1396
|
+
}
|
|
1397
|
+
function meetingRow(meeting) {
|
|
1398
|
+
const roles = meeting.roles.map(escapeHtml).join(", ");
|
|
1399
|
+
const purpose = meeting.purpose === undefined ? "" : `: ${escapeHtml(meeting.purpose)}`;
|
|
1400
|
+
return `<li class="agent"><span class="kind">reunión</span> <span class="who">${roles}</span>${purpose}</li>`;
|
|
1401
|
+
}
|
|
1402
|
+
function meetingsPanel(meetings) {
|
|
1403
|
+
if (meetings.length === 0) {
|
|
1404
|
+
return [
|
|
1405
|
+
'<section class="panel" id="panel-meetings">',
|
|
1406
|
+
"<h2>Reuniones</h2>",
|
|
1407
|
+
'<p class="muted">Sin reuniones multi-agente todavía.</p>',
|
|
1408
|
+
"</section>"
|
|
1409
|
+
].join("");
|
|
1410
|
+
}
|
|
1411
|
+
const rows = meetings.map(meetingRow).join("");
|
|
1412
|
+
return [
|
|
1413
|
+
'<section class="panel" id="panel-meetings">',
|
|
1414
|
+
"<h2>Reuniones</h2>",
|
|
1415
|
+
`<ul class="timeline">${rows}</ul>`,
|
|
1416
|
+
"</section>"
|
|
1417
|
+
].join("");
|
|
1418
|
+
}
|
|
1419
|
+
var STATUS_LABELS = {
|
|
1420
|
+
running: "activa",
|
|
1421
|
+
idle: "en reposo",
|
|
1422
|
+
"waiting-input": "esperando input",
|
|
1423
|
+
ended: "finalizada"
|
|
1424
|
+
};
|
|
1425
|
+
function statusBadge(status) {
|
|
1426
|
+
return `<span class="badge status-${status}">${STATUS_LABELS[status]}</span>`;
|
|
1427
|
+
}
|
|
1428
|
+
function tabBar(sessions) {
|
|
1429
|
+
const allTab = '<button type="button" class="tab" data-tab="all">Todas</button>';
|
|
1430
|
+
const tabs = sessions.map((tab) => {
|
|
1431
|
+
const id = escapeHtml(tab.sessionID);
|
|
1432
|
+
const short = escapeHtml(shortSessionID(tab.sessionID));
|
|
1433
|
+
return `<button type="button" class="tab" data-tab="${id}"><span class="dot status-${tab.status}"></span>${short}</button>`;
|
|
1434
|
+
}).join("");
|
|
1435
|
+
return `<nav class="tabs" id="tabbar">${allTab}${tabs}</nav>`;
|
|
1436
|
+
}
|
|
1437
|
+
function shortSessionID(sessionID) {
|
|
1438
|
+
return sessionID.length <= 12 ? sessionID : `${sessionID.slice(0, 12)}…`;
|
|
1439
|
+
}
|
|
1440
|
+
function sessionHeaderPanel(tab) {
|
|
1441
|
+
const endpoint = tab.endpoint === undefined ? '<span class="muted">endpoint no anunciado</span>' : `<code>${escapeHtml(tab.endpoint.url)}</code>`;
|
|
1442
|
+
const dir = tab.endpoint?.directory === undefined ? "" : `<div class="stat"><span class="k">Directorio</span><span class="v mono">${escapeHtml(tab.endpoint.directory)}</span></div>`;
|
|
1443
|
+
const title = tab.endpoint?.title === undefined ? "" : `<div class="stat"><span class="k">Título</span><span class="v">${escapeHtml(tab.endpoint.title)}</span></div>`;
|
|
1444
|
+
const s = tab.summary;
|
|
1445
|
+
return [
|
|
1446
|
+
'<section class="panel">',
|
|
1447
|
+
`<h2>Sesión ${escapeHtml(shortSessionID(tab.sessionID))} ${statusBadge(tab.status)}</h2>`,
|
|
1448
|
+
'<div class="grid">',
|
|
1449
|
+
`<div class="stat"><span class="k">Endpoint</span><span class="v">${endpoint}</span></div>`,
|
|
1450
|
+
dir,
|
|
1451
|
+
title,
|
|
1452
|
+
`<div class="stat"><span class="k">Coste real</span><span class="v">${usd(s.realCostUSD)}</span></div>`,
|
|
1453
|
+
`<div class="stat"><span class="k">Tokens in/out</span><span class="v">${s.tokensIn}/${s.tokensOut}</span></div>`,
|
|
1454
|
+
`<div class="stat"><span class="k">Local/Frontier</span><span class="v"><span class="local">${s.localCount}</span>/<span class="frontier">${s.frontierCount}</span></span></div>`,
|
|
1455
|
+
`<div class="stat"><span class="k">Toolcalls</span><span class="v">${s.toolcalls.count} (${avgMs(s.toolcalls)})</span></div>`,
|
|
1456
|
+
"</div>",
|
|
1457
|
+
"</section>"
|
|
1458
|
+
].join("");
|
|
1459
|
+
}
|
|
1460
|
+
function terminalPanel(tab) {
|
|
1461
|
+
return [
|
|
1462
|
+
'<section class="panel term">',
|
|
1463
|
+
"<h2>Terminal (sesión)</h2>",
|
|
1464
|
+
'<pre class="termlog" aria-live="polite"></pre>',
|
|
1465
|
+
'<form class="terminput" autocomplete="off">',
|
|
1466
|
+
`<input type="text" name="text" placeholder="Enviar prompt a ${escapeHtml(shortSessionID(tab.sessionID))}…" aria-label="Prompt a la sesión">`,
|
|
1467
|
+
'<button type="submit">Enviar</button>',
|
|
1468
|
+
"</form>",
|
|
1469
|
+
"</section>"
|
|
1470
|
+
].join("");
|
|
1471
|
+
}
|
|
1472
|
+
function ptyPanel(tab) {
|
|
1473
|
+
return [
|
|
1474
|
+
`<section class="panel pty" data-pty-session="${escapeHtml(tab.sessionID)}">`,
|
|
1475
|
+
"<h2>Terminal (PTY)</h2>",
|
|
1476
|
+
'<pre class="ptylog" aria-live="polite"></pre>',
|
|
1477
|
+
'<form class="ptyinput" autocomplete="off" hidden>',
|
|
1478
|
+
'<input type="text" name="cmd" placeholder="Comando…" aria-label="Comando de la terminal">',
|
|
1479
|
+
'<button type="submit">Ejecutar</button>',
|
|
1480
|
+
"</form>",
|
|
1481
|
+
'<button type="button" class="pty-start">Abrir shell</button>',
|
|
1482
|
+
"</section>"
|
|
1483
|
+
].join("");
|
|
1484
|
+
}
|
|
1485
|
+
function sessionView(tab, terminalEnabled, ptyEnabled) {
|
|
1486
|
+
const body = [
|
|
1487
|
+
sessionHeaderPanel(tab),
|
|
1488
|
+
terminalEnabled ? terminalPanel(tab) : "",
|
|
1489
|
+
terminalEnabled && ptyEnabled ? ptyPanel(tab) : "",
|
|
1490
|
+
routesPanel(tab.recentRoutes),
|
|
1491
|
+
decisionsPanel(tab.decisions),
|
|
1492
|
+
meetingsPanel(tab.meetings),
|
|
1493
|
+
activityPanel(tab.activity)
|
|
1494
|
+
].join("");
|
|
1495
|
+
return `<div class="view" data-view="${escapeHtml(tab.sessionID)}" hidden>${body}</div>`;
|
|
1536
1496
|
}
|
|
1497
|
+
var STYLE = `
|
|
1498
|
+
:root{color-scheme:dark;--bg:#0f1115;--panel:#171a21;--fg:#e6e8eb;--muted:#8b929c;--good:#3fb950;--local:#58a6ff;--frontier:#d29922;--line:#262b34}
|
|
1499
|
+
*{box-sizing:border-box}
|
|
1500
|
+
body{margin:0;background:var(--bg);color:var(--fg);font:14px/1.5 ui-sans-serif,system-ui,Segoe UI,Roboto,sans-serif}
|
|
1501
|
+
header{padding:16px 24px;border-bottom:1px solid var(--line);display:flex;justify-content:space-between;align-items:baseline;gap:12px;flex-wrap:wrap}
|
|
1502
|
+
header h1{font-size:18px;margin:0}
|
|
1503
|
+
header .meta{color:var(--muted);font-size:12px}
|
|
1504
|
+
main{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:16px;padding:24px}
|
|
1505
|
+
.panel{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:16px}
|
|
1506
|
+
.panel h2{font-size:14px;margin:0 0 12px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}
|
|
1507
|
+
.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}
|
|
1508
|
+
.stat{display:flex;flex-direction:column;gap:2px}
|
|
1509
|
+
.stat .k{color:var(--muted);font-size:12px}
|
|
1510
|
+
.stat .v{font-size:18px;font-weight:600}
|
|
1511
|
+
.good{color:var(--good)}
|
|
1512
|
+
.local{color:var(--local)}
|
|
1513
|
+
.frontier{color:var(--frontier)}
|
|
1514
|
+
.muted{color:var(--muted)}
|
|
1515
|
+
table{width:100%;border-collapse:collapse;font-size:13px}
|
|
1516
|
+
th,td{text-align:left;padding:6px 8px;border-bottom:1px solid var(--line)}
|
|
1517
|
+
th{color:var(--muted);font-weight:600}
|
|
1518
|
+
.hash{font-family:ui-monospace,Consolas,monospace;color:var(--muted)}
|
|
1519
|
+
.progress{height:10px;background:#0b0d11;border-radius:6px;overflow:hidden;border:1px solid var(--line)}
|
|
1520
|
+
.progress .bar{height:100%;background:var(--good)}
|
|
1521
|
+
ul.items,ul.timeline{list-style:none;margin:8px 0 0;padding:0;max-height:320px;overflow:auto}
|
|
1522
|
+
ul.items li,ul.timeline li{padding:4px 0;border-bottom:1px solid var(--line)}
|
|
1523
|
+
ul.items li.done{color:var(--muted)}
|
|
1524
|
+
ul.items .box{font-family:monospace}
|
|
1525
|
+
.who{color:var(--local)}
|
|
1526
|
+
.tag{color:var(--frontier);font-size:12px}
|
|
1527
|
+
.kind{display:inline-block;min-width:66px;color:var(--muted);font-size:12px}
|
|
1528
|
+
code{font-family:ui-monospace,Consolas,monospace;background:#0b0d11;padding:1px 5px;border-radius:4px}
|
|
1529
|
+
.mono{font-family:ui-monospace,Consolas,monospace;font-size:12px}
|
|
1530
|
+
.tabs{display:flex;gap:6px;padding:12px 24px 0;flex-wrap:wrap;border-bottom:1px solid var(--line)}
|
|
1531
|
+
.tabs .tab{background:var(--panel);color:var(--fg);border:1px solid var(--line);border-bottom:none;border-radius:8px 8px 0 0;padding:8px 14px;cursor:pointer;font:inherit;display:flex;align-items:center;gap:6px}
|
|
1532
|
+
.tabs .tab.active{background:var(--bg);border-color:var(--local);color:var(--fg)}
|
|
1533
|
+
.dot{width:8px;height:8px;border-radius:50%;display:inline-block;background:var(--muted)}
|
|
1534
|
+
.badge{font-size:11px;padding:1px 8px;border-radius:10px;border:1px solid var(--line);color:var(--muted);vertical-align:middle}
|
|
1535
|
+
.status-running{background:var(--good)}
|
|
1536
|
+
.badge.status-running{color:var(--good);border-color:var(--good)}
|
|
1537
|
+
.status-idle{background:var(--muted)}
|
|
1538
|
+
.badge.status-idle{color:var(--muted)}
|
|
1539
|
+
.status-waiting-input{background:var(--frontier)}
|
|
1540
|
+
.badge.status-waiting-input{color:var(--frontier);border-color:var(--frontier)}
|
|
1541
|
+
.status-ended{background:#6e7681}
|
|
1542
|
+
.badge.status-ended{color:#6e7681}
|
|
1543
|
+
.term .termlog{background:#0b0d11;border:1px solid var(--line);border-radius:8px;padding:10px;max-height:340px;overflow:auto;font-family:ui-monospace,Consolas,monospace;font-size:12px;white-space:pre-wrap}
|
|
1544
|
+
.term .tl{padding:1px 0}
|
|
1545
|
+
.term .tl.input{color:var(--local)}
|
|
1546
|
+
.term .tl.tool{color:var(--frontier)}
|
|
1547
|
+
.term .tl.status{color:var(--muted)}
|
|
1548
|
+
.term .tl.error{color:#f85149}
|
|
1549
|
+
.term .tl.permission{color:var(--frontier);font-weight:600}
|
|
1550
|
+
.term form.terminput{display:flex;gap:8px;margin-top:10px}
|
|
1551
|
+
.term form.terminput input{flex:1;background:#0b0d11;color:var(--fg);border:1px solid var(--line);border-radius:8px;padding:8px 10px;font:inherit}
|
|
1552
|
+
.term form.terminput button{background:var(--local);color:#04122b;border:none;border-radius:8px;padding:8px 16px;cursor:pointer;font:inherit;font-weight:600}
|
|
1553
|
+
.pty .ptylog{background:#05070a;border:1px solid var(--line);border-radius:8px;padding:10px;max-height:340px;overflow:auto;font-family:ui-monospace,Consolas,monospace;font-size:12px;white-space:pre-wrap;color:#c8d1dc}
|
|
1554
|
+
.pty form.ptyinput{display:flex;gap:8px;margin-top:10px}
|
|
1555
|
+
.pty form.ptyinput input{flex:1;background:#05070a;color:var(--fg);border:1px solid var(--line);border-radius:8px;padding:8px 10px;font:inherit}
|
|
1556
|
+
.pty form.ptyinput button,.pty .pty-start{background:var(--frontier);color:#1a1200;border:none;border-radius:8px;padding:8px 16px;cursor:pointer;font:inherit;font-weight:600;margin-top:8px}
|
|
1557
|
+
.pty .pty-start:disabled{opacity:.5;cursor:default}
|
|
1558
|
+
`;
|
|
1559
|
+
var CLIENT_JS = `
|
|
1560
|
+
(function(){
|
|
1561
|
+
function reloadIfChanged(prev){
|
|
1562
|
+
fetch('/api/state').then(function(r){return r.json()}).then(function(s){
|
|
1563
|
+
if(s.generatedAt!==prev){location.reload()}
|
|
1564
|
+
}).catch(function(){});
|
|
1565
|
+
}
|
|
1566
|
+
var current=document.documentElement.getAttribute('data-generated')||'';
|
|
1567
|
+
if('EventSource' in window){
|
|
1568
|
+
try{
|
|
1569
|
+
var es=new EventSource('/events');
|
|
1570
|
+
es.addEventListener('state',function(){location.reload()});
|
|
1571
|
+
es.onerror=function(){/* fallback below */};
|
|
1572
|
+
}catch(e){/* ignore */}
|
|
1573
|
+
}
|
|
1574
|
+
var ms=parseInt(document.documentElement.getAttribute('data-refresh')||'2000',10);
|
|
1575
|
+
setInterval(function(){reloadIfChanged(current)},isNaN(ms)?2000:Math.max(250,ms));
|
|
1537
1576
|
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
var
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
selected: ModelRefSchema,
|
|
1548
|
-
rationale: z5.string(),
|
|
1549
|
-
estimatedCostUSD: z5.number().finite().min(0),
|
|
1550
|
-
baselineCostUSD: z5.number().finite().min(0),
|
|
1551
|
-
estimatedSavingsUSD: z5.number().finite(),
|
|
1552
|
-
budgetAction: z5.string().min(1),
|
|
1553
|
-
tokensIn: z5.number().int().min(0).optional(),
|
|
1554
|
-
tokensOut: z5.number().int().min(0).optional()
|
|
1555
|
-
});
|
|
1556
|
-
|
|
1557
|
-
// src/telemetry/read.ts
|
|
1558
|
-
var DEFAULT_TELEMETRY_PATH = ".opencode/openteam-telemetry.jsonl";
|
|
1559
|
-
function parseCostRecordsJsonl(text) {
|
|
1560
|
-
const records = [];
|
|
1561
|
-
for (const line of text.split(`
|
|
1562
|
-
`)) {
|
|
1563
|
-
const trimmed = line.trim();
|
|
1564
|
-
if (trimmed.length === 0) {
|
|
1565
|
-
continue;
|
|
1566
|
-
}
|
|
1567
|
-
let candidate;
|
|
1568
|
-
try {
|
|
1569
|
-
candidate = JSON.parse(trimmed);
|
|
1570
|
-
} catch {
|
|
1571
|
-
continue;
|
|
1572
|
-
}
|
|
1573
|
-
const parsed = CostRecordSchema.safeParse(candidate);
|
|
1574
|
-
if (parsed.success) {
|
|
1575
|
-
records.push(parsed.data);
|
|
1577
|
+
function selectTab(name){
|
|
1578
|
+
var tabs=document.querySelectorAll('#tabbar .tab');
|
|
1579
|
+
for(var i=0;i<tabs.length;i++){tabs[i].classList.toggle('active',tabs[i].getAttribute('data-tab')===name)}
|
|
1580
|
+
var views=document.querySelectorAll('main .view');
|
|
1581
|
+
var matched=false;
|
|
1582
|
+
for(var j=0;j<views.length;j++){
|
|
1583
|
+
var show=views[j].getAttribute('data-view')===name;
|
|
1584
|
+
views[j].hidden=!show;
|
|
1585
|
+
if(show){matched=true;if(name!=='all'){connectTerminal(views[j],name)}}
|
|
1576
1586
|
}
|
|
1587
|
+
if(!matched){return false}
|
|
1588
|
+
try{sessionStorage.setItem('openteam.tab',name)}catch(e){}
|
|
1589
|
+
return true;
|
|
1577
1590
|
}
|
|
1578
|
-
|
|
1579
|
-
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
var
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
continue;
|
|
1591
|
+
function connectTerminal(view,id){
|
|
1592
|
+
if(view.__wired){return}
|
|
1593
|
+
var root=document.documentElement;
|
|
1594
|
+
var token=root.getAttribute('data-token');
|
|
1595
|
+
if(root.getAttribute('data-terminal')!=='1'||!token){return}
|
|
1596
|
+
var log=view.querySelector('.termlog');
|
|
1597
|
+
if(!log){return}
|
|
1598
|
+
view.__wired=true;
|
|
1599
|
+
function append(cls,text){
|
|
1600
|
+
var line=document.createElement('div');
|
|
1601
|
+
line.className='tl '+cls;line.textContent=text;
|
|
1602
|
+
log.appendChild(line);log.scrollTop=log.scrollHeight;
|
|
1591
1603
|
}
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1604
|
+
var q='/console/session/'+encodeURIComponent(id);
|
|
1605
|
+
var tk='token='+encodeURIComponent(token);
|
|
1606
|
+
if('EventSource' in window){
|
|
1607
|
+
try{
|
|
1608
|
+
var es=new EventSource(q+'/stream?'+tk);
|
|
1609
|
+
es.addEventListener('frame',function(e){try{var f=JSON.parse(e.data);append(f.kind,f.text)}catch(_){}});
|
|
1610
|
+
es.onerror=function(){/* reconecta solo */};
|
|
1611
|
+
}catch(_){/* ignore */}
|
|
1597
1612
|
}
|
|
1598
|
-
|
|
1599
|
-
if
|
|
1600
|
-
|
|
1613
|
+
var form=view.querySelector('form.terminput');
|
|
1614
|
+
if(form){
|
|
1615
|
+
form.addEventListener('submit',function(e){
|
|
1616
|
+
e.preventDefault();
|
|
1617
|
+
var inp=form.querySelector('input[name=text]');
|
|
1618
|
+
var text=inp.value;
|
|
1619
|
+
if(!text){return}
|
|
1620
|
+
inp.value='';append('input','› '+text);
|
|
1621
|
+
fetch(q+'/input?'+tk,{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({text:text})})
|
|
1622
|
+
.then(function(r){if(!r.ok){append('error','✖ envío rechazado ('+r.status+')')}})
|
|
1623
|
+
.catch(function(){append('error','✖ no se pudo enviar')});
|
|
1624
|
+
});
|
|
1601
1625
|
}
|
|
1626
|
+
connectPty(view,id,q,tk);
|
|
1602
1627
|
}
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1628
|
+
function connectPty(view,id,q,tk){
|
|
1629
|
+
var root=document.documentElement;
|
|
1630
|
+
if(root.getAttribute('data-pty')!=='1'){return}
|
|
1631
|
+
var pane=view.querySelector('.pty');
|
|
1632
|
+
if(!pane){return}
|
|
1633
|
+
var log=pane.querySelector('.ptylog');
|
|
1634
|
+
var startBtn=pane.querySelector('.pty-start');
|
|
1635
|
+
var input=pane.querySelector('form.ptyinput');
|
|
1636
|
+
function plog(text){
|
|
1637
|
+
var d=document.createElement('span');d.textContent=text;
|
|
1638
|
+
log.appendChild(d);log.scrollTop=log.scrollHeight;
|
|
1639
|
+
}
|
|
1640
|
+
if(!startBtn){return}
|
|
1641
|
+
startBtn.addEventListener('click',function(){
|
|
1642
|
+
startBtn.disabled=true;plog('Abriendo shell…
|
|
1643
|
+
');
|
|
1644
|
+
fetch(q+'/pty?'+tk,{method:'POST',headers:{'content-type':'application/json'},body:'{}'})
|
|
1645
|
+
.then(function(r){return r.ok?r.json():Promise.reject(r.status)})
|
|
1646
|
+
.then(function(info){openPtyWs(info.wsUrl)})
|
|
1647
|
+
.catch(function(s){plog('✖ no se pudo abrir PTY ('+s+')
|
|
1648
|
+
');startBtn.disabled=false});
|
|
1649
|
+
});
|
|
1650
|
+
function openPtyWs(url){
|
|
1651
|
+
try{
|
|
1652
|
+
var ws=new WebSocket(url);
|
|
1653
|
+
ws.binaryType='arraybuffer';
|
|
1654
|
+
ws.onmessage=function(e){
|
|
1655
|
+
var data=e.data;
|
|
1656
|
+
if(data instanceof ArrayBuffer){data=new TextDecoder().decode(new Uint8Array(data))}
|
|
1657
|
+
plog(data);
|
|
1658
|
+
};
|
|
1659
|
+
ws.onclose=function(){plog('
|
|
1660
|
+
[shell cerrada]
|
|
1661
|
+
');startBtn.disabled=false};
|
|
1662
|
+
ws.onerror=function(){plog('
|
|
1663
|
+
✖ error de WebSocket
|
|
1664
|
+
')};
|
|
1665
|
+
if(input){
|
|
1666
|
+
input.hidden=false;
|
|
1667
|
+
input.addEventListener('submit',function(e){
|
|
1668
|
+
e.preventDefault();
|
|
1669
|
+
var c=input.querySelector('input[name=cmd]');
|
|
1670
|
+
if(!c.value){return}
|
|
1671
|
+
ws.send(c.value+'\r');c.value='';
|
|
1672
|
+
});
|
|
1673
|
+
}
|
|
1674
|
+
}catch(_){plog('
|
|
1675
|
+
✖ WebSocket no disponible
|
|
1676
|
+
');startBtn.disabled=false}
|
|
1677
|
+
}
|
|
1624
1678
|
}
|
|
1625
|
-
|
|
1626
|
-
|
|
1679
|
+
var bar=document.getElementById('tabbar');
|
|
1680
|
+
if(bar){
|
|
1681
|
+
bar.addEventListener('click',function(e){
|
|
1682
|
+
var t=e.target;
|
|
1683
|
+
while(t&&t!==bar&&!t.getAttribute('data-tab')){t=t.parentNode}
|
|
1684
|
+
if(t&&t.getAttribute('data-tab')){selectTab(t.getAttribute('data-tab'))}
|
|
1685
|
+
});
|
|
1627
1686
|
}
|
|
1628
|
-
|
|
1687
|
+
var saved='all';
|
|
1688
|
+
try{saved=sessionStorage.getItem('openteam.tab')||'all'}catch(e){}
|
|
1689
|
+
if(!selectTab(saved)){selectTab('all')}
|
|
1690
|
+
})();
|
|
1691
|
+
`;
|
|
1692
|
+
function renderConsoleHtml(state, options = {}) {
|
|
1693
|
+
const refreshMs = options.refreshMs ?? 2000;
|
|
1694
|
+
const terminalEnabled = options.terminalEnabled === true;
|
|
1695
|
+
const ptyEnabled = options.ptyEnabled === true;
|
|
1696
|
+
const allBody = [
|
|
1697
|
+
summaryPanel(state.cost),
|
|
1698
|
+
totalsPanel(state.totals),
|
|
1699
|
+
sessionsPanel(state.sessions),
|
|
1700
|
+
loopPanel(state.loop),
|
|
1701
|
+
teamPanel(state.team),
|
|
1702
|
+
tierPanel(state.cost),
|
|
1703
|
+
routesPanel(state.recentRoutes),
|
|
1704
|
+
decisionsPanel(state.decisions),
|
|
1705
|
+
meetingsPanel(state.meetings),
|
|
1706
|
+
activityPanel(state.activity)
|
|
1707
|
+
].join("");
|
|
1708
|
+
const allView = `<div class="view" data-view="all">${allBody}</div>`;
|
|
1709
|
+
const sessionViews = state.sessionsDetail.map((tab) => sessionView(tab, terminalEnabled, ptyEnabled)).join("");
|
|
1710
|
+
const body = allView + sessionViews;
|
|
1711
|
+
const session = state.session.id === undefined ? "" : ` · sesión ${escapeHtml(state.session.id)}`;
|
|
1712
|
+
const tokenAttr = terminalEnabled && options.consoleToken !== undefined ? ` data-token="${escapeHtml(options.consoleToken)}" data-terminal="1"${ptyEnabled ? ' data-pty="1"' : ""}` : "";
|
|
1713
|
+
return [
|
|
1714
|
+
"<!doctype html>",
|
|
1715
|
+
`<html lang="es" data-generated="${escapeHtml(state.generatedAt)}" data-refresh="${refreshMs}"${tokenAttr}>`,
|
|
1716
|
+
"<head>",
|
|
1717
|
+
'<meta charset="utf-8">',
|
|
1718
|
+
'<meta name="viewport" content="width=device-width,initial-scale=1">',
|
|
1719
|
+
"<title>openteam console</title>",
|
|
1720
|
+
`<style>${STYLE}</style>`,
|
|
1721
|
+
"</head>",
|
|
1722
|
+
"<body>",
|
|
1723
|
+
"<header>",
|
|
1724
|
+
"<h1>openteam console</h1>",
|
|
1725
|
+
`<span class="meta">actualizado ${escapeHtml(state.generatedAt)}${session}</span>`,
|
|
1726
|
+
"</header>",
|
|
1727
|
+
tabBar(state.sessionsDetail),
|
|
1728
|
+
`<main>${body}</main>`,
|
|
1729
|
+
`<script>${CLIENT_JS}</script>`,
|
|
1730
|
+
"</body>",
|
|
1731
|
+
"</html>"
|
|
1732
|
+
].join("");
|
|
1629
1733
|
}
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1734
|
+
|
|
1735
|
+
// src/commands/report.ts
|
|
1736
|
+
var TIERS2 = [
|
|
1737
|
+
"trivial",
|
|
1738
|
+
"simple",
|
|
1739
|
+
"moderate",
|
|
1740
|
+
"hard"
|
|
1741
|
+
];
|
|
1742
|
+
function emptyTierSummary() {
|
|
1743
|
+
return {
|
|
1744
|
+
trivial: { count: 0, estimatedUSD: 0, savingsUSD: 0 },
|
|
1745
|
+
simple: { count: 0, estimatedUSD: 0, savingsUSD: 0 },
|
|
1746
|
+
moderate: { count: 0, estimatedUSD: 0, savingsUSD: 0 },
|
|
1747
|
+
hard: { count: 0, estimatedUSD: 0, savingsUSD: 0 }
|
|
1644
1748
|
};
|
|
1645
|
-
if (event.tokensIn !== undefined) {
|
|
1646
|
-
record.tokensIn = event.tokensIn;
|
|
1647
|
-
}
|
|
1648
|
-
if (event.tokensOut !== undefined) {
|
|
1649
|
-
record.tokensOut = event.tokensOut;
|
|
1650
|
-
}
|
|
1651
|
-
return record;
|
|
1652
1749
|
}
|
|
1653
|
-
|
|
1654
|
-
const
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1750
|
+
function summarizeCostRecords(records) {
|
|
1751
|
+
const report = {
|
|
1752
|
+
count: 0,
|
|
1753
|
+
localCount: 0,
|
|
1754
|
+
frontierCount: 0,
|
|
1755
|
+
totalEstimatedUSD: 0,
|
|
1756
|
+
totalBaselineUSD: 0,
|
|
1757
|
+
totalSavingsUSD: 0,
|
|
1758
|
+
savingsPct: 0,
|
|
1759
|
+
tokensIn: 0,
|
|
1760
|
+
tokensOut: 0,
|
|
1761
|
+
byTier: emptyTierSummary()
|
|
1762
|
+
};
|
|
1763
|
+
for (const record of records) {
|
|
1764
|
+
report.count += 1;
|
|
1765
|
+
if (record.routeKind === "local") {
|
|
1766
|
+
report.localCount += 1;
|
|
1767
|
+
} else {
|
|
1768
|
+
report.frontierCount += 1;
|
|
1659
1769
|
}
|
|
1770
|
+
report.totalEstimatedUSD += record.estimatedCostUSD;
|
|
1771
|
+
report.totalBaselineUSD += record.baselineCostUSD;
|
|
1772
|
+
report.totalSavingsUSD += record.estimatedSavingsUSD;
|
|
1773
|
+
report.tokensIn += record.tokensIn ?? 0;
|
|
1774
|
+
report.tokensOut += record.tokensOut ?? 0;
|
|
1775
|
+
const tier = report.byTier[record.tier];
|
|
1776
|
+
tier.count += 1;
|
|
1777
|
+
tier.estimatedUSD += record.estimatedCostUSD;
|
|
1778
|
+
tier.savingsUSD += record.estimatedSavingsUSD;
|
|
1660
1779
|
}
|
|
1661
|
-
|
|
1780
|
+
report.savingsPct = report.totalBaselineUSD > 0 ? report.totalSavingsUSD / report.totalBaselineUSD * 100 : 0;
|
|
1781
|
+
return report;
|
|
1662
1782
|
}
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
const events = perFile.flat();
|
|
1670
|
-
if (deps.legacyTelemetryPath !== undefined) {
|
|
1671
|
-
const legacyText = await deps.storage.read(deps.legacyTelemetryPath);
|
|
1672
|
-
if (legacyText !== undefined) {
|
|
1673
|
-
for (const record of parseCostRecordsJsonl(legacyText)) {
|
|
1674
|
-
events.push(costRecordToRouteEvent(record));
|
|
1675
|
-
}
|
|
1676
|
-
}
|
|
1783
|
+
function usd2(value) {
|
|
1784
|
+
return `$${value.toFixed(5)}`;
|
|
1785
|
+
}
|
|
1786
|
+
function renderReport(report) {
|
|
1787
|
+
if (report.count === 0) {
|
|
1788
|
+
return "openteam report: sin registros de telemetría todavía.";
|
|
1677
1789
|
}
|
|
1678
|
-
|
|
1790
|
+
const lines = [
|
|
1791
|
+
"openteam report:",
|
|
1792
|
+
` decisiones: ${report.count} (local ${report.localCount} · frontier ${report.frontierCount})`,
|
|
1793
|
+
` coste real: ${usd2(report.totalEstimatedUSD)}`,
|
|
1794
|
+
` baseline: ${usd2(report.totalBaselineUSD)}`,
|
|
1795
|
+
` ahorro: ${usd2(report.totalSavingsUSD)} (${report.savingsPct.toFixed(2)}%)`,
|
|
1796
|
+
` tokens: in ${report.tokensIn} · out ${report.tokensOut}`,
|
|
1797
|
+
" por tier:"
|
|
1798
|
+
];
|
|
1799
|
+
for (const tier of TIERS2) {
|
|
1800
|
+
const summary = report.byTier[tier];
|
|
1801
|
+
lines.push(` ${tier.padEnd(9)} ${summary.count} · coste ${usd2(summary.estimatedUSD)} · ahorro ${usd2(summary.savingsUSD)}`);
|
|
1802
|
+
}
|
|
1803
|
+
return lines.join(`
|
|
1804
|
+
`);
|
|
1679
1805
|
}
|
|
1680
1806
|
|
|
1681
1807
|
// src/console/backlog.ts
|
|
@@ -2771,6 +2897,20 @@ async function runConsoleServe(deps, options) {
|
|
|
2771
2897
|
if (config.console.remoteStorage) {
|
|
2772
2898
|
deps.log("Storage remoto expuesto en /storage/* (read-only, requiere token).");
|
|
2773
2899
|
}
|
|
2900
|
+
if (config.storage.sqliteIndex.enabled) {
|
|
2901
|
+
const buildIndex = deps.buildSqliteIndex ?? buildSqliteIndexFromConfig;
|
|
2902
|
+
try {
|
|
2903
|
+
const result = await buildIndex(config.storage.sqliteIndex.path, deps.sessionsDir, {
|
|
2904
|
+
storage: deps.storage,
|
|
2905
|
+
...deps.legacyTelemetryPath !== undefined ? { legacyTelemetryPath: deps.legacyTelemetryPath } : {}
|
|
2906
|
+
});
|
|
2907
|
+
result.index.close();
|
|
2908
|
+
deps.log(`Índice SQLite reconstruido (${result.events} eventos) en ${config.storage.sqliteIndex.path}.`);
|
|
2909
|
+
} catch (error) {
|
|
2910
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2911
|
+
deps.log(`Índice SQLite deshabilitado (no disponible): ${message}`);
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2774
2914
|
if (options.open && deps.openBrowser !== undefined) {
|
|
2775
2915
|
try {
|
|
2776
2916
|
await deps.openBrowser(runtime.url);
|
|
@@ -4044,6 +4184,7 @@ var HELP = [
|
|
|
4044
4184
|
" openteam console Lanza la Console web multi-sesión (Ctrl+C para parar; --open abre el navegador)",
|
|
4045
4185
|
" openteam console --status Muestra la config de la Console sin lanzarla",
|
|
4046
4186
|
" openteam tunnel Expone la Console vía Dev Tunnels (--terminal expone opencode; Ctrl+C para cerrar)",
|
|
4187
|
+
" openteam mcp Servidor MCP de solo lectura (memoria del equipo) sobre stdio",
|
|
4047
4188
|
" openteam report Resumen de coste/ahorro (telemetría)",
|
|
4048
4189
|
" openteam yolo status Muestra si el modo YOLO está activo",
|
|
4049
4190
|
" openteam yolo on Activa YOLO (opencode auto-aprueba todo)",
|
|
@@ -4804,6 +4945,220 @@ function loadOpenTeamConfig(raw) {
|
|
|
4804
4945
|
return OpenTeamConfigSchema.parse(raw ?? {});
|
|
4805
4946
|
}
|
|
4806
4947
|
|
|
4948
|
+
// src/mcp/teamMemory.ts
|
|
4949
|
+
function buildTeamMemory(events) {
|
|
4950
|
+
const aggregate = aggregateEvents(events);
|
|
4951
|
+
return {
|
|
4952
|
+
sessions: aggregate.sessions,
|
|
4953
|
+
totals: aggregate.totals,
|
|
4954
|
+
decisions: aggregate.decisions,
|
|
4955
|
+
meetings: aggregate.meetings
|
|
4956
|
+
};
|
|
4957
|
+
}
|
|
4958
|
+
function applyLimit(list, limit) {
|
|
4959
|
+
if (limit === undefined || limit <= 0) {
|
|
4960
|
+
return [...list];
|
|
4961
|
+
}
|
|
4962
|
+
return list.slice(0, limit);
|
|
4963
|
+
}
|
|
4964
|
+
function queryDecisions(memory, query = {}) {
|
|
4965
|
+
const filtered = memory.decisions.filter((decision) => {
|
|
4966
|
+
if (query.agent !== undefined && decision.agent !== query.agent) {
|
|
4967
|
+
return false;
|
|
4968
|
+
}
|
|
4969
|
+
if (query.tag !== undefined && !(decision.tags ?? []).includes(query.tag)) {
|
|
4970
|
+
return false;
|
|
4971
|
+
}
|
|
4972
|
+
return true;
|
|
4973
|
+
});
|
|
4974
|
+
return applyLimit(filtered, query.limit);
|
|
4975
|
+
}
|
|
4976
|
+
function queryMeetings(memory, query = {}) {
|
|
4977
|
+
return applyLimit(memory.meetings, query.limit);
|
|
4978
|
+
}
|
|
4979
|
+
function querySessions(memory, query = {}) {
|
|
4980
|
+
return applyLimit(memory.sessions, query.limit);
|
|
4981
|
+
}
|
|
4982
|
+
function costSummary(memory) {
|
|
4983
|
+
const { totals } = memory;
|
|
4984
|
+
return {
|
|
4985
|
+
sessions: totals.sessions,
|
|
4986
|
+
messageCount: totals.messageCount,
|
|
4987
|
+
realCostUSD: totals.realCostUSD,
|
|
4988
|
+
estimatedCostUSD: totals.estimatedCostUSD,
|
|
4989
|
+
estimatedSavingsUSD: totals.estimatedSavingsUSD,
|
|
4990
|
+
tokensIn: totals.tokensIn,
|
|
4991
|
+
tokensOut: totals.tokensOut
|
|
4992
|
+
};
|
|
4993
|
+
}
|
|
4994
|
+
|
|
4995
|
+
// src/mcp/server.ts
|
|
4996
|
+
var MCP_PROTOCOL_VERSION = "2024-11-05";
|
|
4997
|
+
var DEFAULT_SERVER_INFO = {
|
|
4998
|
+
name: "openteam-memory",
|
|
4999
|
+
version: "1"
|
|
5000
|
+
};
|
|
5001
|
+
var LIMIT_PROP = {
|
|
5002
|
+
limit: {
|
|
5003
|
+
type: "integer",
|
|
5004
|
+
minimum: 1,
|
|
5005
|
+
description: "Máximo de elementos a devolver."
|
|
5006
|
+
}
|
|
5007
|
+
};
|
|
5008
|
+
var MCP_TOOLS = [
|
|
5009
|
+
{
|
|
5010
|
+
name: "list_decisions",
|
|
5011
|
+
description: "Decisiones/aprendizajes del equipo (redactadas), más recientes primero. Filtra por agente y/o etiqueta.",
|
|
5012
|
+
inputSchema: {
|
|
5013
|
+
type: "object",
|
|
5014
|
+
properties: {
|
|
5015
|
+
agent: { type: "string", description: "Filtra por agente." },
|
|
5016
|
+
tag: { type: "string", description: "Filtra por etiqueta." },
|
|
5017
|
+
...LIMIT_PROP
|
|
5018
|
+
}
|
|
5019
|
+
}
|
|
5020
|
+
},
|
|
5021
|
+
{
|
|
5022
|
+
name: "list_meetings",
|
|
5023
|
+
description: "Reuniones (batches multi-agente del orquestador), más recientes primero.",
|
|
5024
|
+
inputSchema: { type: "object", properties: { ...LIMIT_PROP } }
|
|
5025
|
+
},
|
|
5026
|
+
{
|
|
5027
|
+
name: "list_sessions",
|
|
5028
|
+
description: "Resúmenes de sesión (coste/tokens reales, modelos, toolcalls), más activas primero.",
|
|
5029
|
+
inputSchema: { type: "object", properties: { ...LIMIT_PROP } }
|
|
5030
|
+
},
|
|
5031
|
+
{
|
|
5032
|
+
name: "cost_summary",
|
|
5033
|
+
description: "Resumen de coste/tokens del equipo, con ahorro estimado frente al baseline all-frontier.",
|
|
5034
|
+
inputSchema: { type: "object", properties: {} }
|
|
5035
|
+
}
|
|
5036
|
+
];
|
|
5037
|
+
function response(id, result) {
|
|
5038
|
+
return { jsonrpc: "2.0", id, result };
|
|
5039
|
+
}
|
|
5040
|
+
function errorResponse(id, code, message) {
|
|
5041
|
+
return { jsonrpc: "2.0", id, error: { code, message } };
|
|
5042
|
+
}
|
|
5043
|
+
function textContent(data) {
|
|
5044
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
5045
|
+
}
|
|
5046
|
+
function optionalString(value) {
|
|
5047
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
5048
|
+
}
|
|
5049
|
+
function optionalLimit(value) {
|
|
5050
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
5051
|
+
}
|
|
5052
|
+
async function callTool(name, args, deps) {
|
|
5053
|
+
const memory = await deps.readMemory();
|
|
5054
|
+
switch (name) {
|
|
5055
|
+
case "list_decisions": {
|
|
5056
|
+
const query = {};
|
|
5057
|
+
const agent = optionalString(args.agent);
|
|
5058
|
+
const tag = optionalString(args.tag);
|
|
5059
|
+
const limit = optionalLimit(args.limit);
|
|
5060
|
+
if (agent !== undefined) {
|
|
5061
|
+
query.agent = agent;
|
|
5062
|
+
}
|
|
5063
|
+
if (tag !== undefined) {
|
|
5064
|
+
query.tag = tag;
|
|
5065
|
+
}
|
|
5066
|
+
if (limit !== undefined) {
|
|
5067
|
+
query.limit = limit;
|
|
5068
|
+
}
|
|
5069
|
+
return textContent(queryDecisions(memory, query));
|
|
5070
|
+
}
|
|
5071
|
+
case "list_meetings":
|
|
5072
|
+
return textContent(queryMeetings(memory, { limit: optionalLimit(args.limit) ?? 0 }));
|
|
5073
|
+
case "list_sessions":
|
|
5074
|
+
return textContent(querySessions(memory, { limit: optionalLimit(args.limit) ?? 0 }));
|
|
5075
|
+
case "cost_summary":
|
|
5076
|
+
return textContent(costSummary(memory));
|
|
5077
|
+
default:
|
|
5078
|
+
return;
|
|
5079
|
+
}
|
|
5080
|
+
}
|
|
5081
|
+
async function handleMcpMessage(message, deps) {
|
|
5082
|
+
const isNotification = message.id === undefined;
|
|
5083
|
+
const id = message.id ?? null;
|
|
5084
|
+
const serverInfo = deps.serverInfo ?? DEFAULT_SERVER_INFO;
|
|
5085
|
+
switch (message.method) {
|
|
5086
|
+
case "initialize":
|
|
5087
|
+
return response(id, {
|
|
5088
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
5089
|
+
capabilities: { tools: {} },
|
|
5090
|
+
serverInfo
|
|
5091
|
+
});
|
|
5092
|
+
case "tools/list":
|
|
5093
|
+
return response(id, { tools: MCP_TOOLS });
|
|
5094
|
+
case "tools/call": {
|
|
5095
|
+
const params = message.params ?? {};
|
|
5096
|
+
const name = optionalString(params.name);
|
|
5097
|
+
if (name === undefined) {
|
|
5098
|
+
return errorResponse(id, -32602, "tools/call requiere 'name'");
|
|
5099
|
+
}
|
|
5100
|
+
const args = typeof params.arguments === "object" && params.arguments !== null ? params.arguments : {};
|
|
5101
|
+
const result = await callTool(name, args, deps);
|
|
5102
|
+
if (result === undefined) {
|
|
5103
|
+
return errorResponse(id, -32602, `tool desconocida: ${name}`);
|
|
5104
|
+
}
|
|
5105
|
+
return response(id, result);
|
|
5106
|
+
}
|
|
5107
|
+
default:
|
|
5108
|
+
if (isNotification) {
|
|
5109
|
+
return null;
|
|
5110
|
+
}
|
|
5111
|
+
return errorResponse(id, -32601, `método no soportado: ${message.method}`);
|
|
5112
|
+
}
|
|
5113
|
+
}
|
|
5114
|
+
|
|
5115
|
+
// src/mcp/stdio.ts
|
|
5116
|
+
function parseErrorResponse() {
|
|
5117
|
+
return JSON.stringify({
|
|
5118
|
+
jsonrpc: "2.0",
|
|
5119
|
+
id: null,
|
|
5120
|
+
error: { code: -32700, message: "JSON inválido" }
|
|
5121
|
+
});
|
|
5122
|
+
}
|
|
5123
|
+
async function runMcpStdio(deps, io) {
|
|
5124
|
+
for await (const rawLine of io.lines) {
|
|
5125
|
+
const line = rawLine.trim();
|
|
5126
|
+
if (line.length === 0) {
|
|
5127
|
+
continue;
|
|
5128
|
+
}
|
|
5129
|
+
let message;
|
|
5130
|
+
try {
|
|
5131
|
+
message = JSON.parse(line);
|
|
5132
|
+
} catch {
|
|
5133
|
+
io.write(`${parseErrorResponse()}
|
|
5134
|
+
`);
|
|
5135
|
+
continue;
|
|
5136
|
+
}
|
|
5137
|
+
const reply = await handleMcpMessage(message, deps);
|
|
5138
|
+
if (reply !== null) {
|
|
5139
|
+
io.write(`${JSON.stringify(reply)}
|
|
5140
|
+
`);
|
|
5141
|
+
}
|
|
5142
|
+
}
|
|
5143
|
+
}
|
|
5144
|
+
async function* linesFromReadable(readable) {
|
|
5145
|
+
let buffer = "";
|
|
5146
|
+
for await (const chunk of readable) {
|
|
5147
|
+
buffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
5148
|
+
let newlineIndex = buffer.indexOf(`
|
|
5149
|
+
`);
|
|
5150
|
+
while (newlineIndex >= 0) {
|
|
5151
|
+
yield buffer.slice(0, newlineIndex);
|
|
5152
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
5153
|
+
newlineIndex = buffer.indexOf(`
|
|
5154
|
+
`);
|
|
5155
|
+
}
|
|
5156
|
+
}
|
|
5157
|
+
if (buffer.length > 0) {
|
|
5158
|
+
yield buffer;
|
|
5159
|
+
}
|
|
5160
|
+
}
|
|
5161
|
+
|
|
4807
5162
|
// src/web/git.ts
|
|
4808
5163
|
function createGitLastCommit(exec) {
|
|
4809
5164
|
return async () => {
|
|
@@ -4970,6 +5325,18 @@ async function main() {
|
|
|
4970
5325
|
process.exitCode = result2.exitCode;
|
|
4971
5326
|
return;
|
|
4972
5327
|
}
|
|
5328
|
+
if (argv[0] === "mcp") {
|
|
5329
|
+
await runMcpStdio({
|
|
5330
|
+
readMemory: async () => buildTeamMemory(await readSessionEvents(DEFAULT_SESSIONS_DIR, {
|
|
5331
|
+
storage,
|
|
5332
|
+
legacyTelemetryPath: DEFAULT_TELEMETRY_PATH
|
|
5333
|
+
}))
|
|
5334
|
+
}, {
|
|
5335
|
+
lines: linesFromReadable(process.stdin),
|
|
5336
|
+
write: (line) => process.stdout.write(line)
|
|
5337
|
+
});
|
|
5338
|
+
return;
|
|
5339
|
+
}
|
|
4973
5340
|
const result = await runCli(argv, deps);
|
|
4974
5341
|
process.stdout.write(`${result.stdout}
|
|
4975
5342
|
`);
|