@agentistics/mcp 2.100.0 → 2.101.1

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.
@@ -43,6 +43,12 @@ var HARNESS_SORT = {
43
43
  opencode: 6
44
44
  };
45
45
  var HARNESS_ORDER = Object.keys(HARNESS_SORT).sort((a, b) => HARNESS_SORT[a] - HARNESS_SORT[b]);
46
+ function canonicalProjectPath(path) {
47
+ if (!path)
48
+ return path;
49
+ const m = /^(.*?)\/{1,2}(?:\.?claude\/worktrees|\.worktrees)\//.exec(path);
50
+ return m && m[1] ? m[1] : path;
51
+ }
46
52
  var BASE_MODEL_PRICING = {
47
53
  "claude-fable-5": { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 },
48
54
  "claude-mythos-5": { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 },
@@ -216,6 +222,114 @@ function redactSecrets(text) {
216
222
  out = out.replace(BEARER, (whole, prefix, value) => guarded(whole, prefix, value));
217
223
  return out;
218
224
  }
225
+ // ../core/src/projection-surfaces.ts
226
+ var PROJECTION_SURFACES_ENV = "AGENTISTICS_PROJECTIONS_SURFACES";
227
+ var PROJECTION_SURFACE_IDS = ["mcp", "vscode", "tui", "web"];
228
+ var LEGACY_SURFACES_VALUES = ["legacy", "none"];
229
+ function projectionSurfaces(env = process.env) {
230
+ const names = (env[PROJECTION_SURFACES_ENV] ?? "").split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
231
+ if (names.length === 0 || names.includes("all"))
232
+ return [...PROJECTION_SURFACE_IDS];
233
+ if (names.some((n) => LEGACY_SURFACES_VALUES.includes(n)))
234
+ return [];
235
+ return PROJECTION_SURFACE_IDS.filter((id) => names.includes(id));
236
+ }
237
+ function projectionSurfaceOn(surface, env = process.env) {
238
+ return projectionSurfaces(env).includes(surface);
239
+ }
240
+ // ../core/src/projection-client.ts
241
+ class ProjectionUnavailable extends Error {
242
+ status;
243
+ code;
244
+ constructor(status, code) {
245
+ super(`projections unavailable (${status} ${code})`);
246
+ this.status = status;
247
+ this.code = code;
248
+ }
249
+ }
250
+ var MAX_PAGES = 200;
251
+ function metricsSearchParams(params) {
252
+ const p = new URLSearchParams;
253
+ for (const [k, v] of Object.entries(params)) {
254
+ if (typeof v === "string")
255
+ p.append(k, v);
256
+ else
257
+ for (const one of v)
258
+ p.append(k, one);
259
+ }
260
+ return p;
261
+ }
262
+ async function allMetricGroups(q, params) {
263
+ const groups = [];
264
+ let cursor = null;
265
+ let basis;
266
+ for (let i = 0;i < MAX_PAGES; i++) {
267
+ const p = metricsSearchParams({ ...params, limit: "1000" });
268
+ if (cursor)
269
+ p.set("cursor", cursor);
270
+ const a = await q(p);
271
+ if (!a.ok)
272
+ throw new ProjectionUnavailable(a.status, a.error);
273
+ groups.push(...a.body.groups);
274
+ basis = a.body.basis;
275
+ cursor = a.body.page.nextCursor;
276
+ if (!cursor)
277
+ return { groups, basis };
278
+ }
279
+ throw new Error(`metrics query did not finish in ${MAX_PAGES} pages`);
280
+ }
281
+ var tok = (t) => ({
282
+ input: t?.input ?? 0,
283
+ output: t?.output ?? 0,
284
+ cacheRead: t?.cacheRead ?? 0,
285
+ cacheWrite: t?.cacheWrite ?? 0,
286
+ total: t?.total ?? 0
287
+ });
288
+ function harnessScope(harness) {
289
+ return harness && harness !== "all" ? { harness } : {};
290
+ }
291
+ async function projectedRollup(q, dim, harness) {
292
+ const by = (extra) => [...dim ? [dim] : [], ...extra].join(",");
293
+ const base = harnessScope(harness);
294
+ const [all, main, days] = await Promise.all([
295
+ allMetricGroups(q, { ...base, ...dim ? { groupBy: by([]) } : {}, metrics: "sessions,messages,tokens,cost" }),
296
+ allMetricGroups(q, { ...base, ...dim ? { groupBy: by([]) } : {}, subagent: "false", metrics: "tokens,cost" }),
297
+ allMetricGroups(q, { ...base, groupBy: by(["day"]), metrics: "sessions" })
298
+ ]);
299
+ const keyOf = (g) => dim ? g.key[dim] ?? null : null;
300
+ const out = new Map;
301
+ const row = (k) => {
302
+ let r = out.get(k);
303
+ if (!r) {
304
+ r = { key: k, sessions: 0, personTurns: null, main: tok(undefined), mainCost: 0, allTokens: 0, allCost: 0, costPartial: false, lastActive: null };
305
+ out.set(k, r);
306
+ }
307
+ return r;
308
+ };
309
+ for (const g of all.groups) {
310
+ const r = row(keyOf(g));
311
+ r.sessions = g.metrics.sessions?.count ?? 0;
312
+ r.personTurns = g.metrics.messages ? g.metrics.messages.value : null;
313
+ r.allTokens = g.metrics.tokens?.total ?? 0;
314
+ r.allCost = g.metrics.cost?.usd ?? 0;
315
+ r.costPartial ||= g.metrics.cost?.partial ?? false;
316
+ }
317
+ for (const g of main.groups) {
318
+ const r = row(keyOf(g));
319
+ r.main = tok(g.metrics.tokens);
320
+ r.mainCost = g.metrics.cost?.usd ?? 0;
321
+ r.costPartial ||= g.metrics.cost?.partial ?? false;
322
+ }
323
+ for (const g of days.groups) {
324
+ const d = g.key.day;
325
+ if (!d)
326
+ continue;
327
+ const r = row(keyOf(g));
328
+ if (!r.lastActive || d > r.lastActive)
329
+ r.lastActive = d;
330
+ }
331
+ return out;
332
+ }
219
333
  // ../core/src/plan-catalog.ts
220
334
  var CLAUDE_PRICING = "https://claude.com/pricing";
221
335
  var COPILOT_PRICING = "https://github.com/features/copilot/plans";
@@ -490,6 +604,7 @@ var EVENT_TYPES = [
490
604
  "tool.approved",
491
605
  "tool.denied",
492
606
  "tool.progress",
607
+ "tool.executed",
493
608
  "mcp.requested",
494
609
  "mcp.completed",
495
610
  "browser.session.started",
@@ -504,6 +619,7 @@ var EVENT_TYPES = [
504
619
  "browser.tab.closed",
505
620
  "context.compacted",
506
621
  "context.window.observed",
622
+ "context.recalled",
507
623
  "policy.requested",
508
624
  "policy.approved",
509
625
  "policy.denied",
@@ -800,12 +916,12 @@ var AGENT_TOOL_POLICY = {
800
916
  routes: ["POST /api/session-notify"],
801
917
  reason: "a per-session on/off switch for delivery; reversible, and the session state is untouched"
802
918
  },
803
- agentistics_summary: { ...DATA, risk: "R", routes: ["GET /api/data"], reason: "reads computed metrics" },
804
- agentistics_harnesses: { ...DATA, risk: "R", routes: ["GET /api/data"], reason: "reads computed metrics per harness" },
805
- agentistics_projects: { ...DATA, risk: "R", routes: ["GET /api/data"], reason: "reads computed metrics per project" },
919
+ agentistics_summary: { ...DATA, capability: "localTranscripts", host: true, risk: "R", routes: ["GET /api/data", "GET /api/runtime/metrics"], reason: "reads computed metrics" },
920
+ agentistics_harnesses: { ...DATA, capability: "localTranscripts", host: true, risk: "R", routes: ["GET /api/data", "GET /api/runtime/metrics"], reason: "reads computed metrics per harness" },
921
+ agentistics_projects: { ...DATA, capability: "localTranscripts", host: true, risk: "R", routes: ["GET /api/data", "GET /api/runtime/metrics"], reason: "reads computed metrics per project" },
806
922
  agentistics_sessions: { ...DATA, risk: "R", routes: ["GET /api/data"], reason: "reads session metadata (first_prompt is untrusted text, §4.8)" },
807
- agentistics_costs: { ...DATA, risk: "R", routes: ["GET /api/data"], reason: "reads computed costs" },
808
- agentistics_repos: { ...DATA, risk: "R", routes: ["GET /api/data"], reason: "reads computed metrics per repository" },
923
+ agentistics_costs: { ...DATA, capability: "localTranscripts", host: true, risk: "R", routes: ["GET /api/data", "GET /api/runtime/metrics"], reason: "reads computed costs" },
924
+ agentistics_repos: { ...DATA, capability: "localTranscripts", host: true, risk: "R", routes: ["GET /api/data", "GET /api/runtime/metrics"], reason: "reads computed metrics per repository" },
809
925
  agentistics_component_catalog: { ...DATA, risk: "R", routes: [], reason: "returns a static list; calls nothing" },
810
926
  agentistics_get_layouts: { ...PREFS, risk: "R", routes: ["GET /api/preferences"], reason: "reads the saved layouts" },
811
927
  agentistics_create_layout: { ...PREFS, risk: "W", routes: ["GET /api/preferences", "PUT /api/preferences"], reason: "adds a layout; removed by delete_layout" },
@@ -845,7 +961,9 @@ function sessionTokens2(s) {
845
961
  input_tokens: input,
846
962
  output_tokens: output,
847
963
  cache_read_input_tokens: cacheRead,
848
- cache_creation_input_tokens: cacheWrite
964
+ cache_creation_input_tokens: cacheWrite,
965
+ cache_creation_1h_input_tokens: s.cache_creation_1h_input_tokens,
966
+ cache_creation_5m_input_tokens: s.cache_creation_5m_input_tokens
849
967
  }) ?? calcCost({
850
968
  inputTokens: input,
851
969
  outputTokens: output,
@@ -1120,6 +1238,325 @@ function taskCommentRequest(args) {
1120
1238
  };
1121
1239
  }
1122
1240
 
1241
+ // legacy-analytics.ts
1242
+ function legacySummary(data, harness) {
1243
+ const unified = !harness || harness === "all";
1244
+ const sc = data.statsCache ?? {};
1245
+ const totals = statsCacheTotals(sc);
1246
+ const allSessions = filterSessions(data.sessions ?? [], harness);
1247
+ let totalCostUSD = 0;
1248
+ let totalInput = 0, totalOutput = 0, totalCacheRead = 0, totalCacheWrite = 0;
1249
+ const modelTokens = {};
1250
+ const projectSessions = {};
1251
+ const activeDates = new Set;
1252
+ for (const s of allSessions) {
1253
+ const { input, output, cacheRead, cacheWrite, cost } = sessionTokens2(s);
1254
+ totalInput += input;
1255
+ totalOutput += output;
1256
+ totalCacheRead += cacheRead;
1257
+ totalCacheWrite += cacheWrite;
1258
+ totalCostUSD += cost;
1259
+ if (s.model)
1260
+ modelTokens[s.model] = (modelTokens[s.model] ?? 0) + input + output;
1261
+ if (s.project_path) {
1262
+ const root = canonicalProjectPath(s.project_path);
1263
+ projectSessions[root] = (projectSessions[root] ?? 0) + 1;
1264
+ }
1265
+ if (s.start_time)
1266
+ activeDates.add(String(s.start_time).slice(0, 10));
1267
+ }
1268
+ const claudeFallback = unified || harness === "claude";
1269
+ const topModel = Object.entries(modelTokens).sort(([, a], [, b]) => b - a)[0]?.[0] ?? (claudeFallback ? totals.topModel ?? "—" : "—");
1270
+ const topProject = Object.entries(projectSessions).sort(([, a], [, b]) => b - a)[0]?.[0] ?? "—";
1271
+ return {
1272
+ harness: unified ? "all" : harness,
1273
+ totalInputTokens: totalInput || (claudeFallback ? totals.input : 0),
1274
+ totalOutputTokens: totalOutput || (claudeFallback ? totals.output : 0),
1275
+ totalCacheReadTokens: totalCacheRead || (claudeFallback ? totals.cacheRead : 0),
1276
+ totalCacheWriteTokens: totalCacheWrite || (claudeFallback ? totals.cacheWrite : 0),
1277
+ estimatedCostUSD: Math.round((totalCostUSD || (claudeFallback ? totals.cost : 0)) * 100) / 100,
1278
+ totalSessions: allSessions.length,
1279
+ topModel,
1280
+ topProject,
1281
+ activeDays: activeDates.size || (claudeFallback ? sc.activeDays ?? 0 : 0),
1282
+ currentStreak: claudeFallback ? sc.currentStreak ?? 0 : null
1283
+ };
1284
+ }
1285
+ function legacyHarnesses(data) {
1286
+ const present = data.harnesses ?? ["claude"];
1287
+ const sessions = data.sessions ?? [];
1288
+ const rows = present.map((h) => {
1289
+ const hs = sessions.filter((s) => sessionHarness(s) === h);
1290
+ let input = 0, output = 0, cacheRead = 0, cacheWrite = 0, cost = 0, messages = 0, lastActive = "";
1291
+ for (const s of hs) {
1292
+ const t = sessionTokens2(s);
1293
+ input += t.input;
1294
+ output += t.output;
1295
+ cacheRead += t.cacheRead;
1296
+ cacheWrite += t.cacheWrite;
1297
+ cost += t.cost;
1298
+ messages += sessionMessages(s);
1299
+ if (s.start_time && String(s.start_time) > lastActive)
1300
+ lastActive = String(s.start_time);
1301
+ }
1302
+ return {
1303
+ harness: h,
1304
+ sessions: hs.length,
1305
+ messages,
1306
+ inputTokens: input,
1307
+ outputTokens: output,
1308
+ cacheReadTokens: cacheRead,
1309
+ cacheWriteTokens: cacheWrite,
1310
+ totalTokens: input + output + cacheRead + cacheWrite,
1311
+ estimatedCostUSD: Math.round(cost * 100) / 100,
1312
+ lastActive: lastActive || null
1313
+ };
1314
+ }).sort((a, b) => b.totalTokens - a.totalTokens);
1315
+ return rows;
1316
+ }
1317
+ function legacyProjects(data, harness) {
1318
+ const allSessions = filterSessions(data.sessions ?? [], harness);
1319
+ const byPath = {};
1320
+ for (const s of allSessions) {
1321
+ const key = canonicalProjectPath(s.project_path);
1322
+ if (!key)
1323
+ continue;
1324
+ if (!byPath[key])
1325
+ byPath[key] = { sessions: 0, inputTokens: 0, outputTokens: 0, cacheRead: 0, cacheWrite: 0, costUSD: 0, messages: 0, lastActive: "", languages: [] };
1326
+ const agg = byPath[key];
1327
+ const { input, output, cacheRead, cacheWrite, cost } = sessionTokens2(s);
1328
+ agg.sessions += 1;
1329
+ agg.inputTokens += input;
1330
+ agg.outputTokens += output;
1331
+ agg.cacheRead += cacheRead;
1332
+ agg.cacheWrite += cacheWrite;
1333
+ agg.costUSD += cost;
1334
+ agg.messages += sessionMessages(s);
1335
+ if (!agg.lastActive || (s.start_time ?? "") > agg.lastActive)
1336
+ agg.lastActive = s.start_time ?? "";
1337
+ for (const lang of s.languages ?? [])
1338
+ if (!agg.languages.includes(lang))
1339
+ agg.languages.push(lang);
1340
+ }
1341
+ const roots = new Map;
1342
+ for (const p of data.projects ?? []) {
1343
+ const path = canonicalProjectPath(p.path ?? "");
1344
+ const r = roots.get(path) ?? { name: p.name, path, sessions: [] };
1345
+ if (p.path === path)
1346
+ r.name = p.name;
1347
+ r.sessions.push(...p.sessions ?? []);
1348
+ roots.set(path, r);
1349
+ }
1350
+ const projects = [...roots.values()];
1351
+ const summary = projects.filter((p) => !harness || harness === "all" || byPath[p.path]).map((p) => {
1352
+ const agg = byPath[p.path] ?? { sessions: 0, inputTokens: 0, outputTokens: 0, cacheRead: 0, cacheWrite: 0, costUSD: 0, messages: 0, lastActive: "", languages: [] };
1353
+ return {
1354
+ name: p.name,
1355
+ path: p.path,
1356
+ sessions: !harness || harness === "all" ? (p.sessions ?? []).length : agg.sessions,
1357
+ messages: agg.messages,
1358
+ inputTokens: agg.inputTokens,
1359
+ outputTokens: agg.outputTokens,
1360
+ totalTokens: agg.inputTokens + agg.outputTokens,
1361
+ estimatedCostUSD: Math.round(agg.costUSD * 1e4) / 1e4,
1362
+ lastActive: agg.lastActive || null,
1363
+ languages: agg.languages
1364
+ };
1365
+ }).sort((a, b) => b.totalTokens - a.totalTokens);
1366
+ return summary;
1367
+ }
1368
+ function legacyCosts(data, harness) {
1369
+ if (!harness || harness === "all" || harness === "claude") {
1370
+ const usage = data.statsCache?.modelUsage ?? {};
1371
+ const breakdown = Object.entries(usage).map(([model, u]) => ({
1372
+ model,
1373
+ inputTokens: u.inputTokens ?? 0,
1374
+ outputTokens: u.outputTokens ?? 0,
1375
+ cacheReadTokens: u.cacheReadTokens ?? 0,
1376
+ cacheWriteTokens: u.cacheWriteTokens ?? 0,
1377
+ totalTokens: u.totalTokens ?? 0,
1378
+ estimatedCostUSD: u.costUSD ?? 0
1379
+ })).sort((a, b) => b.totalTokens - a.totalTokens);
1380
+ return breakdown;
1381
+ }
1382
+ const byModel = {};
1383
+ for (const s of filterSessions(data.sessions ?? [], harness)) {
1384
+ const model = s.model || "unknown";
1385
+ if (!byModel[model])
1386
+ byModel[model] = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, estimatedCostUSD: 0 };
1387
+ const agg = byModel[model];
1388
+ const { input, output, cacheRead, cacheWrite, cost } = sessionTokens2(s);
1389
+ agg.inputTokens += input;
1390
+ agg.outputTokens += output;
1391
+ agg.cacheReadTokens += cacheRead;
1392
+ agg.cacheWriteTokens += cacheWrite;
1393
+ agg.estimatedCostUSD += cost;
1394
+ }
1395
+ const breakdown = Object.entries(byModel).map(([model, u]) => ({
1396
+ model,
1397
+ inputTokens: u.inputTokens,
1398
+ outputTokens: u.outputTokens,
1399
+ cacheReadTokens: u.cacheReadTokens,
1400
+ cacheWriteTokens: u.cacheWriteTokens,
1401
+ totalTokens: u.inputTokens + u.outputTokens + u.cacheReadTokens + u.cacheWriteTokens,
1402
+ estimatedCostUSD: Math.round(u.estimatedCostUSD * 1e4) / 1e4
1403
+ })).sort((a, b) => b.totalTokens - a.totalTokens);
1404
+ return breakdown;
1405
+ }
1406
+ function legacyRepos(data, harness) {
1407
+ const allSessions = filterSessions(data.sessions ?? [], harness);
1408
+ const byRemote = {};
1409
+ for (const s of allSessions) {
1410
+ const key = s.git_remote || "";
1411
+ if (!byRemote[key])
1412
+ byRemote[key] = { sessions: 0, messages: 0, inputTokens: 0, outputTokens: 0, cacheRead: 0, cacheWrite: 0, costUSD: 0, lastActive: "" };
1413
+ const agg = byRemote[key];
1414
+ const { input, output, cacheRead, cacheWrite, cost } = sessionTokens2(s);
1415
+ agg.sessions += 1;
1416
+ agg.messages += sessionMessages(s);
1417
+ agg.inputTokens += input;
1418
+ agg.outputTokens += output;
1419
+ agg.cacheRead += cacheRead;
1420
+ agg.cacheWrite += cacheWrite;
1421
+ agg.costUSD += cost;
1422
+ if (s.start_time && String(s.start_time) > agg.lastActive)
1423
+ agg.lastActive = String(s.start_time);
1424
+ }
1425
+ const rows = Object.entries(byRemote).map(([remote, agg]) => ({
1426
+ repo: remote || "unlinked",
1427
+ remote: remote || null,
1428
+ sessions: agg.sessions,
1429
+ messages: agg.messages,
1430
+ inputTokens: agg.inputTokens,
1431
+ outputTokens: agg.outputTokens,
1432
+ totalTokens: agg.inputTokens + agg.outputTokens + agg.cacheRead + agg.cacheWrite,
1433
+ estimatedCostUSD: Math.round(agg.costUSD * 1e4) / 1e4,
1434
+ lastActive: agg.lastActive || null
1435
+ })).sort((a, b) => b.totalTokens - a.totalTokens);
1436
+ return rows;
1437
+ }
1438
+
1439
+ // projected-analytics.ts
1440
+ var round = (n, places) => Math.round(n * 10 ** places) / 10 ** places;
1441
+ function spend(r, places) {
1442
+ return {
1443
+ inputTokens: r.main.input,
1444
+ outputTokens: r.main.output,
1445
+ cacheReadTokens: r.main.cacheRead,
1446
+ cacheWriteTokens: r.main.cacheWrite,
1447
+ totalTokens: r.main.input + r.main.output + r.main.cacheRead + r.main.cacheWrite,
1448
+ estimatedCostUSD: round(r.mainCost, places),
1449
+ costPartial: r.costPartial,
1450
+ subagentTokens: Math.max(0, r.allTokens - r.main.total),
1451
+ subagentCostUSD: round(Math.max(0, r.allCost - r.mainCost), places)
1452
+ };
1453
+ }
1454
+ async function projectedHarnesses(q) {
1455
+ const rows = [...(await projectedRollup(q, "harness")).values()].map((r) => ({
1456
+ harness: r.key,
1457
+ sessions: r.sessions,
1458
+ personTurns: r.personTurns,
1459
+ messages: r.personTurns,
1460
+ ...spend(r, 2),
1461
+ lastActive: r.lastActive,
1462
+ source: "projections"
1463
+ }));
1464
+ return rows.sort((a, b) => b.totalTokens - a.totalTokens);
1465
+ }
1466
+ async function projectedProjects(q, harness) {
1467
+ const rows = [...(await projectedRollup(q, "project", harness)).values()].filter((r) => r.key).map((r) => {
1468
+ const s = spend(r, 4);
1469
+ return {
1470
+ name: r.key.split(/[\\/]/).filter(Boolean).pop() ?? r.key,
1471
+ path: r.key,
1472
+ sessions: r.sessions,
1473
+ personTurns: r.personTurns,
1474
+ messages: r.personTurns,
1475
+ inputTokens: s.inputTokens,
1476
+ outputTokens: s.outputTokens,
1477
+ totalTokens: s.inputTokens + s.outputTokens,
1478
+ estimatedCostUSD: s.estimatedCostUSD,
1479
+ costPartial: s.costPartial,
1480
+ subagentTokens: s.subagentTokens,
1481
+ subagentCostUSD: s.subagentCostUSD,
1482
+ lastActive: r.lastActive,
1483
+ languages: null,
1484
+ source: "projections"
1485
+ };
1486
+ });
1487
+ return rows.sort((a, b) => b.totalTokens - a.totalTokens);
1488
+ }
1489
+ async function projectedRepos(q, harness) {
1490
+ const rows = [...(await projectedRollup(q, "repo", harness)).values()].map((r) => ({
1491
+ repo: r.key || "unlinked",
1492
+ remote: r.key || null,
1493
+ sessions: r.sessions,
1494
+ personTurns: r.personTurns,
1495
+ messages: r.personTurns,
1496
+ ...spend(r, 4),
1497
+ lastActive: r.lastActive,
1498
+ source: "projections"
1499
+ }));
1500
+ return rows.sort((a, b) => b.totalTokens - a.totalTokens);
1501
+ }
1502
+ async function projectedCosts(q, harness) {
1503
+ const rows = [...(await projectedRollup(q, "model", harness)).values()].map((r) => {
1504
+ const { costPartial, subagentTokens, subagentCostUSD, ...s } = spend(r, 4);
1505
+ return { model: r.key ?? "unknown", ...s, costPartial, subagentTokens, subagentCostUSD, source: "projections" };
1506
+ });
1507
+ return rows.sort((a, b) => b.totalTokens - a.totalTokens);
1508
+ }
1509
+ function streakOf(days, today) {
1510
+ const set = new Set(days);
1511
+ const prev = (d) => new Date(Date.parse(`${d}T00:00:00Z`) - 86400000).toISOString().slice(0, 10);
1512
+ let d = set.has(today) ? today : prev(today);
1513
+ let n = 0;
1514
+ while (set.has(d)) {
1515
+ n++;
1516
+ d = prev(d);
1517
+ }
1518
+ return n;
1519
+ }
1520
+ async function projectedSummary(q, harness, now = () => new Date) {
1521
+ const unified = !harness || harness === "all";
1522
+ const base = harnessScope(harness);
1523
+ const [totals, models, projects, days] = await Promise.all([
1524
+ projectedRollup(q, null, harness),
1525
+ allMetricGroups(q, { ...base, groupBy: "model", subagent: "false", metrics: "tokens" }),
1526
+ allMetricGroups(q, { ...base, groupBy: "project", metrics: "sessions" }),
1527
+ allMetricGroups(q, { ...base, groupBy: "day", metrics: "sessions" })
1528
+ ]);
1529
+ const t = totals.get(null) ?? null;
1530
+ const top = (gs, dim, n) => gs.filter((g) => g.key[dim]).sort((a, b) => n(b) - n(a))[0]?.key[dim] ?? "—";
1531
+ const activeDays = days.groups.map((g) => g.key.day).filter((d) => !!d);
1532
+ const s = t ? spend(t, 2) : null;
1533
+ return {
1534
+ harness: unified ? "all" : harness,
1535
+ totalInputTokens: s?.inputTokens ?? 0,
1536
+ totalOutputTokens: s?.outputTokens ?? 0,
1537
+ totalCacheReadTokens: s?.cacheReadTokens ?? 0,
1538
+ totalCacheWriteTokens: s?.cacheWriteTokens ?? 0,
1539
+ estimatedCostUSD: s?.estimatedCostUSD ?? 0,
1540
+ costPartial: s?.costPartial ?? false,
1541
+ subagentTokens: s?.subagentTokens ?? 0,
1542
+ subagentCostUSD: s?.subagentCostUSD ?? 0,
1543
+ totalSessions: t?.sessions ?? 0,
1544
+ topModel: top(models.groups, "model", (g) => (g.metrics.tokens?.input ?? 0) + (g.metrics.tokens?.output ?? 0)),
1545
+ topProject: top(projects.groups, "project", (g) => g.metrics.sessions?.count ?? 0),
1546
+ activeDays: activeDays.length,
1547
+ currentStreak: streakOf(activeDays, now().toISOString().slice(0, 10)),
1548
+ freshness: days.basis?.freshness ?? null,
1549
+ source: "projections"
1550
+ };
1551
+ }
1552
+ var PROJECTED_TOOLS = {
1553
+ agentistics_summary: projectedSummary,
1554
+ agentistics_harnesses: (q) => projectedHarnesses(q),
1555
+ agentistics_projects: projectedProjects,
1556
+ agentistics_costs: projectedCosts,
1557
+ agentistics_repos: projectedRepos
1558
+ };
1559
+
1123
1560
  // agentistics-mcp.ts
1124
1561
  var API = process.env.AGENTISTICS_API ?? "http://localhost:47291";
1125
1562
  var HARNESS_PARAM = harnessParam();
@@ -1159,6 +1596,30 @@ async function apiGet(path) {
1159
1596
  throw new Error(`agentistics API ${path} → HTTP ${res.status}`);
1160
1597
  return res.json();
1161
1598
  }
1599
+ var LEGACY_TOOLS = {
1600
+ agentistics_summary: legacySummary,
1601
+ agentistics_harnesses: (data) => legacyHarnesses(data),
1602
+ agentistics_projects: legacyProjects,
1603
+ agentistics_costs: legacyCosts,
1604
+ agentistics_repos: legacyRepos
1605
+ };
1606
+ async function analytics(tool, harness, from) {
1607
+ if (projectionSurfaceOn("mcp")) {
1608
+ const query = async (params) => {
1609
+ const res = await from.metrics(params);
1610
+ const body = await res.json().catch(() => ({}));
1611
+ return res.ok ? { ok: true, body } : { ok: false, status: res.status, error: String(body?.error ?? res.status) };
1612
+ };
1613
+ try {
1614
+ return await PROJECTED_TOOLS[tool](query, harness);
1615
+ } catch (err) {
1616
+ if (!(err instanceof ProjectionUnavailable))
1617
+ throw err;
1618
+ }
1619
+ }
1620
+ return LEGACY_TOOLS[tool](await from.legacy(), harness);
1621
+ }
1622
+ var text = (value) => ({ content: [{ type: "text", text: JSON.stringify(value, null, 2) }] });
1162
1623
  async function getPrefs() {
1163
1624
  return apiGet("/api/preferences");
1164
1625
  }
@@ -1538,12 +1999,12 @@ var TOOLS = [
1538
1999
  },
1539
2000
  {
1540
2001
  name: "agentistics_harnesses",
1541
- description: "Compare the tracked AI coding harnesses side by side. Lists every harness present in the data with its sessions, messages, token usage, estimated cost, and last-active date. Use this to answer 'which harness do I use most / costs most' questions.",
2002
+ description: "Compare the tracked AI coding harnesses side by side. Lists every harness present in the data with its sessions, messages, token usage, estimated cost, and last-active date. Use this to answer 'which harness do I use most / costs most' questions. Rows tagged source \"projections\" (read from the journal) carry personTurns (the person's turns); there `messages` is a deprecated alias of personTurns, and subagent spend is reported apart (subagentTokens, subagentCostUSD).",
1542
2003
  inputSchema: { type: "object", properties: {}, required: [] }
1543
2004
  },
1544
2005
  {
1545
2006
  name: "agentistics_projects",
1546
- description: "List projects with session counts, message counts, token usage, estimated cost, and last active date. Optionally scope to a single harness.",
2007
+ description: 'List projects with session counts, message counts, token usage, estimated cost, and last active date. Optionally scope to a single harness. Rows tagged source "projections" (read from the journal) carry personTurns (the person\'s turns); there `messages` is a deprecated alias of personTurns, and subagent spend is reported apart (subagentTokens, subagentCostUSD).',
1547
2008
  inputSchema: { type: "object", properties: { harness: HARNESS_PARAM }, required: [] }
1548
2009
  },
1549
2010
  {
@@ -1720,7 +2181,7 @@ var TOOLS = [
1720
2181
  },
1721
2182
  {
1722
2183
  name: "agentistics_repos",
1723
- description: "List repositories, grouped by normalized git remote (independent of local path or which machine produced the session), with session/message/token/cost totals and last-active date. Sessions with no linked repository are grouped under 'unlinked'. Optionally scope to a single harness.",
2184
+ description: "List repositories, grouped by normalized git remote (independent of local path or which machine produced the session), with session/message/token/cost totals and last-active date. Sessions with no linked repository are grouped under 'unlinked'. Optionally scope to a single harness. Rows tagged source \"projections\" (read from the journal) carry personTurns (the person's turns); there `messages` is a deprecated alias of personTurns, and subagent spend is reported apart (subagentTokens, subagentCostUSD).",
1724
2185
  inputSchema: { type: "object", properties: { harness: HARNESS_PARAM }, required: [] }
1725
2186
  },
1726
2187
  {
@@ -1961,128 +2422,23 @@ async function callTool(req) {
1961
2422
  }
1962
2423
  case "agentistics_summary": {
1963
2424
  const harness = args?.harness;
1964
- const unified = !harness || harness === "all";
1965
- const data = await apiGet("/api/data");
1966
- const sc = data.statsCache ?? {};
1967
- const totals = statsCacheTotals(sc);
1968
- const allSessions = filterSessions(data.sessions ?? [], harness);
1969
- let totalCostUSD = 0;
1970
- let totalInput = 0, totalOutput = 0, totalCacheRead = 0, totalCacheWrite = 0;
1971
- const modelTokens = {};
1972
- const projectSessions = {};
1973
- const activeDates = new Set;
1974
- for (const s of allSessions) {
1975
- const { input, output, cacheRead, cacheWrite, cost } = sessionTokens2(s);
1976
- totalInput += input;
1977
- totalOutput += output;
1978
- totalCacheRead += cacheRead;
1979
- totalCacheWrite += cacheWrite;
1980
- totalCostUSD += cost;
1981
- if (s.model)
1982
- modelTokens[s.model] = (modelTokens[s.model] ?? 0) + input + output;
1983
- if (s.project_path)
1984
- projectSessions[s.project_path] = (projectSessions[s.project_path] ?? 0) + 1;
1985
- if (s.start_time)
1986
- activeDates.add(String(s.start_time).slice(0, 10));
1987
- }
1988
- const claudeFallback = unified || harness === "claude";
1989
- const topModel = Object.entries(modelTokens).sort(([, a], [, b]) => b - a)[0]?.[0] ?? (claudeFallback ? totals.topModel ?? "—" : "—");
1990
- const topProject = Object.entries(projectSessions).sort(([, a], [, b]) => b - a)[0]?.[0] ?? "—";
1991
- return {
1992
- content: [{
1993
- type: "text",
1994
- text: JSON.stringify({
1995
- harness: unified ? "all" : harness,
1996
- totalInputTokens: totalInput || (claudeFallback ? totals.input : 0),
1997
- totalOutputTokens: totalOutput || (claudeFallback ? totals.output : 0),
1998
- totalCacheReadTokens: totalCacheRead || (claudeFallback ? totals.cacheRead : 0),
1999
- totalCacheWriteTokens: totalCacheWrite || (claudeFallback ? totals.cacheWrite : 0),
2000
- estimatedCostUSD: Math.round((totalCostUSD || (claudeFallback ? totals.cost : 0)) * 100) / 100,
2001
- totalSessions: allSessions.length,
2002
- topModel,
2003
- topProject,
2004
- activeDays: activeDates.size || (claudeFallback ? sc.activeDays ?? 0 : 0),
2005
- currentStreak: claudeFallback ? sc.currentStreak ?? 0 : null
2006
- }, null, 2)
2007
- }]
2008
- };
2425
+ return text(await analytics("agentistics_summary", harness, {
2426
+ legacy: () => apiGet("/api/data"),
2427
+ metrics: (q) => fetch(`${API}/api/runtime/metrics?${q}`)
2428
+ }));
2009
2429
  }
2010
2430
  case "agentistics_harnesses": {
2011
- const data = await apiGet("/api/data");
2012
- const present = data.harnesses ?? ["claude"];
2013
- const sessions = data.sessions ?? [];
2014
- const rows = present.map((h) => {
2015
- const hs = sessions.filter((s) => sessionHarness(s) === h);
2016
- let input = 0, output = 0, cacheRead = 0, cacheWrite = 0, cost = 0, messages = 0, lastActive = "";
2017
- for (const s of hs) {
2018
- const t = sessionTokens2(s);
2019
- input += t.input;
2020
- output += t.output;
2021
- cacheRead += t.cacheRead;
2022
- cacheWrite += t.cacheWrite;
2023
- cost += t.cost;
2024
- messages += sessionMessages(s);
2025
- if (s.start_time && String(s.start_time) > lastActive)
2026
- lastActive = String(s.start_time);
2027
- }
2028
- return {
2029
- harness: h,
2030
- sessions: hs.length,
2031
- messages,
2032
- inputTokens: input,
2033
- outputTokens: output,
2034
- cacheReadTokens: cacheRead,
2035
- cacheWriteTokens: cacheWrite,
2036
- totalTokens: input + output + cacheRead + cacheWrite,
2037
- estimatedCostUSD: Math.round(cost * 100) / 100,
2038
- lastActive: lastActive || null
2039
- };
2040
- }).sort((a, b) => b.totalTokens - a.totalTokens);
2041
- return { content: [{ type: "text", text: JSON.stringify(rows, null, 2) }] };
2431
+ return text(await analytics("agentistics_harnesses", undefined, {
2432
+ legacy: () => apiGet("/api/data"),
2433
+ metrics: (q) => fetch(`${API}/api/runtime/metrics?${q}`)
2434
+ }));
2042
2435
  }
2043
2436
  case "agentistics_projects": {
2044
2437
  const harness = args?.harness;
2045
- const data = await apiGet("/api/data");
2046
- const allSessions = filterSessions(data.sessions ?? [], harness);
2047
- const byPath = {};
2048
- for (const s of allSessions) {
2049
- const key = s.project_path;
2050
- if (!key)
2051
- continue;
2052
- if (!byPath[key])
2053
- byPath[key] = { sessions: 0, inputTokens: 0, outputTokens: 0, cacheRead: 0, cacheWrite: 0, costUSD: 0, messages: 0, lastActive: "", languages: [] };
2054
- const agg = byPath[key];
2055
- const { input, output, cacheRead, cacheWrite, cost } = sessionTokens2(s);
2056
- agg.sessions += 1;
2057
- agg.inputTokens += input;
2058
- agg.outputTokens += output;
2059
- agg.cacheRead += cacheRead;
2060
- agg.cacheWrite += cacheWrite;
2061
- agg.costUSD += cost;
2062
- agg.messages += sessionMessages(s);
2063
- if (!agg.lastActive || (s.start_time ?? "") > agg.lastActive)
2064
- agg.lastActive = s.start_time ?? "";
2065
- for (const lang of s.languages ?? [])
2066
- if (!agg.languages.includes(lang))
2067
- agg.languages.push(lang);
2068
- }
2069
- const projects = data.projects ?? [];
2070
- const summary = projects.filter((p) => !harness || harness === "all" || byPath[p.path]).map((p) => {
2071
- const agg = byPath[p.path] ?? { sessions: 0, inputTokens: 0, outputTokens: 0, cacheRead: 0, cacheWrite: 0, costUSD: 0, messages: 0, lastActive: "", languages: [] };
2072
- return {
2073
- name: p.name,
2074
- path: p.path,
2075
- sessions: !harness || harness === "all" ? (p.sessions ?? []).length : agg.sessions,
2076
- messages: agg.messages,
2077
- inputTokens: agg.inputTokens,
2078
- outputTokens: agg.outputTokens,
2079
- totalTokens: agg.inputTokens + agg.outputTokens,
2080
- estimatedCostUSD: Math.round(agg.costUSD * 1e4) / 1e4,
2081
- lastActive: agg.lastActive || null,
2082
- languages: agg.languages
2083
- };
2084
- }).sort((a, b) => b.totalTokens - a.totalTokens);
2085
- return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
2438
+ return text(await analytics("agentistics_projects", harness, {
2439
+ legacy: () => apiGet("/api/data"),
2440
+ metrics: (q) => fetch(`${API}/api/runtime/metrics?${q}`)
2441
+ }));
2086
2442
  }
2087
2443
  case "agentistics_sessions": {
2088
2444
  const limit = Math.min(args?.limit ?? 20, 50);
@@ -2110,43 +2466,10 @@ async function callTool(req) {
2110
2466
  }
2111
2467
  case "agentistics_costs": {
2112
2468
  const harness = args?.harness;
2113
- const data = await apiGet("/api/data");
2114
- if (!harness || harness === "all" || harness === "claude") {
2115
- const usage = data.statsCache?.modelUsage ?? {};
2116
- const breakdown = Object.entries(usage).map(([model, u]) => ({
2117
- model,
2118
- inputTokens: u.inputTokens ?? 0,
2119
- outputTokens: u.outputTokens ?? 0,
2120
- cacheReadTokens: u.cacheReadTokens ?? 0,
2121
- cacheWriteTokens: u.cacheWriteTokens ?? 0,
2122
- totalTokens: u.totalTokens ?? 0,
2123
- estimatedCostUSD: u.costUSD ?? 0
2124
- })).sort((a, b) => b.totalTokens - a.totalTokens);
2125
- return { content: [{ type: "text", text: JSON.stringify(breakdown, null, 2) }] };
2126
- }
2127
- const byModel = {};
2128
- for (const s of filterSessions(data.sessions ?? [], harness)) {
2129
- const model = s.model || "unknown";
2130
- if (!byModel[model])
2131
- byModel[model] = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, estimatedCostUSD: 0 };
2132
- const agg = byModel[model];
2133
- const { input, output, cacheRead, cacheWrite, cost } = sessionTokens2(s);
2134
- agg.inputTokens += input;
2135
- agg.outputTokens += output;
2136
- agg.cacheReadTokens += cacheRead;
2137
- agg.cacheWriteTokens += cacheWrite;
2138
- agg.estimatedCostUSD += cost;
2139
- }
2140
- const breakdown = Object.entries(byModel).map(([model, u]) => ({
2141
- model,
2142
- inputTokens: u.inputTokens,
2143
- outputTokens: u.outputTokens,
2144
- cacheReadTokens: u.cacheReadTokens,
2145
- cacheWriteTokens: u.cacheWriteTokens,
2146
- totalTokens: u.inputTokens + u.outputTokens + u.cacheReadTokens + u.cacheWriteTokens,
2147
- estimatedCostUSD: Math.round(u.estimatedCostUSD * 1e4) / 1e4
2148
- })).sort((a, b) => b.totalTokens - a.totalTokens);
2149
- return { content: [{ type: "text", text: JSON.stringify(breakdown, null, 2) }] };
2469
+ return text(await analytics("agentistics_costs", harness, {
2470
+ legacy: () => apiGet("/api/data"),
2471
+ metrics: (q) => fetch(`${API}/api/runtime/metrics?${q}`)
2472
+ }));
2150
2473
  }
2151
2474
  case "agentistics_component_catalog": {
2152
2475
  const category = args?.category;
@@ -2382,37 +2705,10 @@ async function callTool(req) {
2382
2705
  }
2383
2706
  case "agentistics_repos": {
2384
2707
  const harness = args?.harness;
2385
- const data = await apiGet("/api/data");
2386
- const allSessions = filterSessions(data.sessions ?? [], harness);
2387
- const byRemote = {};
2388
- for (const s of allSessions) {
2389
- const key = s.git_remote || "";
2390
- if (!byRemote[key])
2391
- byRemote[key] = { sessions: 0, messages: 0, inputTokens: 0, outputTokens: 0, cacheRead: 0, cacheWrite: 0, costUSD: 0, lastActive: "" };
2392
- const agg = byRemote[key];
2393
- const { input, output, cacheRead, cacheWrite, cost } = sessionTokens2(s);
2394
- agg.sessions += 1;
2395
- agg.messages += sessionMessages(s);
2396
- agg.inputTokens += input;
2397
- agg.outputTokens += output;
2398
- agg.cacheRead += cacheRead;
2399
- agg.cacheWrite += cacheWrite;
2400
- agg.costUSD += cost;
2401
- if (s.start_time && String(s.start_time) > agg.lastActive)
2402
- agg.lastActive = String(s.start_time);
2403
- }
2404
- const rows = Object.entries(byRemote).map(([remote, agg]) => ({
2405
- repo: remote || "unlinked",
2406
- remote: remote || null,
2407
- sessions: agg.sessions,
2408
- messages: agg.messages,
2409
- inputTokens: agg.inputTokens,
2410
- outputTokens: agg.outputTokens,
2411
- totalTokens: agg.inputTokens + agg.outputTokens + agg.cacheRead + agg.cacheWrite,
2412
- estimatedCostUSD: Math.round(agg.costUSD * 1e4) / 1e4,
2413
- lastActive: agg.lastActive || null
2414
- })).sort((a, b) => b.totalTokens - a.totalTokens);
2415
- return { content: [{ type: "text", text: JSON.stringify(rows, null, 2) }] };
2708
+ return text(await analytics("agentistics_repos", harness, {
2709
+ legacy: () => apiGet("/api/data"),
2710
+ metrics: (q) => fetch(`${API}/api/runtime/metrics?${q}`)
2711
+ }));
2416
2712
  }
2417
2713
  case "agentistics_team_status": {
2418
2714
  const prefs = await getPrefs();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentistics/mcp",
3
- "version": "2.100.0",
3
+ "version": "2.101.1",
4
4
  "description": "Agentistics MCP server — Claude Code analytics for Claude Desktop and Claude Code",
5
5
  "type": "module",
6
6
  "main": "./dist/agentistics-mcp.js",