@a-t-h-i/bot-lobby 0.6.45 → 0.6.46
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/package.json +1 -1
- package/src/execution/task-cancellation.ts +27 -0
- package/src/lobby/host.ts +4 -0
- package/src/lobby/prompts.ts +1 -2
- package/src/lobby/service.ts +77 -1
- package/src/state/persistence.ts +26 -1
- package/src/webui/api/index.ts +13 -6
- package/src/webui/api/tasks.ts +39 -2
- package/src/webui/palette-core.ts +233 -2
- package/src/webui/protocol.ts +6 -74
- package/src/workflow/workflow.ts +15 -3
- package/webui/dist/assets/index-BJ1fEle9.js +110 -0
- package/webui/dist/assets/index-BdtiIB9L.css +2 -0
- package/webui/dist/build.json +33 -61
- package/webui/dist/index.html +2 -2
- package/src/lobby/models/metrics.ts +0 -14
- package/src/webui/api/metrics.ts +0 -96
- package/webui/dist/assets/index-ClwTDiyo.css +0 -2
- package/webui/dist/assets/index-Ct47Kp7Q.js +0 -110
package/webui/dist/build.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
|
-
"hash": "
|
|
3
|
-
"builtAt": "2026-10-
|
|
2
|
+
"hash": "44ae0a7d08d0f0c860d5299fdddd1521df143ae76b865c975b70d62f335842a2",
|
|
3
|
+
"builtAt": "2026-10-13T23:01:05.500Z",
|
|
4
4
|
"files": [
|
|
5
5
|
{
|
|
6
6
|
"path": "src/lobby/prompts.ts",
|
|
7
|
-
"sha256": "
|
|
7
|
+
"sha256": "4a705f59f54682a71b9ffa53570a3d9be1fe139f5da8372b0598c8707d63aee7"
|
|
8
8
|
},
|
|
9
9
|
{
|
|
10
10
|
"path": "src/webui/protocol.ts",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "d5a2b938c1ff5cb4f50c13268bb437766f65c202bd7607b9fbfb7a058e4e36d4"
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
"path": "webui/components.json",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
26
|
"path": "webui/src/app/AltH.tsx",
|
|
27
|
-
"sha256": "
|
|
27
|
+
"sha256": "a760972ea3deaaa3fefcbc4352d63c010b09fb1ea7d3aa29b7eb67c2a6420f4d"
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
"path": "webui/src/app/AppTools.tsx",
|
|
@@ -48,11 +48,11 @@
|
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
"path": "webui/src/app/PhoneTabs.tsx",
|
|
51
|
-
"sha256": "
|
|
51
|
+
"sha256": "eab9d2bdd15a702b529fbf292d9335a2a7243ae10a2148e450e0fdbb918fcf84"
|
|
52
52
|
},
|
|
53
53
|
{
|
|
54
54
|
"path": "webui/src/app/ProjectFolderDialog.tsx",
|
|
55
|
-
"sha256": "
|
|
55
|
+
"sha256": "a2bd027322c72a947c4c85ee583f3b401c9415a46a55a38fc93efc2de7030b34"
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
"path": "webui/src/app/ProjectSwitcher.tsx",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
},
|
|
61
61
|
{
|
|
62
62
|
"path": "webui/src/app/Shell.tsx",
|
|
63
|
-
"sha256": "
|
|
63
|
+
"sha256": "fe5739ce1f56b2002274ed948f85cd99a0f33c097a82ae62ab31baf5f9e75fa7"
|
|
64
64
|
},
|
|
65
65
|
{
|
|
66
66
|
"path": "webui/src/app/Sidebar.tsx",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
},
|
|
77
77
|
{
|
|
78
78
|
"path": "webui/src/app/TabIcons.tsx",
|
|
79
|
-
"sha256": "
|
|
79
|
+
"sha256": "6205394cd77f672efe6affc9dc4f765c7b0b28b69ee1c87393028a7234672ec8"
|
|
80
80
|
},
|
|
81
81
|
{
|
|
82
82
|
"path": "webui/src/app/TabStrip.tsx",
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
"path": "webui/src/app/phoneCarousel.ts",
|
|
123
|
-
"sha256": "
|
|
123
|
+
"sha256": "e3031ac90e2062b27d7d7e8958eaa29721bea25e2097749771fe35d5be81f1b2"
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
126
|
"path": "webui/src/app/readTopic.ts",
|
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
},
|
|
133
133
|
{
|
|
134
134
|
"path": "webui/src/app/router.ts",
|
|
135
|
-
"sha256": "
|
|
135
|
+
"sha256": "c205e52d7591e67bdcc4bb47e83f890deda169abcc7d3710526e860246b0650a"
|
|
136
136
|
},
|
|
137
137
|
{
|
|
138
138
|
"path": "webui/src/app/sound.ts",
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
},
|
|
141
141
|
{
|
|
142
142
|
"path": "webui/src/app/taskRoute.ts",
|
|
143
|
-
"sha256": "
|
|
143
|
+
"sha256": "0d683f985c5f3787bef4a481dbefea30b93c960523879313c25cea01ec1208cd"
|
|
144
144
|
},
|
|
145
145
|
{
|
|
146
146
|
"path": "webui/src/app/useApiRead.ts",
|
|
@@ -196,7 +196,7 @@
|
|
|
196
196
|
},
|
|
197
197
|
{
|
|
198
198
|
"path": "webui/src/components/ui/alert-dialog.tsx",
|
|
199
|
-
"sha256": "
|
|
199
|
+
"sha256": "119fded71513a3b1ea22f1a9e833c36fa63e74405ae7a83e74cb9faa04f086b5"
|
|
200
200
|
},
|
|
201
201
|
{
|
|
202
202
|
"path": "webui/src/components/ui/badge.tsx",
|
|
@@ -252,7 +252,7 @@
|
|
|
252
252
|
},
|
|
253
253
|
{
|
|
254
254
|
"path": "webui/src/components/ui/toaster.tsx",
|
|
255
|
-
"sha256": "
|
|
255
|
+
"sha256": "6b77318cdbb2e03883bab82709fd5fe856edd6c3ebeb0d3e29f8900aad342e04"
|
|
256
256
|
},
|
|
257
257
|
{
|
|
258
258
|
"path": "webui/src/components/ui/tooltip.tsx",
|
|
@@ -260,7 +260,7 @@
|
|
|
260
260
|
},
|
|
261
261
|
{
|
|
262
262
|
"path": "webui/src/index.css",
|
|
263
|
-
"sha256": "
|
|
263
|
+
"sha256": "566435ec3a072eb7643432fd87ed4d23edfb1c376835221de75bcb6a718f58dc"
|
|
264
264
|
},
|
|
265
265
|
{
|
|
266
266
|
"path": "webui/src/lib/act.ts",
|
|
@@ -364,15 +364,15 @@
|
|
|
364
364
|
},
|
|
365
365
|
{
|
|
366
366
|
"path": "webui/src/prompts/MockupViewer.tsx",
|
|
367
|
-
"sha256": "
|
|
367
|
+
"sha256": "bcf076888a230e89f9d4dbac2b544f5ff9852ad0b29a839abcd71357b649cf12"
|
|
368
368
|
},
|
|
369
369
|
{
|
|
370
370
|
"path": "webui/src/prompts/QuestionCard.tsx",
|
|
371
|
-
"sha256": "
|
|
371
|
+
"sha256": "046423859f7e921ffe8dd31e55dd229a161ebf4fc9f794fe4c7a73eabcff28e5"
|
|
372
372
|
},
|
|
373
373
|
{
|
|
374
374
|
"path": "webui/src/prompts/QuestionPopup.tsx",
|
|
375
|
-
"sha256": "
|
|
375
|
+
"sha256": "1db09e21bc793672b68f3bdd099de50fe5a2069362ef0ca9f22bb58be4fa793d"
|
|
376
376
|
},
|
|
377
377
|
{
|
|
378
378
|
"path": "webui/src/prompts/StaticPreview.tsx",
|
|
@@ -420,7 +420,7 @@
|
|
|
420
420
|
},
|
|
421
421
|
{
|
|
422
422
|
"path": "webui/src/tabs/knowledge/Editors.tsx",
|
|
423
|
-
"sha256": "
|
|
423
|
+
"sha256": "ba8f8670ffde33cf4cc3d671a76b6f41b0e09ada927338a0dbc8927279d62c4e"
|
|
424
424
|
},
|
|
425
425
|
{
|
|
426
426
|
"path": "webui/src/tabs/knowledge/FileDetail.tsx",
|
|
@@ -460,7 +460,7 @@
|
|
|
460
460
|
},
|
|
461
461
|
{
|
|
462
462
|
"path": "webui/src/tabs/lobby/Thoughts.tsx",
|
|
463
|
-
"sha256": "
|
|
463
|
+
"sha256": "1ecf5f407247a2206d4999fe06672c91a6144388ca96bc62f3d8a64d8aa26562"
|
|
464
464
|
},
|
|
465
465
|
{
|
|
466
466
|
"path": "webui/src/tabs/lobby/busy.ts",
|
|
@@ -482,34 +482,6 @@
|
|
|
482
482
|
"path": "webui/src/tabs/lobby/weave.ts",
|
|
483
483
|
"sha256": "c22b23e409642a05d6b50dcb90f606f4635b8ebf6b7c56f443ec7d9c810d6b5a"
|
|
484
484
|
},
|
|
485
|
-
{
|
|
486
|
-
"path": "webui/src/tabs/metrics/Charts.tsx",
|
|
487
|
-
"sha256": "e0f6294f2f0f31b79fca4d291524c0dc46e229a44de602595f2d89c7826d041f"
|
|
488
|
-
},
|
|
489
|
-
{
|
|
490
|
-
"path": "webui/src/tabs/metrics/Classifier.tsx",
|
|
491
|
-
"sha256": "08624cdf2e74b01e1ec3640b5145ee21e995d19db971fea6628c17ce80991671"
|
|
492
|
-
},
|
|
493
|
-
{
|
|
494
|
-
"path": "webui/src/tabs/metrics/DateFilter.tsx",
|
|
495
|
-
"sha256": "49b9955a9a314ce2aed14811b405a1b6c81af4eff69e887c2a72a16d772a7163"
|
|
496
|
-
},
|
|
497
|
-
{
|
|
498
|
-
"path": "webui/src/tabs/metrics/MetricsTab.tsx",
|
|
499
|
-
"sha256": "06f8ba70e205c134548c5602da03974d199a63bb1eae41a5d340a8d29d823cf9"
|
|
500
|
-
},
|
|
501
|
-
{
|
|
502
|
-
"path": "webui/src/tabs/metrics/MetricsTable.tsx",
|
|
503
|
-
"sha256": "dde7487a676dc7514b1124b91fbd1c930c6d184afc1ce9d4aeefff6a30373a45"
|
|
504
|
-
},
|
|
505
|
-
{
|
|
506
|
-
"path": "webui/src/tabs/metrics/Tiles.tsx",
|
|
507
|
-
"sha256": "ed968d73751ebb511516e8c9b3e89b01cb470124c27effb935dc305797409288"
|
|
508
|
-
},
|
|
509
|
-
{
|
|
510
|
-
"path": "webui/src/tabs/metrics/words.ts",
|
|
511
|
-
"sha256": "684c84d1ec9e782140d7d4796529f00fcbf1d0a36f9f4e50c081f19c3df61e73"
|
|
512
|
-
},
|
|
513
485
|
{
|
|
514
486
|
"path": "webui/src/tabs/plan/Conversation.tsx",
|
|
515
487
|
"sha256": "8fe5db3fdf973afda7fd0bfedca5ad0cb4b0ff3db275ecdb412df14d2bd1fee8"
|
|
@@ -564,11 +536,11 @@
|
|
|
564
536
|
},
|
|
565
537
|
{
|
|
566
538
|
"path": "webui/src/tabs/registry.tsx",
|
|
567
|
-
"sha256": "
|
|
539
|
+
"sha256": "6205bec77f47a8a83dee0ee88a3644a05f8c25ab62c2065bf47da3fe8d374749"
|
|
568
540
|
},
|
|
569
541
|
{
|
|
570
542
|
"path": "webui/src/tabs/settings/SettingsForm.tsx",
|
|
571
|
-
"sha256": "
|
|
543
|
+
"sha256": "93c9ef05485f632c0a68d20f283fb4238524ccfe4da982f66ae983e473c36fbb"
|
|
572
544
|
},
|
|
573
545
|
{
|
|
574
546
|
"path": "webui/src/tabs/settings/SettingsTab.tsx",
|
|
@@ -580,7 +552,7 @@
|
|
|
580
552
|
},
|
|
581
553
|
{
|
|
582
554
|
"path": "webui/src/tabs/settings/parts.tsx",
|
|
583
|
-
"sha256": "
|
|
555
|
+
"sha256": "29ad5b69cc1b67caa8b304ed5942d19197b9c5670583dbec1e46337768822e76"
|
|
584
556
|
},
|
|
585
557
|
{
|
|
586
558
|
"path": "webui/src/tabs/settings/words.ts",
|
|
@@ -592,11 +564,11 @@
|
|
|
592
564
|
},
|
|
593
565
|
{
|
|
594
566
|
"path": "webui/src/tabs/tasks/DeliveryReview.tsx",
|
|
595
|
-
"sha256": "
|
|
567
|
+
"sha256": "72d76d6e6736f5e6d56534696d98c51e1ede44024dc3f31e0adb09c9ff0b3f7d"
|
|
596
568
|
},
|
|
597
569
|
{
|
|
598
570
|
"path": "webui/src/tabs/tasks/DetailSections.tsx",
|
|
599
|
-
"sha256": "
|
|
571
|
+
"sha256": "2abf152f2a36e60a35fa31465a1b18380a2a912e8125ada54e0bb9947b6685a7"
|
|
600
572
|
},
|
|
601
573
|
{
|
|
602
574
|
"path": "webui/src/tabs/tasks/OpenInSession.tsx",
|
|
@@ -615,20 +587,20 @@
|
|
|
615
587
|
"sha256": "3bbaa2342151be92262f350a7aee0c832a3609a02e1737c356070a17fad67837"
|
|
616
588
|
},
|
|
617
589
|
{
|
|
618
|
-
"path": "webui/src/tabs/tasks/
|
|
619
|
-
"sha256": "
|
|
590
|
+
"path": "webui/src/tabs/tasks/TaskDetail.tsx",
|
|
591
|
+
"sha256": "45459fa495940ec37f44a5e54be228a7c1b368bfefcd00052ba27df41c0193d6"
|
|
620
592
|
},
|
|
621
593
|
{
|
|
622
|
-
"path": "webui/src/tabs/tasks/
|
|
623
|
-
"sha256": "
|
|
594
|
+
"path": "webui/src/tabs/tasks/TaskInterventions.tsx",
|
|
595
|
+
"sha256": "6e7d22e9c41052c3fdc624e24026012b0258a0e93f93683c50328c34bc5a82dc"
|
|
624
596
|
},
|
|
625
597
|
{
|
|
626
598
|
"path": "webui/src/tabs/tasks/TaskList.tsx",
|
|
627
|
-
"sha256": "
|
|
599
|
+
"sha256": "1005f150fe3b91331c66c0e10e89526ef8a80ae6ecbfdf02d64de04fd1b65b64"
|
|
628
600
|
},
|
|
629
601
|
{
|
|
630
602
|
"path": "webui/src/tabs/tasks/TasksTab.tsx",
|
|
631
|
-
"sha256": "
|
|
603
|
+
"sha256": "9923567df7cb9a52f8a1e2f84567eab5d7fdc26107ff248302019bb81e498491"
|
|
632
604
|
},
|
|
633
605
|
{
|
|
634
606
|
"path": "webui/src/tabs/tasks/words.ts",
|
|
@@ -652,7 +624,7 @@
|
|
|
652
624
|
},
|
|
653
625
|
{
|
|
654
626
|
"path": "webui/src/ui/EffortSlider.tsx",
|
|
655
|
-
"sha256": "
|
|
627
|
+
"sha256": "0590baefa6c17ca9d4372d8658f8508e0578d61da6160165e4e5116c41adb706"
|
|
656
628
|
},
|
|
657
629
|
{
|
|
658
630
|
"path": "webui/src/ui/Frame.tsx",
|
|
@@ -692,7 +664,7 @@
|
|
|
692
664
|
},
|
|
693
665
|
{
|
|
694
666
|
"path": "webui/src/ui/rows.ts",
|
|
695
|
-
"sha256": "
|
|
667
|
+
"sha256": "41c1e5f9f0c3b8abc0006209bb76dff6e45366740be1c7aa623c348c9253685f"
|
|
696
668
|
},
|
|
697
669
|
{
|
|
698
670
|
"path": "webui/src/ui/task-facts.tsx",
|
package/webui/dist/index.html
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
|
14
14
|
<title>bot-lobby</title>
|
|
15
15
|
<meta property="csp-nonce" nonce="__CSP_NONCE__">
|
|
16
|
-
<script type="module" crossorigin src="/assets/index-
|
|
17
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
16
|
+
<script type="module" crossorigin src="/assets/index-BJ1fEle9.js" nonce="__CSP_NONCE__"></script>
|
|
17
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BdtiIB9L.css" nonce="__CSP_NONCE__">
|
|
18
18
|
</head>
|
|
19
19
|
<body>
|
|
20
20
|
<div id="root"></div>
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure metrics data helpers shared by the terminal tab and the web API.
|
|
3
|
-
* This module touches neither the terminal nor pi-tui (unlike
|
|
4
|
-
* `../tabs/metrics.ts`, which draws through `layout.ts`), so the loopback
|
|
5
|
-
* server can import it freely.
|
|
6
|
-
*/
|
|
7
|
-
import type { MetricRecord } from "../../state/metrics.ts";
|
|
8
|
-
|
|
9
|
-
/** Runs whose agent, model, thinking level, kind, status or task mention `query`. */
|
|
10
|
-
export function filterRecords(records: readonly MetricRecord[], query: string | undefined): MetricRecord[] {
|
|
11
|
-
const needle = query?.trim().toLowerCase();
|
|
12
|
-
if (!needle) return [...records];
|
|
13
|
-
return records.filter((record) => [record.agent, record.model ?? "", record.thinking ?? "", record.kind, record.status, record.taskId ?? ""].join(" ").toLowerCase().includes(needle));
|
|
14
|
-
}
|
package/src/webui/api/metrics.ts
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The Metrics tab over HTTP: the KPI tiles, the per-model groups, where the
|
|
3
|
-
* time goes, and what the classifier did — the same figures the terminal
|
|
4
|
-
* tab draws, as data. Search narrows runs exactly like the tab's `/`.
|
|
5
|
-
*/
|
|
6
|
-
import { aggregateMetrics, collectMetrics, sortGroups, summarizeClassifier, taskStats, taskTimesByModel, type MetricRecord } from "../../state/metrics.ts";
|
|
7
|
-
import { filterRecords } from "../../lobby/models/metrics.ts";
|
|
8
|
-
import type { Api, MetricsData } from "../protocol.ts";
|
|
9
|
-
import { fail, type ApiContext } from "./index.ts";
|
|
10
|
-
|
|
11
|
-
function timed(records: readonly MetricRecord[]): MetricRecord[] {
|
|
12
|
-
return records.filter((record) => record.status !== "cancelled" && record.durationMs > 0);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** The KPI row as data: runs, success, average run, cost, tasks. */
|
|
16
|
-
function tilesOf(records: readonly MetricRecord[], stats: ReturnType<typeof taskStats>): MetricsData["tiles"] {
|
|
17
|
-
const done = timed(records);
|
|
18
|
-
const ok = records.filter((record) => record.status === "success").length;
|
|
19
|
-
const cost = records.reduce((sum, record) => sum + (record.cost ?? 0), 0);
|
|
20
|
-
const avg = done.length > 0 ? done.reduce((sum, record) => sum + record.durationMs, 0) / done.length : 0;
|
|
21
|
-
const sorted = done.map((record) => record.durationMs).sort((a, b) => a - b);
|
|
22
|
-
const p90 = sorted.length > 0 ? sorted[Math.min(sorted.length - 1, Math.ceil(0.9 * sorted.length) - 1)]! : 0;
|
|
23
|
-
return {
|
|
24
|
-
runs: records.length,
|
|
25
|
-
successes: ok,
|
|
26
|
-
stalls: records.filter((record) => record.stalled || record.status === "timeout").length,
|
|
27
|
-
avgMs: avg,
|
|
28
|
-
p90Ms: p90,
|
|
29
|
-
cost,
|
|
30
|
-
completed: stats.completed,
|
|
31
|
-
active: stats.active,
|
|
32
|
-
avgCompleteMs: stats.avgCompleteMs,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Each agent's share of all run time, longest first. */
|
|
37
|
-
function shareOf(records: readonly MetricRecord[]): MetricsData["timeShare"]["byAgent"] {
|
|
38
|
-
const byAgent = new Map<string, { runs: number; ms: number }>();
|
|
39
|
-
for (const record of timed(records)) {
|
|
40
|
-
const entry = byAgent.get(record.agent) ?? { runs: 0, ms: 0 };
|
|
41
|
-
entry.runs += 1;
|
|
42
|
-
entry.ms += record.durationMs;
|
|
43
|
-
byAgent.set(record.agent, entry);
|
|
44
|
-
}
|
|
45
|
-
const total = [...byAgent.values()].reduce((sum, entry) => sum + entry.ms, 0);
|
|
46
|
-
return [...byAgent.entries()]
|
|
47
|
-
.sort((a, b) => b[1].ms - a[1].ms)
|
|
48
|
-
.map(([agent, entry]) => ({ agent, runs: entry.runs, ms: entry.ms, share: total > 0 ? entry.ms / total : 0 }));
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** The dashboard figures for one grouping and search. */
|
|
52
|
-
export function metricsGet(body: Api["metrics.get"]["request"], ctx: ApiContext): MetricsData {
|
|
53
|
-
const { from, to } = body;
|
|
54
|
-
const validDate = (value: string) => /^\d{4}-\d{2}-\d{2}$/.test(value) && Number.isFinite(Date.parse(value)) && new Date(value).toISOString().slice(0, 10) === value;
|
|
55
|
-
if ((from === undefined) !== (to === undefined) || (from !== undefined && (!validDate(from) || !validDate(to!) || from > to!))) {
|
|
56
|
-
fail(400, "bad_request", "Choose a valid start and end date, with the start on or before the end.");
|
|
57
|
-
}
|
|
58
|
-
let formatter: Intl.DateTimeFormat;
|
|
59
|
-
try {
|
|
60
|
-
formatter = new Intl.DateTimeFormat("en", { timeZone: body.timeZone ?? "UTC", year: "numeric", month: "2-digit", day: "2-digit" });
|
|
61
|
-
} catch {
|
|
62
|
-
fail(400, "bad_request", "Choose a valid time zone.");
|
|
63
|
-
}
|
|
64
|
-
const dayOf = (timestamp: string) => {
|
|
65
|
-
const date = new Date(timestamp);
|
|
66
|
-
if (!Number.isFinite(date.getTime())) return "";
|
|
67
|
-
const parts = formatter.formatToParts(date);
|
|
68
|
-
return ["year", "month", "day"].map((part) => parts.find((item) => item.type === part)!.value).join("-");
|
|
69
|
-
};
|
|
70
|
-
const inRange = (timestamp: string) => !from || (dayOf(timestamp) >= from && dayOf(timestamp) <= to!);
|
|
71
|
-
const allTasks = ctx.service.tasks();
|
|
72
|
-
const tasks = allTasks.filter((task) => inRange(task.state === "completed" ? task.updatedAt : task.createdAt));
|
|
73
|
-
const allRecords = filterRecords(collectMetrics(ctx.service.metrics(), allTasks), body.query);
|
|
74
|
-
const records = allRecords.filter((record) => inRange(record.startedAt));
|
|
75
|
-
const stats = taskStats(tasks);
|
|
76
|
-
const classifier = summarizeClassifier((ctx.service.classifierMetrics?.() ?? []).filter((record) => inRange(record.startedAt)), records);
|
|
77
|
-
const daily = new Map<string, MetricsData["daily"][number]>();
|
|
78
|
-
for (const record of records) {
|
|
79
|
-
const date = dayOf(record.startedAt);
|
|
80
|
-
if (!date) continue;
|
|
81
|
-
const entry = daily.get(date) ?? { date, runs: 0, successes: 0, cost: 0 };
|
|
82
|
-
entry.runs += 1;
|
|
83
|
-
entry.successes += Number(record.status === "success");
|
|
84
|
-
entry.cost += record.cost ?? 0;
|
|
85
|
-
daily.set(date, entry);
|
|
86
|
-
}
|
|
87
|
-
// Include range edges without allocating an entry for every empty day.
|
|
88
|
-
for (const date of [from, to]) if (date && !daily.has(date)) daily.set(date, { date, runs: 0, successes: 0, cost: 0 });
|
|
89
|
-
return {
|
|
90
|
-
tiles: tilesOf(records, stats),
|
|
91
|
-
daily: [...daily.values()].sort((a, b) => a.date.localeCompare(b.date)),
|
|
92
|
-
groups: sortGroups(aggregateMetrics(records, body.groupBy), "runs"),
|
|
93
|
-
timeShare: { byAgent: shareOf(records), taskTimes: taskTimesByModel(tasks, allRecords) },
|
|
94
|
-
...(classifier ? { classifier } : {}),
|
|
95
|
-
};
|
|
96
|
-
}
|