agent-relay-server 0.6.1 → 0.7.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/package.json +3 -1
- package/public/dashboard/actions.js +819 -0
- package/public/dashboard/api.js +336 -0
- package/public/dashboard/app.js +34 -0
- package/public/dashboard/charts.js +128 -0
- package/public/dashboard/computed.js +693 -0
- package/public/dashboard/constants.js +28 -0
- package/public/dashboard/display.js +345 -0
- package/public/dashboard/state.js +129 -0
- package/public/dashboard/utils.js +207 -0
- package/public/index.html +48 -36
- package/scripts/orchestrator-spawn-smoke.ts +140 -0
- package/src/cli.ts +5 -4
- package/src/config.ts +1 -0
- package/src/db.ts +52 -4
- package/src/routes.ts +74 -48
- package/src/types.ts +16 -0
- package/src/upgrade.ts +80 -7
- package/public/dashboard.js +0 -3032
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const PREF_PREFIX = "ar-";
|
|
2
|
+
export const HUMAN_AGENT_ID = "user";
|
|
3
|
+
export const INBOX_OPERATOR_ID = HUMAN_AGENT_ID;
|
|
4
|
+
export const DEFAULT_COMPOSE = { from: "", to: "", body: "", channel: "", subject: "", claimable: false };
|
|
5
|
+
export const DEFAULT_INBOX_COMPOSE = { toMode: "agent", to: "", body: "", channel: "", subject: "", claimable: false };
|
|
6
|
+
export const DEFAULT_PAIR_MESSAGE = { pairId: "", from: "", body: "", subject: "" };
|
|
7
|
+
export const DEFAULT_PAIR_INVITE = { requesterId: "", targetId: "", objective: "" };
|
|
8
|
+
export const DEFAULT_AGENT_SPAWN = { provider: "codex", approvalMode: "guarded", cwd: "", label: "" };
|
|
9
|
+
export const CLOSED_TASK_STATUSES = new Set(["done", "failed", "canceled"]);
|
|
10
|
+
export const WAITING_TASK_STATUSES = new Set(["open", "blocked"]);
|
|
11
|
+
export const STATUS_SORT_ORDER = { online: 0, idle: 1, busy: 2, offline: 3 };
|
|
12
|
+
export const LIVE_REFRESH_MS = 5_000;
|
|
13
|
+
export const AGENT_TYPE_ICONS = {
|
|
14
|
+
codex: "ti-terminal-2",
|
|
15
|
+
claude: "claude-sol",
|
|
16
|
+
user: "ti-user",
|
|
17
|
+
system: "ti-server",
|
|
18
|
+
channel: "ti-messages",
|
|
19
|
+
agent: "ti-robot",
|
|
20
|
+
};
|
|
21
|
+
export const AGENT_TYPE_TITLES = {
|
|
22
|
+
codex: "Codex agent",
|
|
23
|
+
claude: "Claude agent",
|
|
24
|
+
user: "Human operator",
|
|
25
|
+
system: "System",
|
|
26
|
+
channel: "Channel",
|
|
27
|
+
agent: "Agent",
|
|
28
|
+
};
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { HUMAN_AGENT_ID } from "./constants.js";
|
|
2
|
+
import {
|
|
3
|
+
isBuiltInAgent, isChannelAgent, agentType, agentTypeIcon, agentTypeTitle,
|
|
4
|
+
isAgentStale, agentSupportsControlActions, messageBody, emptyAttention,
|
|
5
|
+
isClaimableTaskWaiting, isClaimableMessageWaiting, targetMatchesAgent,
|
|
6
|
+
} from "./utils.js";
|
|
7
|
+
|
|
8
|
+
function displayName(agent) {
|
|
9
|
+
if (!agent) return "?";
|
|
10
|
+
return agent.label || agent.name || agent.id.slice(-12);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function displayTarget(target) {
|
|
14
|
+
if (!target) return "?";
|
|
15
|
+
if (target === "broadcast") return "broadcast";
|
|
16
|
+
if (target.startsWith("tag:")) return "#" + target.slice(4);
|
|
17
|
+
if (target.startsWith("cap:")) return target.slice(4);
|
|
18
|
+
if (target.startsWith("label:")) return target.slice(6);
|
|
19
|
+
const agent = this.agentsById[target];
|
|
20
|
+
return agent ? this.displayName(agent) : target.slice(-8);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function conversationTitle(thread) {
|
|
24
|
+
if (!thread) return "Inbox";
|
|
25
|
+
return this.displayTarget(thread.peer);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function messagePreview(msg) {
|
|
29
|
+
const text = msg?.subject || messageBody(msg) || "";
|
|
30
|
+
return text.length > 90 ? text.slice(0, 90) + "..." : text;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function agentPair(agent) {
|
|
34
|
+
return agent ? this.pairsByAgentId[agent.id] : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function pairPeerId(pair, agentId) {
|
|
38
|
+
if (!pair) return "";
|
|
39
|
+
return pair.requesterId === agentId ? pair.targetId : pair.requesterId;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function pairBadgeClass(pair) {
|
|
43
|
+
if (pair?.status === "active") return "bg-success-lt";
|
|
44
|
+
if (pair?.status === "pending") return "bg-warning-lt";
|
|
45
|
+
return "bg-secondary-lt";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function pairStatusClass(pair) {
|
|
49
|
+
if (pair?.status === "active") return "bg-success";
|
|
50
|
+
if (pair?.status === "pending") return "bg-warning";
|
|
51
|
+
if (pair?.status === "rejected" || pair?.status === "expired") return "bg-danger";
|
|
52
|
+
return "bg-secondary";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function pairBadgeLabel(pair, agentId) {
|
|
56
|
+
if (!pair) return "";
|
|
57
|
+
const peer = this.displayTarget(pairPeerId(pair, agentId));
|
|
58
|
+
if (pair.status === "active") return "paired with " + peer;
|
|
59
|
+
if (pair.status === "pending" && pair.requesterId === agentId) return "invite to " + peer;
|
|
60
|
+
if (pair.status === "pending") return "invite from " + peer;
|
|
61
|
+
return pair.status;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function pairTitle(pair, agentId) {
|
|
65
|
+
if (!pair) return "";
|
|
66
|
+
const label = pairBadgeLabel.call(this, pair, agentId);
|
|
67
|
+
const objective = pair.objective ? " - " + pair.objective : "";
|
|
68
|
+
return `${label} (${pair.id})${objective}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function agentAttention(agent) {
|
|
72
|
+
if (!agent) return emptyAttention();
|
|
73
|
+
const thread = this.allInboxThreads.find((item) => item.peer === agent.id && !item.archived);
|
|
74
|
+
const pair = this.agentPair(agent);
|
|
75
|
+
const pendingPairInvite = pair?.status === "pending";
|
|
76
|
+
const claimableTasks = countAgentClaimableWaiting(this, agent);
|
|
77
|
+
const attention = {
|
|
78
|
+
unread: thread?.attention?.unread || 0,
|
|
79
|
+
needsHumanResponse: false,
|
|
80
|
+
agentQuestion: Boolean(thread?.attention?.agentQuestion),
|
|
81
|
+
pendingPairInvite,
|
|
82
|
+
claimableTasks,
|
|
83
|
+
};
|
|
84
|
+
attention.total = attention.unread +
|
|
85
|
+
(attention.agentQuestion ? 1 : 0) +
|
|
86
|
+
(attention.pendingPairInvite ? 1 : 0) +
|
|
87
|
+
attention.claimableTasks;
|
|
88
|
+
attention.score = attention.unread * 10 +
|
|
89
|
+
(attention.agentQuestion ? 3 : 0) +
|
|
90
|
+
(attention.pendingPairInvite ? 4 : 0) +
|
|
91
|
+
attention.claimableTasks * 2;
|
|
92
|
+
return attention;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function countAgentClaimableWaiting(vm, agent) {
|
|
96
|
+
const taskCount = vm.tasks.filter((task) => isClaimableTaskWaiting(task) && targetMatchesAgent(task.target, agent)).length;
|
|
97
|
+
const messageCount = vm.messages.filter((msg) => isClaimableMessageWaiting(msg) && targetMatchesAgent(msg.to, agent)).length;
|
|
98
|
+
return taskCount + messageCount;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function agentAttentionTitle(agent) {
|
|
102
|
+
const attention = agentAttention.call(this, agent);
|
|
103
|
+
const parts = [];
|
|
104
|
+
if (attention.unread) parts.push(`${attention.unread} unread`);
|
|
105
|
+
if (attention.agentQuestion) parts.push("agent asked a question");
|
|
106
|
+
if (attention.pendingPairInvite) parts.push("pair invite pending");
|
|
107
|
+
if (attention.claimableTasks) parts.push(`${attention.claimableTasks} claimable waiting`);
|
|
108
|
+
return parts.join(", ");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function presenceBadges(attention, pair) {
|
|
112
|
+
const badges = [];
|
|
113
|
+
if (pair?.status === "active") badges.push({ label: "paired", className: "bg-success-lt" });
|
|
114
|
+
if (pair?.status === "pending") badges.push({ label: "pair invite pending", className: "bg-warning-lt" });
|
|
115
|
+
if (attention.unread) badges.push({ label: attention.unread + " unread", className: "bg-danger-lt" });
|
|
116
|
+
if (attention.agentQuestion) badges.push({ label: "question", className: "bg-info-lt" });
|
|
117
|
+
if (attention.claimableTasks) badges.push({ label: attention.claimableTasks + " claimable", className: "bg-orange-lt" });
|
|
118
|
+
return badges;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function agentPresence(agent) {
|
|
122
|
+
const attention = agentAttention.call(this, agent);
|
|
123
|
+
const pair = this.agentPair(agent);
|
|
124
|
+
const stale = isAgentStale(this, agent);
|
|
125
|
+
const reconnecting = agent?.status !== "offline" && !agent?.ready && stale;
|
|
126
|
+
const starting = agent?.status !== "offline" && !agent?.ready && !stale;
|
|
127
|
+
const unreadIdle = attention.unread > 0 && agent?.status !== "busy";
|
|
128
|
+
|
|
129
|
+
if (agent?.status === "offline") {
|
|
130
|
+
return { label: "offline", tone: "secondary", icon: "ti-plug-off", stale: false, reconnecting: false, badges: presenceBadges(attention, pair) };
|
|
131
|
+
}
|
|
132
|
+
if (reconnecting) {
|
|
133
|
+
return { label: "reconnecting", tone: "danger", icon: "ti-refresh", stale, reconnecting, badges: presenceBadges(attention, pair) };
|
|
134
|
+
}
|
|
135
|
+
if (starting) {
|
|
136
|
+
return { label: "online, not ready", tone: "warning", icon: "ti-loader", stale, reconnecting, badges: presenceBadges(attention, pair) };
|
|
137
|
+
}
|
|
138
|
+
if (agent?.status === "busy") {
|
|
139
|
+
return { label: "busy in turn", tone: "warning", icon: "ti-player-play", stale, reconnecting, badges: presenceBadges(attention, pair) };
|
|
140
|
+
}
|
|
141
|
+
if (pair?.status === "active") {
|
|
142
|
+
return { label: "paired", tone: "success", icon: "ti-link", stale, reconnecting, badges: presenceBadges(attention, pair) };
|
|
143
|
+
}
|
|
144
|
+
if (unreadIdle) {
|
|
145
|
+
return { label: "idle, unread", tone: "danger", icon: "ti-bell", stale, reconnecting, badges: presenceBadges(attention, pair) };
|
|
146
|
+
}
|
|
147
|
+
return { label: agent?.status === "idle" ? "idle" : "ready", tone: "success", icon: "ti-circle-check", stale, reconnecting, badges: presenceBadges(attention, pair) };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function agentPresenceBadges(agent) {
|
|
151
|
+
return agentPresence.call(this, agent).badges;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function agentStatusClass(agent) {
|
|
155
|
+
const presence = agentPresence.call(this, agent);
|
|
156
|
+
return [
|
|
157
|
+
agent?.status || "offline",
|
|
158
|
+
agent?.status !== "offline" && !agent?.ready ? "not-ready" : "",
|
|
159
|
+
presence.stale ? "stale" : "",
|
|
160
|
+
presence.reconnecting ? "reconnecting" : "",
|
|
161
|
+
presence.label === "paired" ? "paired" : "",
|
|
162
|
+
presence.label === "idle, unread" ? "attention" : "",
|
|
163
|
+
].filter(Boolean).join(" ");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function severityClass(severity) {
|
|
167
|
+
if (severity === "critical") return "bg-danger-lt";
|
|
168
|
+
if (severity === "warning") return "bg-warning-lt";
|
|
169
|
+
return "bg-info-lt";
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function integrationPresence(integration) {
|
|
173
|
+
const stats = integration?.taskStats || {};
|
|
174
|
+
if ((stats.waitingTasks || 0) > 0) return { label: "waiting", tone: "warning", icon: "ti-alert-circle" };
|
|
175
|
+
if ((stats.openTasks || 0) > 0) return { label: "active", tone: "info", icon: "ti-activity" };
|
|
176
|
+
if (!integration?.configured) return { label: "observed only", tone: "secondary", icon: "ti-eye" };
|
|
177
|
+
if (integration.observed) return { label: "quiet", tone: "success", icon: "ti-circle-check" };
|
|
178
|
+
return { label: "configured", tone: "primary", icon: "ti-plug-connected" };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function connectorPresence(connector) {
|
|
182
|
+
const runtime = connector?.runtime || {};
|
|
183
|
+
if (runtime.status === "error") return { label: "error", tone: "danger", icon: "ti-alert-triangle" };
|
|
184
|
+
if (runtime.status === "warn") return { label: "warning", tone: "warning", icon: "ti-alert-circle" };
|
|
185
|
+
if (runtime.running) return { label: "running", tone: "success", icon: "ti-circle-check" };
|
|
186
|
+
if (runtime.enabled === false) return { label: "disabled", tone: "secondary", icon: "ti-player-pause" };
|
|
187
|
+
if (runtime.status === "ok") return { label: "ok", tone: "success", icon: "ti-circle-check" };
|
|
188
|
+
return { label: "unknown", tone: "secondary", icon: "ti-help-circle" };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function orchestratorHealthLabel(orch) {
|
|
192
|
+
const health = orch?.health;
|
|
193
|
+
if (!health || health.status === "ok") return orch?.version ? "current" : "unknown version";
|
|
194
|
+
if ((health.issues || []).some((issue) => issue.code === "protocol-mismatch")) return "protocol mismatch";
|
|
195
|
+
if ((health.issues || []).some((issue) => issue.code === "outdated")) return "outdated";
|
|
196
|
+
if (health.restartRequired) return "restart required";
|
|
197
|
+
return health.status;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function orchestratorHealthClass(orch) {
|
|
201
|
+
const status = orch?.health?.status;
|
|
202
|
+
if (status === "error") return "bg-danger-lt";
|
|
203
|
+
if (status === "warn") return "bg-warning-lt";
|
|
204
|
+
return "bg-success-lt";
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function agentChannels(agent) {
|
|
208
|
+
if (!agent) return [];
|
|
209
|
+
return (this.channels || []).filter((ch) => ch.agentId === agent.id);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function channelPresence(channel) {
|
|
213
|
+
if (!channel) return { label: "unknown", tone: "secondary", icon: "ti-plug-off" };
|
|
214
|
+
if (channel.targetHealth?.status === "error") return { label: "target broken", tone: "danger", icon: "ti-alert-triangle" };
|
|
215
|
+
if (channel.targetHealth?.status === "warning") return { label: "target warning", tone: "warning", icon: "ti-alert-circle" };
|
|
216
|
+
if (channel.status === "offline") return { label: "offline", tone: "secondary", icon: "ti-plug-off" };
|
|
217
|
+
if (!channel.ready) return { label: "not ready", tone: "warning", icon: "ti-loader" };
|
|
218
|
+
if (channel.status === "busy") return { label: "busy", tone: "warning", icon: "ti-activity" };
|
|
219
|
+
return { label: "ready", tone: "success", icon: "ti-circle-check" };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function agentStatusTitle(agent) {
|
|
223
|
+
if (!agent) return "";
|
|
224
|
+
if (agent.status === "offline") return "offline";
|
|
225
|
+
if (isAgentStale(this, agent) && !agent.ready) return "reconnecting";
|
|
226
|
+
if (isAgentStale(this, agent)) return "stale heartbeat";
|
|
227
|
+
if (agent.status === "busy") return "busy in turn";
|
|
228
|
+
if (this.agentPair(agent)?.status === "active") return "paired";
|
|
229
|
+
if (agentAttention.call(this, agent).unread) return "idle but has unread";
|
|
230
|
+
if (agent.ready) return agent.status;
|
|
231
|
+
|
|
232
|
+
const lastSeenMs = new Date(agent.lastSeen).getTime();
|
|
233
|
+
if (!Number.isFinite(lastSeenMs)) return "Trying to reconnect...";
|
|
234
|
+
const ageSec = Math.max(0, ((this.now || Date.now()) - lastSeenMs) / 1000);
|
|
235
|
+
return ageSec <= 45 ? "Starting up..." : "Trying to reconnect...";
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function timeAgo(iso) {
|
|
239
|
+
if (!iso) return "";
|
|
240
|
+
const ts = new Date(iso).getTime();
|
|
241
|
+
if (!Number.isFinite(ts)) return "";
|
|
242
|
+
const diff = Math.max(0, ((this.now || Date.now()) - ts) / 1000);
|
|
243
|
+
if (diff < 60) return Math.floor(diff) + "s ago";
|
|
244
|
+
if (diff < 3600) return Math.floor(diff / 60) + "m ago";
|
|
245
|
+
if (diff < 86400) return Math.floor(diff / 3600) + "h ago";
|
|
246
|
+
return Math.floor(diff / 86400) + "d ago";
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function fmtTime(iso) {
|
|
250
|
+
if (!iso) return "";
|
|
251
|
+
return new Date(iso).toLocaleString();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function healthAlertClass(status) {
|
|
255
|
+
if (status === "error") return "alert-danger";
|
|
256
|
+
if (status === "degraded") return "alert-warning";
|
|
257
|
+
return "alert-success";
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function activityKindClass(kind) {
|
|
261
|
+
if (kind === "question") return "bg-info-lt";
|
|
262
|
+
if (kind === "task") return "bg-warning-lt";
|
|
263
|
+
if (kind === "pair") return "bg-success-lt";
|
|
264
|
+
if (kind === "operator") return "bg-primary-lt";
|
|
265
|
+
if (kind === "reply") return "bg-purple-lt";
|
|
266
|
+
return "bg-secondary-lt";
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export { agentAttention, inboxComposeTarget };
|
|
270
|
+
|
|
271
|
+
export function createDisplayMethods() {
|
|
272
|
+
return {
|
|
273
|
+
displayName,
|
|
274
|
+
displayTarget,
|
|
275
|
+
conversationTitle,
|
|
276
|
+
messageBody,
|
|
277
|
+
messagePreview,
|
|
278
|
+
agentPair,
|
|
279
|
+
pairPeerId,
|
|
280
|
+
pairBadgeClass,
|
|
281
|
+
pairStatusClass,
|
|
282
|
+
pairBadgeLabel,
|
|
283
|
+
pairTitle,
|
|
284
|
+
agentAttention,
|
|
285
|
+
agentAttentionTitle,
|
|
286
|
+
agentType,
|
|
287
|
+
agentTypeIcon,
|
|
288
|
+
agentTypeTitle,
|
|
289
|
+
agentPresence,
|
|
290
|
+
agentPresenceBadges,
|
|
291
|
+
agentStatusClass,
|
|
292
|
+
severityClass,
|
|
293
|
+
agentStatusTitle,
|
|
294
|
+
isBuiltInAgent,
|
|
295
|
+
agentChannels,
|
|
296
|
+
channelPresence,
|
|
297
|
+
connectorPresence,
|
|
298
|
+
integrationPresence,
|
|
299
|
+
orchestratorHealthLabel,
|
|
300
|
+
orchestratorHealthClass,
|
|
301
|
+
composeTargetAllowsClaimable,
|
|
302
|
+
inboxComposeTargetAllowsClaimable,
|
|
303
|
+
agentSupportsControlActions,
|
|
304
|
+
timeAgo,
|
|
305
|
+
fmtTime,
|
|
306
|
+
healthAlertClass,
|
|
307
|
+
activityKindClass,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function composeTargetAllowsClaimable() {
|
|
312
|
+
return matchingComposeRecipientCount(this, this.compose.to) > 1;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function inboxComposeTargetAllowsClaimable() {
|
|
316
|
+
return matchingComposeRecipientCount(this, inboxComposeTarget(this)) > 1;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function matchingComposeRecipientCount(vm, target) {
|
|
320
|
+
if (!target) return 0;
|
|
321
|
+
const candidates = vm.composeAgents.filter((agent) => !isBuiltInAgent(agent) && !isChannelAgent(agent));
|
|
322
|
+
if (target === "broadcast") return candidates.length;
|
|
323
|
+
if (vm.agentsById[target]) return 1;
|
|
324
|
+
if (target.startsWith("tag:")) {
|
|
325
|
+
const tag = target.slice(4);
|
|
326
|
+
return candidates.filter((agent) => (agent.tags || []).includes(tag)).length;
|
|
327
|
+
}
|
|
328
|
+
if (target.startsWith("cap:")) {
|
|
329
|
+
const cap = target.slice(4);
|
|
330
|
+
return candidates.filter((agent) => (agent.capabilities || []).includes(cap)).length;
|
|
331
|
+
}
|
|
332
|
+
if (target.startsWith("label:")) {
|
|
333
|
+
const label = target.slice(6);
|
|
334
|
+
return candidates.filter((agent) => agent.label === label).length;
|
|
335
|
+
}
|
|
336
|
+
return 1;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function inboxComposeTarget(vm) {
|
|
340
|
+
const target = vm.inboxCompose.to;
|
|
341
|
+
if (!target) return "";
|
|
342
|
+
if (vm.inboxCompose.toMode === "tag") return "tag:" + target;
|
|
343
|
+
if (vm.inboxCompose.toMode === "cap") return "cap:" + target;
|
|
344
|
+
return target;
|
|
345
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { PREF_PREFIX } from "./constants.js";
|
|
2
|
+
|
|
3
|
+
export function loadPref(key, fallback) {
|
|
4
|
+
try {
|
|
5
|
+
const value = localStorage.getItem(PREF_PREFIX + key);
|
|
6
|
+
return value !== null ? JSON.parse(value) : fallback;
|
|
7
|
+
} catch {
|
|
8
|
+
return fallback;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function savePref(key, value) {
|
|
13
|
+
localStorage.setItem(PREF_PREFIX + key, JSON.stringify(value));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function initialState() {
|
|
17
|
+
return {
|
|
18
|
+
view: loadPref("view", "overview"),
|
|
19
|
+
|
|
20
|
+
showOffline: loadPref("showOffline", false),
|
|
21
|
+
showBuiltIns: loadPref("showBuiltIns", false),
|
|
22
|
+
autoRefresh: loadPref("autoRefresh", true),
|
|
23
|
+
agentSort: loadPref("agentSort", "status"),
|
|
24
|
+
agentSortDir: loadPref("agentSortDir", "asc"),
|
|
25
|
+
agentPresetFilter: loadPref("agentPresetFilter", ""),
|
|
26
|
+
|
|
27
|
+
agents: [],
|
|
28
|
+
agentsById: {},
|
|
29
|
+
orchestrators: [],
|
|
30
|
+
pairs: [],
|
|
31
|
+
messages: [],
|
|
32
|
+
tasks: [],
|
|
33
|
+
integrations: [],
|
|
34
|
+
channels: [],
|
|
35
|
+
connectors: [],
|
|
36
|
+
taskEvents: [],
|
|
37
|
+
taskEventCache: {},
|
|
38
|
+
stats: {},
|
|
39
|
+
health: null,
|
|
40
|
+
now: Date.now(),
|
|
41
|
+
authToken: loadPref("authToken", ""),
|
|
42
|
+
inboxReadCursors: loadPref("inboxReadCursors", {}),
|
|
43
|
+
inboxArchivedThreads: loadPref("inboxArchivedThreads", {}),
|
|
44
|
+
inboxDrafts: loadPref("inboxDrafts", {}),
|
|
45
|
+
inboxSearch: "",
|
|
46
|
+
inboxSort: loadPref("inboxSort", "attention"),
|
|
47
|
+
inboxSortDir: loadPref("inboxSortDir", "desc"),
|
|
48
|
+
inboxShowArchived: loadPref("inboxShowArchived", false),
|
|
49
|
+
operatorActivity: loadPref("operatorActivity", []),
|
|
50
|
+
activityEvents: [],
|
|
51
|
+
activityFilter: loadPref("activityFilter", ""),
|
|
52
|
+
|
|
53
|
+
selectedAgent: "",
|
|
54
|
+
agentDetailOpen: false,
|
|
55
|
+
agentDetailId: "",
|
|
56
|
+
channelDetailOpen: false,
|
|
57
|
+
channelDetailId: "",
|
|
58
|
+
selectedInboxThread: "",
|
|
59
|
+
replyTo: null,
|
|
60
|
+
composeOpen: false,
|
|
61
|
+
agentSpawnOpen: false,
|
|
62
|
+
orchestratorSpawnOpen: false,
|
|
63
|
+
spawnOrchId: "",
|
|
64
|
+
spawnProvider: "claude",
|
|
65
|
+
spawnCwd: "",
|
|
66
|
+
spawnLabel: "",
|
|
67
|
+
spawnApproval: "guarded",
|
|
68
|
+
spawnPrompt: "",
|
|
69
|
+
spawnDirListing: null,
|
|
70
|
+
agentDirectoryBrowser: { open: false, loading: false, path: "", parent: "", home: "", cwd: "", entries: [], error: "" },
|
|
71
|
+
pairInviteOpen: false,
|
|
72
|
+
pairMessageOpen: false,
|
|
73
|
+
threadOpen: false,
|
|
74
|
+
threadMessages: [],
|
|
75
|
+
taskEventsOpen: false,
|
|
76
|
+
commandPaletteOpen: false,
|
|
77
|
+
commandQuery: "",
|
|
78
|
+
connected: false,
|
|
79
|
+
authNeeded: false,
|
|
80
|
+
|
|
81
|
+
compose: { from: "", to: "", body: "", channel: "", subject: "", claimable: false },
|
|
82
|
+
agentSpawn: { provider: "codex", approvalMode: "guarded", cwd: "", label: "" },
|
|
83
|
+
pairInvite: { requesterId: "", targetId: "", objective: "" },
|
|
84
|
+
pairMessage: { pairId: "", from: "", body: "", subject: "" },
|
|
85
|
+
inboxCompose: { toMode: "agent", to: "", body: "", channel: "", subject: "", claimable: false },
|
|
86
|
+
|
|
87
|
+
confirmModal: { show: false, title: "", message: "", action: null },
|
|
88
|
+
renameModal: { show: false, agentId: "", label: "" },
|
|
89
|
+
|
|
90
|
+
channelFilter: "",
|
|
91
|
+
tagFilter: "",
|
|
92
|
+
agentStatusFilter: loadPref("agentStatusFilter", ""),
|
|
93
|
+
agentTagFilter: loadPref("agentTagFilter", ""),
|
|
94
|
+
pairStatusFilter: loadPref("pairStatusFilter", "open"),
|
|
95
|
+
taskStatusFilter: "",
|
|
96
|
+
taskSourceFilter: "",
|
|
97
|
+
|
|
98
|
+
chartInstances: {},
|
|
99
|
+
_es: null,
|
|
100
|
+
_clockTimer: null,
|
|
101
|
+
_refreshTimer: null,
|
|
102
|
+
_refreshInFlight: false,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function watchPersistedPrefs(vm) {
|
|
107
|
+
vm.$watch("showOffline", (value) => vm.save("showOffline", value));
|
|
108
|
+
vm.$watch("showBuiltIns", (value) => vm.save("showBuiltIns", value));
|
|
109
|
+
vm.$watch("autoRefresh", (value) => {
|
|
110
|
+
vm.save("autoRefresh", value);
|
|
111
|
+
if (value) vm.startAutoRefresh();
|
|
112
|
+
else vm.stopAutoRefresh();
|
|
113
|
+
});
|
|
114
|
+
vm.$watch("agentSort", (value) => vm.save("agentSort", value));
|
|
115
|
+
vm.$watch("agentSortDir", (value) => vm.save("agentSortDir", value));
|
|
116
|
+
vm.$watch("agentPresetFilter", (value) => vm.save("agentPresetFilter", value));
|
|
117
|
+
vm.$watch("agentStatusFilter", (value) => vm.save("agentStatusFilter", value));
|
|
118
|
+
vm.$watch("agentTagFilter", (value) => vm.save("agentTagFilter", value));
|
|
119
|
+
vm.$watch("pairStatusFilter", (value) => vm.save("pairStatusFilter", value));
|
|
120
|
+
vm.$watch("inboxSort", (value) => vm.save("inboxSort", value));
|
|
121
|
+
vm.$watch("inboxSortDir", (value) => vm.save("inboxSortDir", value));
|
|
122
|
+
vm.$watch("inboxShowArchived", (value) => vm.save("inboxShowArchived", value));
|
|
123
|
+
vm.$watch("activityFilter", (value) => vm.save("activityFilter", value));
|
|
124
|
+
vm.$watch("view", (value, oldValue) => {
|
|
125
|
+
vm.save("view", value);
|
|
126
|
+
if (oldValue === "analytics") vm.destroyAllCharts();
|
|
127
|
+
if (value === "analytics") vm.$nextTick(() => vm.renderCharts());
|
|
128
|
+
});
|
|
129
|
+
}
|