@lovenyberg/ove 0.7.0 → 0.9.0
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/bin/ove.ts +37 -1
- package/package.json +1 -1
- package/public/index.html +164 -3
- package/public/metrics.html +716 -0
- package/public/status.html +43 -0
- package/public/trace.html +127 -0
- package/src/adapters/github.test.ts +8 -0
- package/src/adapters/github.ts +3 -2
- package/src/adapters/http.test.ts +597 -1
- package/src/adapters/http.ts +222 -3
- package/src/adapters/slack.test.ts +233 -0
- package/src/adapters/types.ts +1 -1
- package/src/adapters/whatsapp.test.ts +102 -0
- package/src/adapters/wiring.test.ts +2 -0
- package/src/diagnostics.test.ts +375 -0
- package/src/handlers.test.ts +553 -0
- package/src/handlers.ts +46 -7
- package/src/index.ts +1 -0
- package/src/queue.test.ts +174 -0
- package/src/queue.ts +85 -7
- package/src/router.test.ts +151 -1
- package/src/router.ts +95 -34
- package/src/sessions.test.ts +41 -0
- package/src/sessions.ts +27 -0
- package/src/setup.ts +160 -1
- package/src/smoke.test.ts +31 -1
package/public/status.html
CHANGED
|
@@ -332,6 +332,48 @@
|
|
|
332
332
|
::-webkit-scrollbar-track { background: transparent; }
|
|
333
333
|
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
|
|
334
334
|
::-webkit-scrollbar-thumb:hover { background: var(--border-light); }
|
|
335
|
+
|
|
336
|
+
/* ── Mobile layout ── */
|
|
337
|
+
@media (max-width: 768px) {
|
|
338
|
+
header {
|
|
339
|
+
flex-wrap: wrap;
|
|
340
|
+
padding: 0.4rem 0.5rem;
|
|
341
|
+
gap: 0.4rem;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.header-left {
|
|
345
|
+
gap: 0.5rem;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.header-left h1 {
|
|
349
|
+
font-size: 0.75rem;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.header-right {
|
|
353
|
+
width: 100%;
|
|
354
|
+
justify-content: flex-end;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.content {
|
|
358
|
+
padding: 0.75rem;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.adapter-grid {
|
|
362
|
+
grid-template-columns: 1fr;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.queue-grid {
|
|
366
|
+
grid-template-columns: repeat(2, 1fr);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.queue-count {
|
|
370
|
+
font-size: 1.1rem;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.pairing-code-value {
|
|
374
|
+
font-size: 1.1rem;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
335
377
|
</style>
|
|
336
378
|
</head>
|
|
337
379
|
<body>
|
|
@@ -341,6 +383,7 @@
|
|
|
341
383
|
<h1><span>ove</span> status</h1>
|
|
342
384
|
<a href="/" class="nav-link">chat</a>
|
|
343
385
|
<a href="/trace" class="nav-link">traces</a>
|
|
386
|
+
<a href="/metrics" class="nav-link">metrics</a>
|
|
344
387
|
</div>
|
|
345
388
|
<div class="header-right">
|
|
346
389
|
<span class="last-updated" id="lastUpdated"></span>
|
package/public/trace.html
CHANGED
|
@@ -225,6 +225,9 @@
|
|
|
225
225
|
.badge-completed { background: var(--green-dim); color: var(--green); }
|
|
226
226
|
.badge-failed { background: var(--red-dim); color: var(--red); }
|
|
227
227
|
|
|
228
|
+
.badge-priority-2 { background: var(--red-dim); color: var(--red); }
|
|
229
|
+
.badge-priority-1 { background: var(--amber-dim); color: var(--amber); }
|
|
230
|
+
|
|
228
231
|
@keyframes pulse {
|
|
229
232
|
0%, 100% { opacity: 1; }
|
|
230
233
|
50% { opacity: 0.6; }
|
|
@@ -297,6 +300,23 @@
|
|
|
297
300
|
.copy-btn:hover { color: var(--text); border-color: var(--border-light); }
|
|
298
301
|
.copy-btn.copied { color: var(--green); border-color: var(--green-dim); }
|
|
299
302
|
|
|
303
|
+
.cancel-btn {
|
|
304
|
+
background: var(--red-dim);
|
|
305
|
+
border: 1px solid var(--red);
|
|
306
|
+
color: var(--red);
|
|
307
|
+
font-family: inherit;
|
|
308
|
+
font-size: 0.6rem;
|
|
309
|
+
padding: 0.2rem 0.6rem;
|
|
310
|
+
border-radius: 3px;
|
|
311
|
+
cursor: pointer;
|
|
312
|
+
transition: all 0.15s;
|
|
313
|
+
text-transform: uppercase;
|
|
314
|
+
letter-spacing: 0.03em;
|
|
315
|
+
font-weight: 600;
|
|
316
|
+
}
|
|
317
|
+
.cancel-btn:hover { background: var(--red); color: #fff; }
|
|
318
|
+
.cancel-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
319
|
+
|
|
300
320
|
/* ── Task context card ── */
|
|
301
321
|
.task-context {
|
|
302
322
|
padding: 0.75rem 1rem;
|
|
@@ -521,6 +541,66 @@
|
|
|
521
541
|
::-webkit-scrollbar-track { background: transparent; }
|
|
522
542
|
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
|
|
523
543
|
::-webkit-scrollbar-thumb:hover { background: var(--border-light); }
|
|
544
|
+
|
|
545
|
+
/* ── Mobile layout ── */
|
|
546
|
+
@media (max-width: 768px) {
|
|
547
|
+
header {
|
|
548
|
+
flex-wrap: wrap;
|
|
549
|
+
padding: 0.4rem 0.5rem;
|
|
550
|
+
gap: 0.4rem;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
.header-left {
|
|
554
|
+
gap: 0.5rem;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.header-left h1 {
|
|
558
|
+
font-size: 0.75rem;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
.header-right {
|
|
562
|
+
flex-wrap: wrap;
|
|
563
|
+
gap: 0.4rem;
|
|
564
|
+
width: 100%;
|
|
565
|
+
justify-content: flex-start;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
.layout {
|
|
569
|
+
flex-direction: column;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
.task-panel {
|
|
573
|
+
width: 100%;
|
|
574
|
+
min-width: 0;
|
|
575
|
+
max-height: 40vh;
|
|
576
|
+
border-right: none;
|
|
577
|
+
border-bottom: 1px solid var(--border);
|
|
578
|
+
flex-shrink: 0;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
.trace-panel {
|
|
582
|
+
flex: 1;
|
|
583
|
+
min-height: 0;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
.trace-event {
|
|
587
|
+
padding: 0.35rem 0.5rem;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
.trace-ts {
|
|
591
|
+
width: 4rem;
|
|
592
|
+
font-size: 0.55rem;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
.ctx-meta {
|
|
596
|
+
gap: 0.75rem;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
.filter-select {
|
|
600
|
+
font-size: 0.6rem;
|
|
601
|
+
padding: 0.15rem 0.3rem;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
524
604
|
</style>
|
|
525
605
|
</head>
|
|
526
606
|
<body>
|
|
@@ -530,6 +610,7 @@
|
|
|
530
610
|
<h1><span>ove</span> trace</h1>
|
|
531
611
|
<a href="/" class="nav-link">chat</a>
|
|
532
612
|
<a href="/status" class="nav-link">status</a>
|
|
613
|
+
<a href="/metrics" class="nav-link">metrics</a>
|
|
533
614
|
</div>
|
|
534
615
|
<div class="header-right">
|
|
535
616
|
<select class="filter-select" id="repoFilter">
|
|
@@ -745,6 +826,11 @@
|
|
|
745
826
|
|
|
746
827
|
var rowTop = el("div", "task-row-top");
|
|
747
828
|
rowTop.appendChild(el("span", "badge badge-" + task.status, task.status));
|
|
829
|
+
if (task.priority === 2) {
|
|
830
|
+
rowTop.appendChild(el("span", "badge badge-priority-2", "urgent"));
|
|
831
|
+
} else if (task.priority === 1) {
|
|
832
|
+
rowTop.appendChild(el("span", "badge badge-priority-1", "high"));
|
|
833
|
+
}
|
|
748
834
|
rowTop.appendChild(el("span", "task-repo", task.repo));
|
|
749
835
|
rowTop.appendChild(el("span", "task-time", fmtTime(task.createdAt)));
|
|
750
836
|
item.appendChild(rowTop);
|
|
@@ -768,10 +854,12 @@
|
|
|
768
854
|
|
|
769
855
|
// Meta row: user, repo, status, timestamps
|
|
770
856
|
var meta = el("div", "ctx-meta");
|
|
857
|
+
var priorityLabel = task.priority === 2 ? "urgent" : task.priority === 1 ? "high" : "normal";
|
|
771
858
|
var items = [
|
|
772
859
|
["user", task.userId],
|
|
773
860
|
["repo", task.repo],
|
|
774
861
|
["status", task.status],
|
|
862
|
+
["priority", priorityLabel],
|
|
775
863
|
["created", fmtTimeFull(task.createdAt)],
|
|
776
864
|
];
|
|
777
865
|
if (task.completedAt) {
|
|
@@ -808,6 +896,18 @@
|
|
|
808
896
|
ctx.appendChild(resultRow);
|
|
809
897
|
}
|
|
810
898
|
|
|
899
|
+
// Cancel button for running/pending tasks
|
|
900
|
+
if (task.status === "running" || task.status === "pending") {
|
|
901
|
+
var cancelRow = el("div", "task-context-row");
|
|
902
|
+
cancelRow.style.marginTop = "0.5rem";
|
|
903
|
+
var cancelBtn = el("button", "cancel-btn", "Cancel task");
|
|
904
|
+
cancelBtn.addEventListener("click", function () {
|
|
905
|
+
cancelTask(task.id, cancelBtn);
|
|
906
|
+
});
|
|
907
|
+
cancelRow.appendChild(cancelBtn);
|
|
908
|
+
ctx.appendChild(cancelRow);
|
|
909
|
+
}
|
|
910
|
+
|
|
811
911
|
taskContextEl.appendChild(ctx);
|
|
812
912
|
}
|
|
813
913
|
|
|
@@ -895,6 +995,33 @@
|
|
|
895
995
|
traceContentEl.appendChild(timeline);
|
|
896
996
|
}
|
|
897
997
|
|
|
998
|
+
// Cancel task
|
|
999
|
+
async function cancelTask(taskId, btn) {
|
|
1000
|
+
btn.disabled = true;
|
|
1001
|
+
btn.textContent = "Cancelling...";
|
|
1002
|
+
try {
|
|
1003
|
+
var res = await fetch("/api/tasks/" + encodeURIComponent(taskId) + "/cancel", {
|
|
1004
|
+
method: "POST",
|
|
1005
|
+
headers: apiHeaders(),
|
|
1006
|
+
});
|
|
1007
|
+
if (res.ok) {
|
|
1008
|
+
btn.textContent = "Cancelled";
|
|
1009
|
+
// Refresh task list and trace
|
|
1010
|
+
await fetchTasks();
|
|
1011
|
+
if (selectedTaskId === taskId) {
|
|
1012
|
+
await selectTask(taskId);
|
|
1013
|
+
}
|
|
1014
|
+
} else {
|
|
1015
|
+
var err = await res.json();
|
|
1016
|
+
btn.textContent = "Failed: " + (err.error || res.status);
|
|
1017
|
+
setTimeout(function () { btn.textContent = "Cancel task"; btn.disabled = false; }, 2000);
|
|
1018
|
+
}
|
|
1019
|
+
} catch (e) {
|
|
1020
|
+
btn.textContent = "Error";
|
|
1021
|
+
setTimeout(function () { btn.textContent = "Cancel task"; btn.disabled = false; }, 2000);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
|
|
898
1025
|
// Auto-refresh
|
|
899
1026
|
var lastSelectedStatus = null;
|
|
900
1027
|
autoRefreshEl.addEventListener("change", function () {
|
|
@@ -18,4 +18,12 @@ describe("GitHubAdapter", () => {
|
|
|
18
18
|
expect(parseMention("hey @ove-bot review PR #5", "ove-bot")).toBe("hey review PR #5");
|
|
19
19
|
expect(parseMention("no mention here", "ove-bot")).toBeNull();
|
|
20
20
|
});
|
|
21
|
+
|
|
22
|
+
test("parseMention handles botName with regex metacharacters", async () => {
|
|
23
|
+
const { parseMention } = await import("./github");
|
|
24
|
+
// botName like "bot[1]" would break if used in a regex without escaping
|
|
25
|
+
expect(parseMention("@bot[1] fix this", "bot[1]")).toBe("fix this");
|
|
26
|
+
expect(parseMention("@bot.star do it", "bot.star")).toBe("do it");
|
|
27
|
+
expect(parseMention("@bot(x) hello", "bot(x)")).toBe("hello");
|
|
28
|
+
});
|
|
21
29
|
});
|
package/src/adapters/github.ts
CHANGED
|
@@ -2,8 +2,9 @@ import type { EventAdapter, IncomingEvent, EventSource, AdapterStatus } from "./
|
|
|
2
2
|
import { logger } from "../logger";
|
|
3
3
|
|
|
4
4
|
export function parseMention(body: string, botName: string): string | null {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
const mention = "@" + botName;
|
|
6
|
+
if (!body.includes(mention)) return null;
|
|
7
|
+
return body.replaceAll(mention, "").trim();
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
interface GitHubComment {
|