@nordbyte/nordrelay 0.5.0 → 0.5.2
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/.env.example +2 -0
- package/README.md +23 -14
- package/dist/access-control.js +2 -0
- package/dist/agent-updates.js +61 -10
- package/dist/bot-ui.js +1 -0
- package/dist/bot.js +142 -1065
- package/dist/channel-actions.js +8 -8
- package/dist/codex-cli.js +1 -1
- package/dist/config-metadata.js +2 -0
- package/dist/operations.js +233 -122
- package/dist/relay-artifact-service.js +126 -0
- package/dist/relay-external-activity-monitor.js +216 -0
- package/dist/relay-queue-service.js +66 -0
- package/dist/relay-runtime-types.js +1 -0
- package/dist/relay-runtime.js +119 -371
- package/dist/state-backend.js +3 -0
- package/dist/support-bundle.js +221 -0
- package/dist/telegram-agent-commands.js +212 -0
- package/dist/telegram-artifact-commands.js +139 -0
- package/dist/telegram-command-menu.js +1 -0
- package/dist/telegram-command-types.js +1 -0
- package/dist/telegram-diagnostics-command.js +102 -0
- package/dist/telegram-general-commands.js +52 -0
- package/dist/telegram-operational-commands.js +153 -0
- package/dist/telegram-preference-commands.js +198 -0
- package/dist/telegram-queue-commands.js +278 -0
- package/dist/telegram-support-command.js +53 -0
- package/dist/telegram-update-commands.js +6 -1
- package/dist/web-api-contract.js +79 -31
- package/dist/web-api-types.js +1 -0
- package/dist/web-dashboard-access-routes.js +163 -0
- package/dist/web-dashboard-artifact-routes.js +65 -0
- package/dist/web-dashboard-assets.js +2 -0
- package/dist/web-dashboard-http.js +143 -0
- package/dist/web-dashboard-pages.js +257 -0
- package/dist/web-dashboard-runtime-routes.js +92 -0
- package/dist/web-dashboard-session-routes.js +209 -0
- package/dist/web-dashboard.js +44 -882
- package/dist/webui-assets/dashboard.css +74 -4
- package/dist/webui-assets/dashboard.js +163 -24
- package/dist/zip-writer.js +83 -0
- package/package.json +10 -4
- package/plugins/nordrelay/.codex-plugin/plugin.json +1 -1
- package/plugins/nordrelay/scripts/nordrelay.mjs +258 -5
|
@@ -62,7 +62,9 @@
|
|
|
62
62
|
background: var(--surface);
|
|
63
63
|
color: var(--text);
|
|
64
64
|
border-color: var(--border);
|
|
65
|
+
min-height: 32px;
|
|
65
66
|
height: 32px;
|
|
67
|
+
line-height: 1;
|
|
66
68
|
}
|
|
67
69
|
.agent-settings-nav button.active {
|
|
68
70
|
background: var(--accent);
|
|
@@ -84,10 +86,13 @@
|
|
|
84
86
|
.chip {
|
|
85
87
|
display: inline-flex;
|
|
86
88
|
align-items: center;
|
|
89
|
+
justify-content: center;
|
|
90
|
+
min-height: 20px;
|
|
87
91
|
border-radius: 999px;
|
|
88
92
|
border: 1px solid var(--border);
|
|
89
|
-
padding:
|
|
93
|
+
padding: 0 8px;
|
|
90
94
|
font-size: 12px;
|
|
95
|
+
line-height: 1;
|
|
91
96
|
color: var(--muted);
|
|
92
97
|
margin-right: 6px;
|
|
93
98
|
}
|
|
@@ -139,6 +144,7 @@
|
|
|
139
144
|
cursor: pointer;
|
|
140
145
|
font-weight: 700;
|
|
141
146
|
margin-bottom: 0;
|
|
147
|
+
line-height: 1.25;
|
|
142
148
|
}
|
|
143
149
|
.session-detail-section[open] summary {
|
|
144
150
|
margin-bottom: 10px;
|
|
@@ -239,9 +245,11 @@
|
|
|
239
245
|
margin-top: 22px;
|
|
240
246
|
}
|
|
241
247
|
.mini-button {
|
|
248
|
+
min-height: 26px;
|
|
242
249
|
height: 26px;
|
|
243
250
|
padding: 0 8px;
|
|
244
251
|
font-size: 12px;
|
|
252
|
+
line-height: 1;
|
|
245
253
|
}
|
|
246
254
|
.update-log {
|
|
247
255
|
max-height: 280px;
|
|
@@ -264,16 +272,26 @@
|
|
|
264
272
|
* {
|
|
265
273
|
box-sizing: border-box;
|
|
266
274
|
}
|
|
275
|
+
html {
|
|
276
|
+
font-size: 16px;
|
|
277
|
+
-webkit-text-size-adjust: 100%;
|
|
278
|
+
text-size-adjust: 100%;
|
|
279
|
+
}
|
|
267
280
|
body {
|
|
268
281
|
margin: 0;
|
|
269
282
|
background: var(--bg);
|
|
270
283
|
color: var(--text);
|
|
271
284
|
font-family:
|
|
272
285
|
Inter,
|
|
286
|
+
"Segoe UI",
|
|
273
287
|
system-ui,
|
|
274
288
|
-apple-system,
|
|
275
|
-
|
|
289
|
+
BlinkMacSystemFont,
|
|
290
|
+
Roboto,
|
|
291
|
+
Arial,
|
|
276
292
|
sans-serif;
|
|
293
|
+
line-height: 1.4;
|
|
294
|
+
font-synthesis: none;
|
|
277
295
|
}
|
|
278
296
|
.app {
|
|
279
297
|
min-height: 100vh;
|
|
@@ -421,14 +439,30 @@ textarea {
|
|
|
421
439
|
background: var(--surface);
|
|
422
440
|
color: var(--text);
|
|
423
441
|
font: inherit;
|
|
442
|
+
line-height: 1.2;
|
|
443
|
+
vertical-align: middle;
|
|
424
444
|
}
|
|
425
445
|
button {
|
|
446
|
+
appearance: none;
|
|
447
|
+
-webkit-appearance: none;
|
|
448
|
+
display: inline-flex;
|
|
449
|
+
align-items: center;
|
|
450
|
+
justify-content: center;
|
|
451
|
+
gap: 6px;
|
|
452
|
+
min-height: 36px;
|
|
426
453
|
height: 36px;
|
|
427
454
|
padding: 0 12px;
|
|
428
455
|
background: var(--accent);
|
|
429
456
|
color: white;
|
|
430
457
|
border-color: var(--accent);
|
|
431
458
|
cursor: pointer;
|
|
459
|
+
line-height: 1;
|
|
460
|
+
text-align: center;
|
|
461
|
+
white-space: nowrap;
|
|
462
|
+
}
|
|
463
|
+
button::-moz-focus-inner {
|
|
464
|
+
border: 0;
|
|
465
|
+
padding: 0;
|
|
432
466
|
}
|
|
433
467
|
button:hover {
|
|
434
468
|
background: var(--accent-strong);
|
|
@@ -437,8 +471,12 @@ button.secondary {
|
|
|
437
471
|
background: var(--surface);
|
|
438
472
|
color: var(--text);
|
|
439
473
|
}
|
|
474
|
+
button:disabled {
|
|
475
|
+
cursor: not-allowed;
|
|
476
|
+
}
|
|
440
477
|
input,
|
|
441
478
|
select {
|
|
479
|
+
min-height: 36px;
|
|
442
480
|
height: 36px;
|
|
443
481
|
padding: 0 10px;
|
|
444
482
|
}
|
|
@@ -446,6 +484,23 @@ textarea {
|
|
|
446
484
|
width: 100%;
|
|
447
485
|
padding: 10px;
|
|
448
486
|
resize: vertical;
|
|
487
|
+
line-height: 1.4;
|
|
488
|
+
}
|
|
489
|
+
nav button {
|
|
490
|
+
display: flex;
|
|
491
|
+
align-items: center;
|
|
492
|
+
justify-content: flex-start;
|
|
493
|
+
width: 100%;
|
|
494
|
+
min-height: 40px;
|
|
495
|
+
height: 40px;
|
|
496
|
+
padding: 0 12px;
|
|
497
|
+
line-height: 1.1;
|
|
498
|
+
text-align: left;
|
|
499
|
+
}
|
|
500
|
+
.menu {
|
|
501
|
+
align-items: center;
|
|
502
|
+
justify-content: center;
|
|
503
|
+
line-height: 1;
|
|
449
504
|
}
|
|
450
505
|
.chat-layout {
|
|
451
506
|
display: grid;
|
|
@@ -520,6 +575,8 @@ textarea {
|
|
|
520
575
|
.file-button {
|
|
521
576
|
display: inline-flex;
|
|
522
577
|
align-items: center;
|
|
578
|
+
justify-content: center;
|
|
579
|
+
min-height: 34px;
|
|
523
580
|
height: 34px;
|
|
524
581
|
padding: 0 10px;
|
|
525
582
|
border: 1px solid var(--border);
|
|
@@ -527,6 +584,7 @@ textarea {
|
|
|
527
584
|
background: var(--surface);
|
|
528
585
|
color: var(--text);
|
|
529
586
|
cursor: pointer;
|
|
587
|
+
line-height: 1;
|
|
530
588
|
}
|
|
531
589
|
input[type=file] {
|
|
532
590
|
display: none;
|
|
@@ -551,6 +609,7 @@ input[type=file] {
|
|
|
551
609
|
}
|
|
552
610
|
.copy-id {
|
|
553
611
|
height: auto;
|
|
612
|
+
min-height: 0;
|
|
554
613
|
padding: 0;
|
|
555
614
|
border: 0;
|
|
556
615
|
background: transparent;
|
|
@@ -562,6 +621,8 @@ input[type=file] {
|
|
|
562
621
|
Consolas,
|
|
563
622
|
monospace;
|
|
564
623
|
font-size: 12px;
|
|
624
|
+
line-height: 1.25;
|
|
625
|
+
white-space: normal;
|
|
565
626
|
}
|
|
566
627
|
.copy-id:hover {
|
|
567
628
|
background: transparent;
|
|
@@ -603,11 +664,13 @@ input[type=file] {
|
|
|
603
664
|
.item strong {
|
|
604
665
|
display: block;
|
|
605
666
|
overflow-wrap: anywhere;
|
|
667
|
+
line-height: 1.25;
|
|
606
668
|
}
|
|
607
669
|
.item small {
|
|
608
670
|
display: block;
|
|
609
671
|
color: var(--muted);
|
|
610
672
|
overflow-wrap: anywhere;
|
|
673
|
+
line-height: 1.35;
|
|
611
674
|
}
|
|
612
675
|
.queue-item {
|
|
613
676
|
cursor: grab;
|
|
@@ -619,11 +682,14 @@ input[type=file] {
|
|
|
619
682
|
.adapter-status {
|
|
620
683
|
display: inline-flex;
|
|
621
684
|
align-items: center;
|
|
685
|
+
justify-content: center;
|
|
686
|
+
min-height: 20px;
|
|
622
687
|
border: 1px solid var(--border);
|
|
623
688
|
border-radius: 999px;
|
|
624
|
-
padding:
|
|
689
|
+
padding: 0 8px;
|
|
625
690
|
color: var(--muted);
|
|
626
691
|
font-size: 12px;
|
|
692
|
+
line-height: 1;
|
|
627
693
|
}
|
|
628
694
|
.adapter-status {
|
|
629
695
|
margin-left: 6px;
|
|
@@ -651,12 +717,14 @@ input[type=file] {
|
|
|
651
717
|
}
|
|
652
718
|
.feature-chip {
|
|
653
719
|
display: flex;
|
|
720
|
+
align-items: center;
|
|
654
721
|
justify-content: space-between;
|
|
655
722
|
gap: 8px;
|
|
656
723
|
border: 1px solid var(--border-soft);
|
|
657
724
|
border-radius: 6px;
|
|
658
725
|
padding: 5px 7px;
|
|
659
726
|
font-size: 12px;
|
|
727
|
+
line-height: 1.2;
|
|
660
728
|
color: var(--muted);
|
|
661
729
|
background: var(--surface);
|
|
662
730
|
}
|
|
@@ -726,7 +794,9 @@ input[type=file] {
|
|
|
726
794
|
background: var(--surface);
|
|
727
795
|
color: var(--text);
|
|
728
796
|
border-color: var(--border);
|
|
797
|
+
min-height: 34px;
|
|
729
798
|
height: 34px;
|
|
799
|
+
line-height: 1;
|
|
730
800
|
}
|
|
731
801
|
.tabs button.active {
|
|
732
802
|
background: var(--accent);
|
|
@@ -836,7 +906,7 @@ dialog::backdrop {
|
|
|
836
906
|
transform: translateX(0);
|
|
837
907
|
}
|
|
838
908
|
.menu {
|
|
839
|
-
display: inline-
|
|
909
|
+
display: inline-flex;
|
|
840
910
|
}
|
|
841
911
|
.header-actions {
|
|
842
912
|
justify-content: flex-end;
|
|
@@ -1,17 +1,145 @@
|
|
|
1
1
|
(() => {
|
|
2
|
-
const
|
|
2
|
+
const WEB_API_CLIENT_ROUTE_RULES = [
|
|
3
|
+
{ path: "/api/auth/me", methods: ["GET"] },
|
|
4
|
+
{ path: "/api/dashboard/logout", methods: ["POST"] },
|
|
5
|
+
{ path: "/api/bootstrap", methods: ["GET"] },
|
|
6
|
+
{ path: "/api/health", methods: ["GET"] },
|
|
7
|
+
{ path: "/api/snapshot", methods: ["GET"] },
|
|
8
|
+
{ path: "/api/tasks", methods: ["GET"] },
|
|
9
|
+
{ path: "/api/progress", methods: ["GET"] },
|
|
10
|
+
{ path: "/api/version", methods: ["GET"] },
|
|
11
|
+
{ path: "/api/update", methods: ["POST"] },
|
|
12
|
+
{ path: "/api/agent-updates", methods: ["GET"] },
|
|
13
|
+
{ path: "/api/agent-update", methods: ["POST"] },
|
|
14
|
+
{ re: /^\/api\/agent-update\/[^\/]+\/log$/, methods: ["GET", "DELETE"] },
|
|
15
|
+
{ re: /^\/api\/agent-update\/[^\/]+\/input$/, methods: ["POST"] },
|
|
16
|
+
{ re: /^\/api\/agent-update\/[^\/]+\/cancel$/, methods: ["POST"] },
|
|
17
|
+
{ path: "/api/adapters/health", methods: ["GET"] },
|
|
18
|
+
{ path: "/api/permissions", methods: ["GET"] },
|
|
19
|
+
{ path: "/api/users", methods: ["GET", "POST"] },
|
|
20
|
+
{ re: /^\/api\/users\/[^\/]+$/, methods: ["PATCH"] },
|
|
21
|
+
{ re: /^\/api\/users\/[^\/]+\/password$/, methods: ["POST"] },
|
|
22
|
+
{ re: /^\/api\/users\/[^\/]+\/sessions$/, methods: ["GET", "DELETE"] },
|
|
23
|
+
{ re: /^\/api\/users\/[^\/]+\/sessions\/[^\/]+$/, methods: ["DELETE"] },
|
|
24
|
+
{ re: /^\/api\/users\/[^\/]+\/telegram$/, methods: ["POST"] },
|
|
25
|
+
{ re: /^\/api\/users\/[^\/]+\/telegram\/[^\/]+$/, methods: ["DELETE"] },
|
|
26
|
+
{ path: "/api/groups", methods: ["GET", "POST"] },
|
|
27
|
+
{ re: /^\/api\/groups\/[^\/]+$/, methods: ["PATCH"] },
|
|
28
|
+
{ path: "/api/telegram-chats", methods: ["GET", "POST"] },
|
|
29
|
+
{ re: /^\/api\/telegram-chats\/[^\/]+$/, methods: ["PATCH"] },
|
|
30
|
+
{ path: "/api/audit", methods: ["GET"] },
|
|
31
|
+
{ path: "/api/locks", methods: ["GET", "POST", "DELETE"] },
|
|
32
|
+
{ path: "/api/auth/status", methods: ["GET"] },
|
|
33
|
+
{ path: "/api/auth/login", methods: ["POST"] },
|
|
34
|
+
{ path: "/api/auth/logout", methods: ["POST"] },
|
|
35
|
+
{ path: "/api/settings", methods: ["GET", "PATCH"] },
|
|
36
|
+
{ path: "/api/control-options", methods: ["GET"] },
|
|
37
|
+
{ path: "/api/sessions", methods: ["GET"] },
|
|
38
|
+
{ path: "/api/sessions/new", methods: ["POST"] },
|
|
39
|
+
{ path: "/api/sessions/switch", methods: ["POST"] },
|
|
40
|
+
{ path: "/api/sessions/attach", methods: ["POST"] },
|
|
41
|
+
{ path: "/api/sessions/detail", methods: ["GET"] },
|
|
42
|
+
{ path: "/api/agent", methods: ["POST"] },
|
|
43
|
+
{ path: "/api/models", methods: ["GET"] },
|
|
44
|
+
{ path: "/api/session/model", methods: ["POST"] },
|
|
45
|
+
{ path: "/api/session/reasoning", methods: ["POST"] },
|
|
46
|
+
{ path: "/api/session/fast", methods: ["POST"] },
|
|
47
|
+
{ path: "/api/session/launch", methods: ["POST"] },
|
|
48
|
+
{ path: "/api/prompt", methods: ["POST"] },
|
|
49
|
+
{ path: "/api/prompt/upload", methods: ["POST"] },
|
|
50
|
+
{ path: "/api/abort", methods: ["POST"] },
|
|
51
|
+
{ path: "/api/stop", methods: ["POST"] },
|
|
52
|
+
{ path: "/api/handback", methods: ["POST"] },
|
|
53
|
+
{ path: "/api/retry", methods: ["POST"] },
|
|
54
|
+
{ path: "/api/sync", methods: ["POST"] },
|
|
55
|
+
{ path: "/api/queue", methods: ["GET", "POST"] },
|
|
56
|
+
{ path: "/api/chat/history", methods: ["GET", "DELETE"] },
|
|
57
|
+
{ path: "/api/activity", methods: ["GET"] },
|
|
58
|
+
{ path: "/api/artifacts", methods: ["GET", "DELETE"] },
|
|
59
|
+
{ path: "/api/artifacts/bulk", methods: ["POST"] },
|
|
60
|
+
{ path: "/api/artifacts/zip", methods: ["GET"] },
|
|
61
|
+
{ path: "/api/artifacts/file", methods: ["GET"] },
|
|
62
|
+
{ path: "/api/artifacts/preview", methods: ["GET"] },
|
|
63
|
+
{ path: "/api/logs", methods: ["GET"] },
|
|
64
|
+
{ path: "/api/logs/clear", methods: ["POST"] },
|
|
65
|
+
{ path: "/api/diagnostics", methods: ["GET"] },
|
|
66
|
+
{ path: "/api/diagnostics/bundle", methods: ["GET"] },
|
|
67
|
+
{ path: "/api/runtime/restart", methods: ["POST"] }
|
|
68
|
+
];
|
|
69
|
+
globalThis.NORDRELAY_WEB_API_CLIENT_ROUTE_RULES = WEB_API_CLIENT_ROUTE_RULES;
|
|
70
|
+
const API_ROUTE_RULES = (
|
|
71
|
+
/** @type {{ NORDRELAY_WEB_API_CLIENT_ROUTE_RULES?: ApiRouteRule[] }} */
|
|
72
|
+
globalThis.NORDRELAY_WEB_API_CLIENT_ROUTE_RULES ?? []
|
|
73
|
+
);
|
|
3
74
|
async function api(path, options = {}) {
|
|
4
|
-
const
|
|
5
|
-
const
|
|
75
|
+
const method = normalizeMethod(options.method, options.body);
|
|
76
|
+
const url = apiUrl(path, options.query);
|
|
77
|
+
assertApiRoute(url.pathname, method);
|
|
78
|
+
const body = normalizeBody(options.body);
|
|
79
|
+
const headers = {
|
|
80
|
+
...body !== void 0 && shouldSendJsonHeader(options.body) ? { "content-type": "application/json" } : {},
|
|
81
|
+
...options.headers || {}
|
|
82
|
+
};
|
|
83
|
+
const res = await fetch(url.pathname + url.search, { method, headers, body });
|
|
6
84
|
if (res.status === 401) {
|
|
7
85
|
location.reload();
|
|
8
|
-
return
|
|
86
|
+
return (
|
|
87
|
+
/** @type {never} */
|
|
88
|
+
void 0
|
|
89
|
+
);
|
|
9
90
|
}
|
|
10
91
|
const text = await res.text();
|
|
11
92
|
const data = text ? JSON.parse(text) : {};
|
|
12
93
|
if (!res.ok) throw new Error(data.error || res.statusText);
|
|
13
94
|
return data;
|
|
14
95
|
}
|
|
96
|
+
function apiUrl(path, query) {
|
|
97
|
+
const url = new URL(path, location.origin);
|
|
98
|
+
if (query) {
|
|
99
|
+
for (const [key, rawValue] of Object.entries(query)) {
|
|
100
|
+
const values = Array.isArray(rawValue) ? rawValue : [rawValue];
|
|
101
|
+
for (const value of values) {
|
|
102
|
+
if (value !== void 0 && value !== null && value !== "") {
|
|
103
|
+
url.searchParams.append(key, String(value));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return url;
|
|
109
|
+
}
|
|
110
|
+
function normalizeMethod(method, body) {
|
|
111
|
+
if (method) {
|
|
112
|
+
const upper = method.toUpperCase();
|
|
113
|
+
if (upper === "GET" || upper === "POST" || upper === "PATCH" || upper === "PUT" || upper === "DELETE") {
|
|
114
|
+
return upper;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return body === void 0 || body === null ? "GET" : "POST";
|
|
118
|
+
}
|
|
119
|
+
function normalizeBody(body) {
|
|
120
|
+
if (body === void 0 || body === null) return void 0;
|
|
121
|
+
if (typeof body === "string") return body;
|
|
122
|
+
if (isNativeBody(body)) return body;
|
|
123
|
+
return JSON.stringify(body);
|
|
124
|
+
}
|
|
125
|
+
function shouldSendJsonHeader(body) {
|
|
126
|
+
return body !== void 0 && body !== null && !isNativeBody(body);
|
|
127
|
+
}
|
|
128
|
+
function isNativeBody(body) {
|
|
129
|
+
return typeof FormData !== "undefined" && body instanceof FormData || typeof Blob !== "undefined" && body instanceof Blob || typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams || typeof ArrayBuffer !== "undefined" && body instanceof ArrayBuffer;
|
|
130
|
+
}
|
|
131
|
+
function assertApiRoute(path, method) {
|
|
132
|
+
const rule = API_ROUTE_RULES.find(
|
|
133
|
+
(candidate) => "path" in candidate && candidate.path === path || "re" in candidate && candidate.re.test(path)
|
|
134
|
+
);
|
|
135
|
+
if (!rule) {
|
|
136
|
+
throw new Error("Unknown WebUI API route: " + path);
|
|
137
|
+
}
|
|
138
|
+
if (!rule.methods.includes(method)) {
|
|
139
|
+
throw new Error("Unsupported WebUI API method: " + method + " " + path);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const state = { snapshot: null, controls: null, newSessionControls: null, enabledAgents: [], auth: null, permissions: [], settings: [], currentPage: "overview", settingsGroup: null, logsPlain: "", logTimer: null, toastTimer: null, cliStatusActive: false, selectedArtifactTurns: /* @__PURE__ */ new Set(), mediaRecorder: null, recordedChunks: [], events: null, reconnectTimer: null, notifications: false, toolTooltipTimer: null, toolTooltipTarget: null, agentUpdateJobs: [], sessionsRequestId: 0 };
|
|
15
143
|
function toast(msg, options = {}) {
|
|
16
144
|
const el = document.getElementById("toast");
|
|
17
145
|
el.textContent = msg;
|
|
@@ -772,7 +900,7 @@
|
|
|
772
900
|
state.newSessionControls = state.controls || {};
|
|
773
901
|
renderNewSessionControls(state.newSessionControls);
|
|
774
902
|
agentSelect.onchange = () => safe(async () => {
|
|
775
|
-
state.newSessionControls = await api("/api/control-options
|
|
903
|
+
state.newSessionControls = await api("/api/control-options", { query: { agent: agentSelect.value } });
|
|
776
904
|
renderNewSessionControls(state.newSessionControls);
|
|
777
905
|
});
|
|
778
906
|
}
|
|
@@ -806,8 +934,7 @@
|
|
|
806
934
|
const requestId = ++state.sessionsRequestId;
|
|
807
935
|
setLoading("sessionsList", "Loading " + (expectedAgent || "agent") + " sessions...");
|
|
808
936
|
const q = document.getElementById("sessionSearch").value || "";
|
|
809
|
-
const
|
|
810
|
-
const data = await api("/api/sessions?query=" + encodeURIComponent(q) + "&page=" + sessionsPager.page + "&limit=" + sessionsPager.pageSize + agentParam);
|
|
937
|
+
const data = await api("/api/sessions", { query: { query: q, page: sessionsPager.page, limit: sessionsPager.pageSize, agent: expectedAgent || void 0 } });
|
|
811
938
|
if (requestId !== state.sessionsRequestId || expectedAgent !== activeAgentId()) return;
|
|
812
939
|
document.getElementById("sessionsList").innerHTML = data.sessions.map((s) => '<div class="item"><strong title="' + attr(s.title || s.firstUserMessage || s.id) + '">' + esc(short(s.title || s.firstUserMessage || s.id)) + '</strong><small><button type="button" class="copy-id" data-copy-id="' + attr(s.id) + '" title="Copy thread ID">' + esc(short(s.id, 64)) + "</button> / " + esc(short((s.cwd || "") + " / " + fmtDate(s.updatedAt))) + '</small><div class="row"><button data-switch="' + attr(s.id) + '"' + disabledAttr("sessions.write") + '>Switch</button><button class="secondary" data-session-detail="' + attr(s.id) + '">Details</button></div></div>').join("") || '<div class="item">No sessions found.</div>';
|
|
813
940
|
sessionsPager.render(data.pagination || {});
|
|
@@ -817,7 +944,7 @@
|
|
|
817
944
|
toast("Permission required: sessions.write");
|
|
818
945
|
return;
|
|
819
946
|
}
|
|
820
|
-
await api("/api/sessions/switch", { method: "POST", body:
|
|
947
|
+
await api("/api/sessions/switch", { method: "POST", body: { threadId: b.dataset.switch } });
|
|
821
948
|
toast("Session switched");
|
|
822
949
|
loadBootstrap();
|
|
823
950
|
}));
|
|
@@ -836,7 +963,7 @@
|
|
|
836
963
|
return '<details class="session-detail-section"><summary>' + esc(title + " (" + count + ")") + '</summary><div class="list">' + (body || '<div class="item">No entries.</div>') + "</div></details>";
|
|
837
964
|
}
|
|
838
965
|
async function loadSessionDetail(threadId) {
|
|
839
|
-
const d = await api("/api/sessions/detail
|
|
966
|
+
const d = await api("/api/sessions/detail", { query: { threadId } });
|
|
840
967
|
const r = d.record || {};
|
|
841
968
|
const messages = d.messages || [];
|
|
842
969
|
const activity = d.activity || [];
|
|
@@ -972,7 +1099,7 @@
|
|
|
972
1099
|
return;
|
|
973
1100
|
}
|
|
974
1101
|
if (confirm("Delete artifact turn " + b.dataset.delArt + "?")) {
|
|
975
|
-
await api("/api/artifacts
|
|
1102
|
+
await api("/api/artifacts", { method: "DELETE", query: { turnId: b.dataset.delArt } });
|
|
976
1103
|
state.selectedArtifactTurns.delete(b.dataset.delArt);
|
|
977
1104
|
loadArtifacts();
|
|
978
1105
|
}
|
|
@@ -1016,7 +1143,7 @@
|
|
|
1016
1143
|
target.innerHTML = '<div class="panel">' + loadingHtml("Loading preview...") + "</div>";
|
|
1017
1144
|
target.scrollIntoView({ block: "start", behavior: "smooth" });
|
|
1018
1145
|
try {
|
|
1019
|
-
const data = await api("/api/artifacts/preview
|
|
1146
|
+
const data = await api("/api/artifacts/preview", { query: { turnId, path } });
|
|
1020
1147
|
if (data.kind === "image") {
|
|
1021
1148
|
target.innerHTML = '<div class="panel"><h2>' + esc(data.name) + '</h2><img src="/api/artifacts/file?turnId=' + encodeURIComponent(turnId) + "&path=" + encodeURIComponent(path) + '"></div>';
|
|
1022
1149
|
return;
|
|
@@ -1033,8 +1160,7 @@
|
|
|
1033
1160
|
}
|
|
1034
1161
|
async function loadActivity() {
|
|
1035
1162
|
setLoading("activityList", "Loading activity...");
|
|
1036
|
-
const
|
|
1037
|
-
const data = await api("/api/activity" + q);
|
|
1163
|
+
const data = await api("/api/activity", { query: { source: val("activitySource"), status: val("activityStatus"), limit: val("activityLimit") || "100" } });
|
|
1038
1164
|
state.activityEvents = data.events || [];
|
|
1039
1165
|
renderActivity(state.activityEvents);
|
|
1040
1166
|
}
|
|
@@ -1376,7 +1502,7 @@
|
|
|
1376
1502
|
document.getElementById("auditList").innerHTML = '<div class="item">Permission required: audit.read</div>';
|
|
1377
1503
|
return;
|
|
1378
1504
|
}
|
|
1379
|
-
const d = await api("/api/audit
|
|
1505
|
+
const d = await api("/api/audit", { query: { limit: val("auditLimit") || "50" } });
|
|
1380
1506
|
document.getElementById("auditList").innerHTML = (d.events || []).map((e) => '<div class="item"><strong>' + esc(fmtDate(e.timestamp) + " / " + (e.channelId || "-") + " / " + e.status + " / " + e.action) + "</strong><small>" + esc((e.contextKey || "-") + " / " + (e.agentId || "-") + " / " + (e.threadId || "-")) + "</small><small>" + esc(e.description || e.detail || "") + "</small></div>").join("") || '<div class="item">No audit events.</div>';
|
|
1381
1507
|
}
|
|
1382
1508
|
document.getElementById("loadAuditBtn").onclick = () => loadAudit();
|
|
@@ -1384,7 +1510,7 @@
|
|
|
1384
1510
|
if (!document.getElementById("logAutoRefresh").checked) setLoading("logs", "Loading logs...");
|
|
1385
1511
|
const target = document.getElementById("logTarget").value;
|
|
1386
1512
|
const lines = document.getElementById("logLines").value;
|
|
1387
|
-
const data = await api("/api/logs
|
|
1513
|
+
const data = await api("/api/logs", { query: { target, lines } });
|
|
1388
1514
|
state.logsPlain = data.plain || "";
|
|
1389
1515
|
renderLogs();
|
|
1390
1516
|
if (document.getElementById("logFollow").checked) document.getElementById("logs").scrollTop = document.getElementById("logs").scrollHeight;
|
|
@@ -1441,7 +1567,7 @@
|
|
|
1441
1567
|
const d = await api("/api/adapters/health");
|
|
1442
1568
|
document.getElementById("adapterHealth").innerHTML = (d.adapters || []).map((a) => '<div class="item"><strong>' + esc(a.label) + ' <span class="adapter-status ' + esc(a.status) + '">' + esc(a.status) + "</span></strong><small>" + esc("CLI: " + (a.cli.label || "-") + " / path " + (a.cli.path || "-") + " / version " + (a.cli.version || "-")) + "</small><small>" + esc("Auth: " + (a.auth.supported ? a.auth.authenticated ? "authenticated" : "not authenticated" : "not managed") + " " + (a.auth.detail || "")) + "</small><small>" + esc("Version: " + a.version.installed + " / latest " + (a.version.latest || "-") + " / " + a.version.status) + "</small>" + featureMatrix(a.capabilities) + '<div class="row"><button data-auth-status="' + attr(a.id) + '">Auth status</button><button data-auth-login="' + attr(a.id) + '" class="secondary" ' + (!a.capabilities.login ? "disabled" : "") + disabledAttr("auth.manage") + '>Login</button><button data-auth-logout="' + attr(a.id) + '" class="secondary" ' + (!a.capabilities.logout ? "disabled" : "") + disabledAttr("auth.manage") + ">Logout</button></div></div>").join("") || '<div class="item">No adapters.</div>';
|
|
1443
1569
|
document.querySelectorAll("[data-auth-status]").forEach((b) => b.onclick = () => safe(async () => {
|
|
1444
|
-
const r = await api("/api/auth/status
|
|
1570
|
+
const r = await api("/api/auth/status", { query: { agent: b.dataset.authStatus } });
|
|
1445
1571
|
toast(r.agentLabel + ": " + r.detail, { duration: 6e3 });
|
|
1446
1572
|
}));
|
|
1447
1573
|
document.querySelectorAll("[data-auth-login]").forEach((b) => b.onclick = () => safe(async () => {
|
|
@@ -1449,7 +1575,7 @@
|
|
|
1449
1575
|
toast("Permission required: auth.manage");
|
|
1450
1576
|
return;
|
|
1451
1577
|
}
|
|
1452
|
-
const r = await api("/api/auth/login", { method: "POST", body:
|
|
1578
|
+
const r = await api("/api/auth/login", { method: "POST", body: { agentId: b.dataset.authLogin } });
|
|
1453
1579
|
toast(r.result?.message || r.detail, { duration: 8e3 });
|
|
1454
1580
|
loadAdapterHealth();
|
|
1455
1581
|
}));
|
|
@@ -1458,7 +1584,7 @@
|
|
|
1458
1584
|
toast("Permission required: auth.manage");
|
|
1459
1585
|
return;
|
|
1460
1586
|
}
|
|
1461
|
-
const r = await api("/api/auth/logout", { method: "POST", body:
|
|
1587
|
+
const r = await api("/api/auth/logout", { method: "POST", body: { agentId: b.dataset.authLogout } });
|
|
1462
1588
|
toast(r.result?.message || r.detail, { duration: 8e3 });
|
|
1463
1589
|
loadAdapterHealth();
|
|
1464
1590
|
}));
|
|
@@ -1482,7 +1608,10 @@
|
|
|
1482
1608
|
function versionCard(key, v) {
|
|
1483
1609
|
const agentId = versionAgentIds[key];
|
|
1484
1610
|
const running = agentId && state.agentUpdateJobs.some((j) => j.agentId === agentId && j.status === "running");
|
|
1485
|
-
const
|
|
1611
|
+
const operation = v.status === "not-installed" ? "install" : v.status === "outdated" ? "update" : "";
|
|
1612
|
+
const actionLabel = operation === "install" ? "Install" : "Update";
|
|
1613
|
+
const runningLabel = operation === "install" ? "Installing" : "Updating";
|
|
1614
|
+
const button = agentId && operation ? '<button class="secondary mini-button" data-update-agent="' + attr(agentId) + '" data-update-operation="' + attr(operation) + '" ' + (running ? "disabled" : "") + ">" + (running ? runningLabel : actionLabel) + "</button>" : "";
|
|
1486
1615
|
return '<div class="item"><strong>' + esc(v.label) + ' <span class="adapter-status ' + esc(versionStatusClass(v.status)) + '">' + esc(versionStatusLabel(v.status)) + "</span> " + button + "</strong><small>" + esc("Installed: " + (v.installedLabel || "-")) + "</small><small>" + esc("Latest: " + (v.latestVersion || "-")) + "</small>" + (v.detail ? "<small>" + esc(v.detail) + "</small>" : "") + "</div>";
|
|
1487
1616
|
}
|
|
1488
1617
|
async function loadAgentUpdateJobs(showLoading = true) {
|
|
@@ -1516,11 +1645,12 @@
|
|
|
1516
1645
|
}
|
|
1517
1646
|
function updateJobCard(job) {
|
|
1518
1647
|
const command = [job.command].concat(job.args || []).join(" ");
|
|
1648
|
+
const operation = job.operation || "update";
|
|
1519
1649
|
const needs = job.needsInput ? '<small><span class="chip warn">Input may be required</span></small>' : "";
|
|
1520
1650
|
const logDeleted = job.logDeletedAt ? "<small>Log deleted " + esc(fmtDate(job.logDeletedAt)) + "</small>" : "";
|
|
1521
1651
|
const deleteLogButton = job.status !== "running" && !job.logDeletedAt ? '<button class="danger mini-button" data-update-delete-log="' + attr(job.id) + '"' + disabledAttr("updates.run") + ">Delete Log</button>" : "";
|
|
1522
|
-
const input = job.canInput ? '<div class="update-input"><input data-update-input="' + attr(job.id) + '" placeholder="Send response to
|
|
1523
|
-
return '<div class="item"><div class="update-job-header"><strong>' + esc(job.agentLabel) + ' <span class="adapter-status ' + esc(jobStatusClass(job.status)) + '">' + esc(job.status) + '</span></strong><div class="row">' + deleteLogButton + "</div></div><small>" + esc(job.method + " / " + fmtDate(job.startedAt) + (job.finishedAt ? " - " + fmtDate(job.finishedAt) : "")) + "</small><small>" + esc(command) + "</small><small>" + esc(job.error || job.summary || "") + "</small>" + logDeleted + needs + '<pre class="update-log">' + esc(job.outputTail || (job.logDeletedAt ? "(log deleted)" : "(waiting for output)")) + "</pre>" + input + "</div>";
|
|
1652
|
+
const input = job.canInput ? '<div class="update-input"><input data-update-input="' + attr(job.id) + '" placeholder="Send response to ' + operation + ' process"><button data-update-send="' + attr(job.id) + '" class="secondary"' + disabledAttr("updates.run") + '>Send</button><button data-update-cancel="' + attr(job.id) + '" class="danger"' + disabledAttr("updates.run") + ">Cancel</button></div>" : "";
|
|
1653
|
+
return '<div class="item"><div class="update-job-header"><strong>' + esc(job.agentLabel + " " + operation) + ' <span class="adapter-status ' + esc(jobStatusClass(job.status)) + '">' + esc(job.status) + '</span></strong><div class="row">' + deleteLogButton + "</div></div><small>" + esc(job.method + " / " + fmtDate(job.startedAt) + (job.finishedAt ? " - " + fmtDate(job.finishedAt) : "")) + "</small><small>" + esc(command) + "</small><small>" + esc(job.error || job.summary || "") + "</small>" + logDeleted + needs + '<pre class="update-log">' + esc(job.outputTail || (job.logDeletedAt ? "(log deleted)" : "(waiting for output)")) + "</pre>" + input + "</div>";
|
|
1524
1654
|
}
|
|
1525
1655
|
function bindAgentUpdateButtons() {
|
|
1526
1656
|
document.querySelectorAll("[data-update-agent]").forEach((b) => b.onclick = () => safe(async () => {
|
|
@@ -1528,11 +1658,12 @@
|
|
|
1528
1658
|
toast("Permission required: updates.run");
|
|
1529
1659
|
return;
|
|
1530
1660
|
}
|
|
1531
|
-
|
|
1532
|
-
|
|
1661
|
+
const operation = b.dataset.updateOperation || "update";
|
|
1662
|
+
if (confirm("Start " + operation + " for " + b.dataset.updateAgent + "?")) {
|
|
1663
|
+
const r = await api("/api/agent-update", { method: "POST", body: JSON.stringify({ agentId: b.dataset.updateAgent, operation }) });
|
|
1533
1664
|
upsertAgentUpdateJob(r.job);
|
|
1534
1665
|
renderAgentUpdateJobs();
|
|
1535
|
-
toast(r.job.agentLabel + "
|
|
1666
|
+
toast(r.job.agentLabel + " " + (r.job.operation || operation) + " started", { duration: 6e3 });
|
|
1536
1667
|
loadVersion();
|
|
1537
1668
|
}
|
|
1538
1669
|
}));
|
|
@@ -1592,6 +1723,14 @@
|
|
|
1592
1723
|
const data = await api("/api/diagnostics");
|
|
1593
1724
|
document.getElementById("diagnostics").innerHTML = diagnosticsHtml(data);
|
|
1594
1725
|
}
|
|
1726
|
+
const exportDiagnosticsBundleBtn = document.getElementById("exportDiagnosticsBundleBtn");
|
|
1727
|
+
if (exportDiagnosticsBundleBtn) exportDiagnosticsBundleBtn.onclick = () => {
|
|
1728
|
+
if (!can("diagnostics.read")) {
|
|
1729
|
+
toast("Permission required: diagnostics.read");
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
window.open("/api/diagnostics/bundle", "_blank");
|
|
1733
|
+
};
|
|
1595
1734
|
function diagnosticsHtml(d) {
|
|
1596
1735
|
const h = d.health || {};
|
|
1597
1736
|
const s = d.snapshot?.session || {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export function createZipBuffer(entries) {
|
|
2
|
+
const localParts = [];
|
|
3
|
+
const centralParts = [];
|
|
4
|
+
let offset = 0;
|
|
5
|
+
for (const entry of entries) {
|
|
6
|
+
const name = normalizeZipEntryName(entry.name);
|
|
7
|
+
const nameBuffer = Buffer.from(name, "utf8");
|
|
8
|
+
const data = Buffer.isBuffer(entry.data) ? entry.data : Buffer.from(entry.data, "utf8");
|
|
9
|
+
const crc = crc32(data);
|
|
10
|
+
const date = entry.date ?? new Date();
|
|
11
|
+
const { dosTime, dosDate } = dateToDos(date);
|
|
12
|
+
const localHeader = Buffer.alloc(30);
|
|
13
|
+
localHeader.writeUInt32LE(0x04034b50, 0);
|
|
14
|
+
localHeader.writeUInt16LE(20, 4);
|
|
15
|
+
localHeader.writeUInt16LE(0x0800, 6);
|
|
16
|
+
localHeader.writeUInt16LE(0, 8);
|
|
17
|
+
localHeader.writeUInt16LE(dosTime, 10);
|
|
18
|
+
localHeader.writeUInt16LE(dosDate, 12);
|
|
19
|
+
localHeader.writeUInt32LE(crc, 14);
|
|
20
|
+
localHeader.writeUInt32LE(data.byteLength, 18);
|
|
21
|
+
localHeader.writeUInt32LE(data.byteLength, 22);
|
|
22
|
+
localHeader.writeUInt16LE(nameBuffer.byteLength, 26);
|
|
23
|
+
localHeader.writeUInt16LE(0, 28);
|
|
24
|
+
localParts.push(localHeader, nameBuffer, data);
|
|
25
|
+
const centralHeader = Buffer.alloc(46);
|
|
26
|
+
centralHeader.writeUInt32LE(0x02014b50, 0);
|
|
27
|
+
centralHeader.writeUInt16LE(20, 4);
|
|
28
|
+
centralHeader.writeUInt16LE(20, 6);
|
|
29
|
+
centralHeader.writeUInt16LE(0x0800, 8);
|
|
30
|
+
centralHeader.writeUInt16LE(0, 10);
|
|
31
|
+
centralHeader.writeUInt16LE(dosTime, 12);
|
|
32
|
+
centralHeader.writeUInt16LE(dosDate, 14);
|
|
33
|
+
centralHeader.writeUInt32LE(crc, 16);
|
|
34
|
+
centralHeader.writeUInt32LE(data.byteLength, 20);
|
|
35
|
+
centralHeader.writeUInt32LE(data.byteLength, 24);
|
|
36
|
+
centralHeader.writeUInt16LE(nameBuffer.byteLength, 28);
|
|
37
|
+
centralHeader.writeUInt16LE(0, 30);
|
|
38
|
+
centralHeader.writeUInt16LE(0, 32);
|
|
39
|
+
centralHeader.writeUInt16LE(0, 34);
|
|
40
|
+
centralHeader.writeUInt16LE(0, 36);
|
|
41
|
+
centralHeader.writeUInt32LE(0, 38);
|
|
42
|
+
centralHeader.writeUInt32LE(offset, 42);
|
|
43
|
+
centralParts.push(centralHeader, nameBuffer);
|
|
44
|
+
offset += localHeader.byteLength + nameBuffer.byteLength + data.byteLength;
|
|
45
|
+
}
|
|
46
|
+
const centralOffset = offset;
|
|
47
|
+
const centralDirectory = Buffer.concat(centralParts);
|
|
48
|
+
const end = Buffer.alloc(22);
|
|
49
|
+
end.writeUInt32LE(0x06054b50, 0);
|
|
50
|
+
end.writeUInt16LE(0, 4);
|
|
51
|
+
end.writeUInt16LE(0, 6);
|
|
52
|
+
end.writeUInt16LE(entries.length, 8);
|
|
53
|
+
end.writeUInt16LE(entries.length, 10);
|
|
54
|
+
end.writeUInt32LE(centralDirectory.byteLength, 12);
|
|
55
|
+
end.writeUInt32LE(centralOffset, 16);
|
|
56
|
+
end.writeUInt16LE(0, 20);
|
|
57
|
+
return Buffer.concat([...localParts, centralDirectory, end]);
|
|
58
|
+
}
|
|
59
|
+
function normalizeZipEntryName(name) {
|
|
60
|
+
return name.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
61
|
+
}
|
|
62
|
+
function dateToDos(date) {
|
|
63
|
+
const year = Math.max(1980, date.getFullYear());
|
|
64
|
+
return {
|
|
65
|
+
dosTime: (date.getHours() << 11) | (date.getMinutes() << 5) | Math.floor(date.getSeconds() / 2),
|
|
66
|
+
dosDate: ((year - 1980) << 9) | ((date.getMonth() + 1) << 5) | date.getDate(),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const CRC32_TABLE = new Uint32Array(256);
|
|
70
|
+
for (let index = 0; index < CRC32_TABLE.length; index += 1) {
|
|
71
|
+
let value = index;
|
|
72
|
+
for (let bit = 0; bit < 8; bit += 1) {
|
|
73
|
+
value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
|
|
74
|
+
}
|
|
75
|
+
CRC32_TABLE[index] = value >>> 0;
|
|
76
|
+
}
|
|
77
|
+
function crc32(data) {
|
|
78
|
+
let crc = 0xffffffff;
|
|
79
|
+
for (const byte of data) {
|
|
80
|
+
crc = CRC32_TABLE[(crc ^ byte) & 0xff] ^ (crc >>> 8);
|
|
81
|
+
}
|
|
82
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
83
|
+
}
|