agent-relay-server 0.4.20 → 0.4.22
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/public/dashboard.js +50 -4
- package/public/index.html +34 -1
- package/src/security.ts +13 -2
package/package.json
CHANGED
package/public/dashboard.js
CHANGED
|
@@ -4,6 +4,16 @@
|
|
|
4
4
|
const CLOSED_TASK_STATUSES = new Set(["done", "failed", "canceled"]);
|
|
5
5
|
const STATUS_SORT_ORDER = { online: 0, idle: 1, busy: 2, offline: 3 };
|
|
6
6
|
const LIVE_REFRESH_MS = 5_000;
|
|
7
|
+
const AGENT_TYPE_ICONS = {
|
|
8
|
+
codex: "ti-terminal-2",
|
|
9
|
+
claude: "ti-sparkles",
|
|
10
|
+
agent: "ti-robot",
|
|
11
|
+
};
|
|
12
|
+
const AGENT_TYPE_TITLES = {
|
|
13
|
+
codex: "Codex agent",
|
|
14
|
+
claude: "Claude agent",
|
|
15
|
+
agent: "Agent",
|
|
16
|
+
};
|
|
7
17
|
|
|
8
18
|
function loadPref(key, fallback) {
|
|
9
19
|
try {
|
|
@@ -95,7 +105,7 @@
|
|
|
95
105
|
|
|
96
106
|
function upsertById(list, item) {
|
|
97
107
|
const idx = list.findIndex((existing) => existing.id === item.id);
|
|
98
|
-
if (idx >= 0) list
|
|
108
|
+
if (idx >= 0) list.splice(idx, 1, item);
|
|
99
109
|
else list.push(item);
|
|
100
110
|
}
|
|
101
111
|
|
|
@@ -126,11 +136,18 @@
|
|
|
126
136
|
function createLifecycleMethods() {
|
|
127
137
|
return {
|
|
128
138
|
async init() {
|
|
139
|
+
this.startClock();
|
|
140
|
+
watchPersistedPrefs(this);
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
this.stats = await this.api("GET", "/stats");
|
|
144
|
+
} catch {
|
|
145
|
+
if (this.authNeeded) return;
|
|
146
|
+
}
|
|
147
|
+
|
|
129
148
|
await this.refresh();
|
|
130
149
|
this.connectSSE();
|
|
131
|
-
this.startClock();
|
|
132
150
|
this.startAutoRefresh();
|
|
133
|
-
watchPersistedPrefs(this);
|
|
134
151
|
},
|
|
135
152
|
|
|
136
153
|
save(key, value) {
|
|
@@ -446,6 +463,9 @@
|
|
|
446
463
|
return {
|
|
447
464
|
displayName,
|
|
448
465
|
displayTarget,
|
|
466
|
+
agentType,
|
|
467
|
+
agentTypeIcon,
|
|
468
|
+
agentTypeTitle,
|
|
449
469
|
severityClass,
|
|
450
470
|
agentStatusTitle,
|
|
451
471
|
timeAgo,
|
|
@@ -470,6 +490,32 @@
|
|
|
470
490
|
return agent ? this.displayName(agent) : target.slice(-8);
|
|
471
491
|
}
|
|
472
492
|
|
|
493
|
+
function agentType(agent) {
|
|
494
|
+
const values = [
|
|
495
|
+
...(agent?.tags || []),
|
|
496
|
+
agent?.meta?.provider,
|
|
497
|
+
agent?.meta?.client,
|
|
498
|
+
agent?.meta?.runtime,
|
|
499
|
+
agent?.meta?.agentType,
|
|
500
|
+
agent?.id,
|
|
501
|
+
agent?.name,
|
|
502
|
+
]
|
|
503
|
+
.filter((value) => typeof value === "string")
|
|
504
|
+
.map((value) => value.toLowerCase());
|
|
505
|
+
|
|
506
|
+
if (values.some((value) => value.includes("codex"))) return "codex";
|
|
507
|
+
if (values.some((value) => value.includes("claude"))) return "claude";
|
|
508
|
+
return "agent";
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function agentTypeIcon(agent) {
|
|
512
|
+
return AGENT_TYPE_ICONS[agentType(agent)] || AGENT_TYPE_ICONS.agent;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function agentTypeTitle(agent) {
|
|
516
|
+
return AGENT_TYPE_TITLES[agentType(agent)] || AGENT_TYPE_TITLES.agent;
|
|
517
|
+
}
|
|
518
|
+
|
|
473
519
|
function severityClass(severity) {
|
|
474
520
|
if (severity === "critical") return "bg-danger-lt";
|
|
475
521
|
if (severity === "warning") return "bg-warning-lt";
|
|
@@ -789,7 +835,7 @@
|
|
|
789
835
|
|
|
790
836
|
window.AgentRelayDashboard = {
|
|
791
837
|
createRelayDashboard,
|
|
792
|
-
helpers: { loadPref, savePref, indexAgents, upsertById, upsertTask, compareAgents },
|
|
838
|
+
helpers: { loadPref, savePref, indexAgents, upsertById, upsertTask, compareAgents, agentType },
|
|
793
839
|
};
|
|
794
840
|
window.relay = createRelayDashboard;
|
|
795
841
|
|
package/public/index.html
CHANGED
|
@@ -48,6 +48,33 @@
|
|
|
48
48
|
.agent-card:hover .agent-actions { opacity: 1; }
|
|
49
49
|
|
|
50
50
|
.agent-label { color: var(--tblr-warning); font-weight: 600; }
|
|
51
|
+
.agent-type-icon {
|
|
52
|
+
width: 20px;
|
|
53
|
+
height: 20px;
|
|
54
|
+
border-radius: 6px;
|
|
55
|
+
display: inline-flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
justify-content: center;
|
|
58
|
+
flex: 0 0 20px;
|
|
59
|
+
border: 1px solid transparent;
|
|
60
|
+
font-size: 14px;
|
|
61
|
+
line-height: 1;
|
|
62
|
+
}
|
|
63
|
+
.agent-type-icon.codex {
|
|
64
|
+
color: var(--tblr-info);
|
|
65
|
+
background: rgba(var(--tblr-info-rgb), 0.12);
|
|
66
|
+
border-color: rgba(var(--tblr-info-rgb), 0.25);
|
|
67
|
+
}
|
|
68
|
+
.agent-type-icon.claude {
|
|
69
|
+
color: var(--tblr-warning);
|
|
70
|
+
background: rgba(var(--tblr-warning-rgb), 0.12);
|
|
71
|
+
border-color: rgba(var(--tblr-warning-rgb), 0.25);
|
|
72
|
+
}
|
|
73
|
+
.agent-type-icon.agent {
|
|
74
|
+
color: var(--tblr-secondary);
|
|
75
|
+
background: var(--tblr-bg-surface-secondary);
|
|
76
|
+
border-color: var(--tblr-border-color);
|
|
77
|
+
}
|
|
51
78
|
|
|
52
79
|
.msg-card { border-left: 3px solid transparent; }
|
|
53
80
|
.msg-card.claimed { border-left-color: var(--tblr-success); }
|
|
@@ -102,7 +129,7 @@
|
|
|
102
129
|
<div class="ar-sidebar-footer">
|
|
103
130
|
<div class="d-flex align-items-center gap-2 mb-2">
|
|
104
131
|
<span class="status-dot" :class="connected ? 'online' : 'offline'"></span>
|
|
105
|
-
<span class="small" x-text="connected ? 'Live' : 'Reconnecting…'"></span>
|
|
132
|
+
<span class="small" x-text="authNeeded ? 'Auth required' : connected ? 'Live' : 'Reconnecting…'"></span>
|
|
106
133
|
</div>
|
|
107
134
|
<div class="d-flex align-items-center gap-2 mb-2">
|
|
108
135
|
<label class="form-check form-switch mb-0">
|
|
@@ -238,6 +265,9 @@
|
|
|
238
265
|
<template x-for="a in sortedAgents.slice(0, 20)" :key="a.id">
|
|
239
266
|
<div class="list-group-item d-flex align-items-center gap-2" style="cursor:pointer" @click="selectedAgent = a.id; switchView('messages')">
|
|
240
267
|
<span class="status-dot" :class="[a.status, a.status !== 'offline' && !a.ready ? 'not-ready' : '']"></span>
|
|
268
|
+
<span class="agent-type-icon" :class="agentType(a)" :title="agentTypeTitle(a)" :aria-label="agentTypeTitle(a)">
|
|
269
|
+
<i class="ti" :class="agentTypeIcon(a)"></i>
|
|
270
|
+
</span>
|
|
241
271
|
<div class="flex-grow-1 min-width-0">
|
|
242
272
|
<div class="text-truncate">
|
|
243
273
|
<template x-if="a.label">
|
|
@@ -339,6 +369,9 @@
|
|
|
339
369
|
<div class="card-body">
|
|
340
370
|
<div class="d-flex align-items-start gap-2">
|
|
341
371
|
<span class="status-dot mt-1" :class="[a.status, a.status !== 'offline' && !a.ready ? 'not-ready' : '']" :title="agentStatusTitle(a)"></span>
|
|
372
|
+
<span class="agent-type-icon mt-0" :class="agentType(a)" :title="agentTypeTitle(a)" :aria-label="agentTypeTitle(a)">
|
|
373
|
+
<i class="ti" :class="agentTypeIcon(a)"></i>
|
|
374
|
+
</span>
|
|
342
375
|
<div class="flex-grow-1 min-width-0">
|
|
343
376
|
<div class="d-flex align-items-center gap-2">
|
|
344
377
|
<template x-if="a.label">
|
package/src/security.ts
CHANGED
|
@@ -19,10 +19,21 @@ export function isOriginAllowed(req: Request): boolean {
|
|
|
19
19
|
if (!origin) return true;
|
|
20
20
|
const origins = corsOrigins();
|
|
21
21
|
if (origins.includes("*")) return true;
|
|
22
|
+
if (origins.includes(origin)) return true;
|
|
22
23
|
|
|
23
24
|
const url = new URL(req.url);
|
|
24
25
|
const sameOrigin = `${url.protocol}//${url.host}`;
|
|
25
|
-
|
|
26
|
+
if (origin === sameOrigin) return true;
|
|
27
|
+
|
|
28
|
+
// Behind a reverse proxy, req.url is the backend address (e.g. http://127.0.0.1:4850)
|
|
29
|
+
// while the browser sends the public origin. Check forwarded headers.
|
|
30
|
+
try {
|
|
31
|
+
const originHost = new URL(origin).host;
|
|
32
|
+
const fwdHost = req.headers.get("x-forwarded-host");
|
|
33
|
+
if (fwdHost && originHost === fwdHost) return true;
|
|
34
|
+
} catch {}
|
|
35
|
+
|
|
36
|
+
return false;
|
|
26
37
|
}
|
|
27
38
|
|
|
28
39
|
export function applyCors(req: Request, response: Response): Response {
|
|
@@ -117,7 +128,7 @@ export function unauthorized(req: Request): Response {
|
|
|
117
128
|
}
|
|
118
129
|
|
|
119
130
|
function authToken(): string {
|
|
120
|
-
return process.env.AGENT_RELAY_TOKEN
|
|
131
|
+
return process.env.AGENT_RELAY_TOKEN ?? AUTH_TOKEN;
|
|
121
132
|
}
|
|
122
133
|
|
|
123
134
|
function extractToken(req: Request): string | null {
|