agent-relay-server 0.4.38 → 0.5.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 +1 -1
- package/public/dashboard.js +338 -14
- package/public/index.html +446 -23
- package/src/agent-spawn.ts +137 -0
- package/src/db.ts +171 -1
- package/src/routes.ts +380 -2
- package/src/sse.ts +15 -1
- package/src/types.ts +60 -0
package/package.json
CHANGED
package/public/dashboard.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
const DEFAULT_INBOX_COMPOSE = { toMode: "agent", to: "", body: "", channel: "", subject: "", claimable: false };
|
|
7
7
|
const DEFAULT_PAIR_MESSAGE = { pairId: "", from: "", body: "", subject: "" };
|
|
8
8
|
const DEFAULT_PAIR_INVITE = { requesterId: "", targetId: "", objective: "" };
|
|
9
|
+
const DEFAULT_AGENT_SPAWN = { provider: "codex", approvalMode: "guarded", cwd: "", label: "" };
|
|
9
10
|
const CLOSED_TASK_STATUSES = new Set(["done", "failed", "canceled"]);
|
|
10
11
|
const WAITING_TASK_STATUSES = new Set(["open", "blocked"]);
|
|
11
12
|
const STATUS_SORT_ORDER = { online: 0, idle: 1, busy: 2, offline: 3 };
|
|
@@ -15,6 +16,7 @@
|
|
|
15
16
|
claude: "claude-sol",
|
|
16
17
|
user: "ti-user",
|
|
17
18
|
system: "ti-server",
|
|
19
|
+
channel: "ti-messages",
|
|
18
20
|
agent: "ti-robot",
|
|
19
21
|
};
|
|
20
22
|
const AGENT_TYPE_TITLES = {
|
|
@@ -22,6 +24,7 @@
|
|
|
22
24
|
claude: "Claude agent",
|
|
23
25
|
user: "Human operator",
|
|
24
26
|
system: "System",
|
|
27
|
+
channel: "Channel",
|
|
25
28
|
agent: "Agent",
|
|
26
29
|
};
|
|
27
30
|
|
|
@@ -51,6 +54,7 @@
|
|
|
51
54
|
|
|
52
55
|
agents: [],
|
|
53
56
|
agentsById: {},
|
|
57
|
+
orchestrators: [],
|
|
54
58
|
pairs: [],
|
|
55
59
|
messages: [],
|
|
56
60
|
tasks: [],
|
|
@@ -76,9 +80,21 @@
|
|
|
76
80
|
selectedAgent: "",
|
|
77
81
|
agentDetailOpen: false,
|
|
78
82
|
agentDetailId: "",
|
|
83
|
+
channelDetailOpen: false,
|
|
84
|
+
channelDetailId: "",
|
|
79
85
|
selectedInboxThread: "",
|
|
80
86
|
replyTo: null,
|
|
81
87
|
composeOpen: false,
|
|
88
|
+
agentSpawnOpen: false,
|
|
89
|
+
orchestratorSpawnOpen: false,
|
|
90
|
+
spawnOrchId: "",
|
|
91
|
+
spawnProvider: "claude",
|
|
92
|
+
spawnCwd: "",
|
|
93
|
+
spawnLabel: "",
|
|
94
|
+
spawnApproval: "guarded",
|
|
95
|
+
spawnPrompt: "",
|
|
96
|
+
spawnDirListing: null,
|
|
97
|
+
agentDirectoryBrowser: { open: false, loading: false, path: "", parent: "", home: "", cwd: "", entries: [], error: "" },
|
|
82
98
|
pairInviteOpen: false,
|
|
83
99
|
pairMessageOpen: false,
|
|
84
100
|
threadOpen: false,
|
|
@@ -90,6 +106,7 @@
|
|
|
90
106
|
authNeeded: false,
|
|
91
107
|
|
|
92
108
|
compose: { ...DEFAULT_COMPOSE },
|
|
109
|
+
agentSpawn: { ...DEFAULT_AGENT_SPAWN },
|
|
93
110
|
pairInvite: { ...DEFAULT_PAIR_INVITE },
|
|
94
111
|
pairMessage: { ...DEFAULT_PAIR_MESSAGE },
|
|
95
112
|
inboxCompose: { ...DEFAULT_INBOX_COMPOSE },
|
|
@@ -273,6 +290,8 @@
|
|
|
273
290
|
es.addEventListener("message.claimed", (event) => handleMessageClaimed(this, parseEventData(event)));
|
|
274
291
|
es.addEventListener("message.claim_released", (event) => handleMessageClaimReleased(this, parseEventData(event)));
|
|
275
292
|
es.addEventListener("message.deleted", (event) => handleMessageDeleted(this, parseEventData(event)));
|
|
293
|
+
es.addEventListener("orchestrator.status", (event) => handleOrchestratorStatus(this, parseEventData(event)));
|
|
294
|
+
es.addEventListener("orchestrator.removed", (event) => handleOrchestratorRemoved(this, parseEventData(event)));
|
|
276
295
|
registerTaskEvents(this, es);
|
|
277
296
|
}
|
|
278
297
|
|
|
@@ -312,6 +331,14 @@
|
|
|
312
331
|
refreshChartsIfVisible(vm);
|
|
313
332
|
}
|
|
314
333
|
|
|
334
|
+
function handleOrchestratorStatus(vm, orch) {
|
|
335
|
+
upsertById(vm.orchestrators, orch);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function handleOrchestratorRemoved(vm, data) {
|
|
339
|
+
vm.orchestrators = vm.orchestrators.filter((o) => o.id !== data.id);
|
|
340
|
+
}
|
|
341
|
+
|
|
315
342
|
function handleMessageClaimed(vm, data) {
|
|
316
343
|
const msg = vm.messages.find((item) => item.id === data.messageId);
|
|
317
344
|
if (!msg) return;
|
|
@@ -366,7 +393,7 @@
|
|
|
366
393
|
},
|
|
367
394
|
|
|
368
395
|
async refresh() {
|
|
369
|
-
await Promise.all([this.fetchStats(), this.fetchHealth(), this.fetchAgents(), this.fetchPairs(), this.fetchMessages(), this.fetchTasks(), this.fetchChannels(), this.fetchIntegrations(), this.fetchInboxState(), this.fetchActivityEvents()]);
|
|
396
|
+
await Promise.all([this.fetchStats(), this.fetchHealth(), this.fetchAgents(), this.fetchOrchestrators(), this.fetchPairs(), this.fetchMessages(), this.fetchTasks(), this.fetchChannels(), this.fetchIntegrations(), this.fetchInboxState(), this.fetchActivityEvents()]);
|
|
370
397
|
},
|
|
371
398
|
|
|
372
399
|
async refreshLiveData() {
|
|
@@ -399,6 +426,12 @@
|
|
|
399
426
|
} catch {}
|
|
400
427
|
},
|
|
401
428
|
|
|
429
|
+
async fetchOrchestrators() {
|
|
430
|
+
try {
|
|
431
|
+
this.orchestrators = await this.api("GET", "/orchestrators");
|
|
432
|
+
} catch {}
|
|
433
|
+
},
|
|
434
|
+
|
|
402
435
|
async fetchPairs() {
|
|
403
436
|
try {
|
|
404
437
|
let pairs;
|
|
@@ -496,11 +529,14 @@
|
|
|
496
529
|
function createComputedDescriptors() {
|
|
497
530
|
return {
|
|
498
531
|
onlineCount: { get: getOnlineCount },
|
|
532
|
+
busyAgentCount: { get: getBusyAgentCount },
|
|
499
533
|
hiddenBuiltInAgentCount: { get: getHiddenBuiltInAgentCount },
|
|
500
534
|
sortedAgents: { get: getSortedAgents },
|
|
501
535
|
pairsByAgentId: { get: getPairsByAgentId },
|
|
502
536
|
selectedAgentDetail: { get: getSelectedAgentDetail },
|
|
503
537
|
agentDetailMessages: { get: getAgentDetailMessages },
|
|
538
|
+
selectedChannelDetail: { get: getSelectedChannelDetail },
|
|
539
|
+
channelDetailMessages: { get: getChannelDetailMessages },
|
|
504
540
|
pairMessagePair: { get: getPairMessagePair },
|
|
505
541
|
allInboxThreads: { get: getAllInboxThreads },
|
|
506
542
|
inboxThreads: { get: getInboxThreads },
|
|
@@ -513,6 +549,7 @@
|
|
|
513
549
|
workQueueItems: { get: getWorkQueueItems },
|
|
514
550
|
channelCards: { get: getChannelCards },
|
|
515
551
|
integrationCards: { get: getIntegrationCards },
|
|
552
|
+
openPairCount: { get: getOpenPairCount },
|
|
516
553
|
filteredMessages: { get: getFilteredMessages },
|
|
517
554
|
groupedMessages: { get: getGroupedMessages },
|
|
518
555
|
filteredTasks: { get: getFilteredTasks },
|
|
@@ -523,13 +560,23 @@
|
|
|
523
560
|
healthIssues: { get: getHealthIssues },
|
|
524
561
|
healthDiagnostics: { get: getHealthDiagnostics },
|
|
525
562
|
commandPaletteItems: { get: getCommandPaletteItems },
|
|
563
|
+
spawnAvailableProviders: { get: getSpawnAvailableProviders },
|
|
526
564
|
};
|
|
527
565
|
}
|
|
528
566
|
|
|
567
|
+
function getSpawnAvailableProviders() {
|
|
568
|
+
const orch = this.orchestrators.find((o) => o.id === this.spawnOrchId);
|
|
569
|
+
return orch ? orch.providers : ["claude", "codex"];
|
|
570
|
+
}
|
|
571
|
+
|
|
529
572
|
function getOnlineCount() {
|
|
530
573
|
return visibleAgents(this).filter((agent) => agent.status !== "offline").length;
|
|
531
574
|
}
|
|
532
575
|
|
|
576
|
+
function getBusyAgentCount() {
|
|
577
|
+
return this.agents.filter((agent) => agent.status === "busy").length;
|
|
578
|
+
}
|
|
579
|
+
|
|
533
580
|
function getHiddenBuiltInAgentCount() {
|
|
534
581
|
return this.showBuiltIns ? 0 : this.agents.filter(isBuiltInAgent).length;
|
|
535
582
|
}
|
|
@@ -578,6 +625,29 @@
|
|
|
578
625
|
.slice(0, 8);
|
|
579
626
|
}
|
|
580
627
|
|
|
628
|
+
function getSelectedChannelDetail() {
|
|
629
|
+
if (!this.channelDetailId) return null;
|
|
630
|
+
return (this.channels || []).find((channel) => channel.id === this.channelDetailId) || null;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function getChannelDetailMessages() {
|
|
634
|
+
const channel = this.selectedChannelDetail;
|
|
635
|
+
if (!channel) return [];
|
|
636
|
+
return this.messages
|
|
637
|
+
.filter((msg) => messageMatchesChannel(msg, channel))
|
|
638
|
+
.slice()
|
|
639
|
+
.sort((a, b) => b.id - a.id)
|
|
640
|
+
.slice(0, 8);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function messageMatchesChannel(message, channel) {
|
|
644
|
+
if (!message || !channel) return false;
|
|
645
|
+
const channelKeys = [channel.id, channel.type, ...(channel.topicChannels || [])].filter(Boolean);
|
|
646
|
+
if (message.channel && channelKeys.includes(message.channel)) return true;
|
|
647
|
+
if (channel.agentId && (message.from === channel.agentId || message.to === channel.agentId)) return true;
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
|
|
581
651
|
function getPairMessagePair() {
|
|
582
652
|
return this.pairs.find((pair) => pair.id === this.pairMessage.pairId) || null;
|
|
583
653
|
}
|
|
@@ -1071,11 +1141,47 @@
|
|
|
1071
1141
|
});
|
|
1072
1142
|
}
|
|
1073
1143
|
|
|
1144
|
+
function getOpenPairCount() {
|
|
1145
|
+
return (this.pairs || []).filter(isOpenPair).length;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
function isOpenPair(pair) {
|
|
1149
|
+
return pair?.status === "active" || pair?.status === "pending";
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1074
1152
|
function getComposeAgents() {
|
|
1075
1153
|
const list = visibleAgents(this);
|
|
1076
1154
|
return this.showOffline ? list : list.filter((agent) => agent.status !== "offline");
|
|
1077
1155
|
}
|
|
1078
1156
|
|
|
1157
|
+
function composeTargetAllowsClaimable() {
|
|
1158
|
+
return matchingComposeRecipientCount(this, this.compose.to) > 1;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
function inboxComposeTargetAllowsClaimable() {
|
|
1162
|
+
return matchingComposeRecipientCount(this, inboxComposeTarget(this)) > 1;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
function matchingComposeRecipientCount(vm, target) {
|
|
1166
|
+
if (!target) return 0;
|
|
1167
|
+
const candidates = vm.composeAgents.filter((agent) => !isBuiltInAgent(agent) && !isChannelAgent(agent));
|
|
1168
|
+
if (target === "broadcast") return candidates.length;
|
|
1169
|
+
if (vm.agentsById[target]) return 1;
|
|
1170
|
+
if (target.startsWith("tag:")) {
|
|
1171
|
+
const tag = target.slice(4);
|
|
1172
|
+
return candidates.filter((agent) => (agent.tags || []).includes(tag)).length;
|
|
1173
|
+
}
|
|
1174
|
+
if (target.startsWith("cap:")) {
|
|
1175
|
+
const cap = target.slice(4);
|
|
1176
|
+
return candidates.filter((agent) => (agent.capabilities || []).includes(cap)).length;
|
|
1177
|
+
}
|
|
1178
|
+
if (target.startsWith("label:")) {
|
|
1179
|
+
const label = target.slice(6);
|
|
1180
|
+
return candidates.filter((agent) => agent.label === label).length;
|
|
1181
|
+
}
|
|
1182
|
+
return 1;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1079
1185
|
function getUniqueLabels() {
|
|
1080
1186
|
return [...new Set(visibleAgents(this).filter((agent) => agent.label).map((agent) => agent.label))];
|
|
1081
1187
|
}
|
|
@@ -1089,13 +1195,22 @@
|
|
|
1089
1195
|
}
|
|
1090
1196
|
|
|
1091
1197
|
function visibleAgents(vm) {
|
|
1092
|
-
|
|
1198
|
+
const nonChannelAgents = vm.agents.filter((agent) => !isChannelAgent(agent));
|
|
1199
|
+
return vm.showBuiltIns ? nonChannelAgents : nonChannelAgents.filter((agent) => !isBuiltInAgent(agent));
|
|
1093
1200
|
}
|
|
1094
1201
|
|
|
1095
1202
|
function isBuiltInAgent(agent) {
|
|
1096
1203
|
return agent?.meta?.builtin === true || agent?.id === HUMAN_AGENT_ID || agent?.id === "system";
|
|
1097
1204
|
}
|
|
1098
1205
|
|
|
1206
|
+
function isChannelAgent(agent) {
|
|
1207
|
+
return agent?.meta?.kind === "channel" || agent?.tags?.includes("channel");
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
function agentSupportsControlActions(agent) {
|
|
1211
|
+
return Boolean(agent && !isBuiltInAgent(agent) && !isChannelAgent(agent));
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1099
1214
|
function applyAgentPreset(vm, list) {
|
|
1100
1215
|
switch (vm.agentPresetFilter) {
|
|
1101
1216
|
case "active":
|
|
@@ -1249,6 +1364,9 @@
|
|
|
1249
1364
|
agentChannels,
|
|
1250
1365
|
channelPresence,
|
|
1251
1366
|
integrationPresence,
|
|
1367
|
+
composeTargetAllowsClaimable,
|
|
1368
|
+
inboxComposeTargetAllowsClaimable,
|
|
1369
|
+
agentSupportsControlActions,
|
|
1252
1370
|
timeAgo,
|
|
1253
1371
|
fmtTime,
|
|
1254
1372
|
healthAlertClass,
|
|
@@ -1368,6 +1486,7 @@
|
|
|
1368
1486
|
function agentType(agent) {
|
|
1369
1487
|
if (agent?.id === HUMAN_AGENT_ID) return "user";
|
|
1370
1488
|
if (agent?.id === "system") return "system";
|
|
1489
|
+
if (isChannelAgent(agent)) return "channel";
|
|
1371
1490
|
|
|
1372
1491
|
const values = [
|
|
1373
1492
|
...(agent?.tags || []),
|
|
@@ -1573,6 +1692,18 @@
|
|
|
1573
1692
|
openPairInvite,
|
|
1574
1693
|
closePairInvite,
|
|
1575
1694
|
doCreatePair,
|
|
1695
|
+
openAgentSpawn,
|
|
1696
|
+
closeAgentSpawn,
|
|
1697
|
+
doSpawnAgent,
|
|
1698
|
+
openAgentDirectoryBrowser,
|
|
1699
|
+
browseAgentDirectory,
|
|
1700
|
+
selectAgentDirectory,
|
|
1701
|
+
openOrchestratorSpawn,
|
|
1702
|
+
openOrchestratorSpawnFor,
|
|
1703
|
+
browseOrchestratorDirs,
|
|
1704
|
+
submitOrchestratorSpawn,
|
|
1705
|
+
orchestratorAction,
|
|
1706
|
+
deleteOrchestrator,
|
|
1576
1707
|
doSendPairMessage,
|
|
1577
1708
|
doAcceptPair,
|
|
1578
1709
|
doRejectPair,
|
|
@@ -1783,7 +1914,7 @@
|
|
|
1783
1914
|
}
|
|
1784
1915
|
|
|
1785
1916
|
function resetInboxComposeTarget() {
|
|
1786
|
-
this.inboxCompose = { ...this.inboxCompose, to: "" };
|
|
1917
|
+
this.inboxCompose = { ...this.inboxCompose, to: "", claimable: false };
|
|
1787
1918
|
}
|
|
1788
1919
|
|
|
1789
1920
|
function inboxComposeTarget(vm) {
|
|
@@ -1809,14 +1940,14 @@
|
|
|
1809
1940
|
};
|
|
1810
1941
|
if (this.inboxCompose.channel) payload.channel = this.inboxCompose.channel;
|
|
1811
1942
|
if (this.inboxCompose.subject) payload.subject = this.inboxCompose.subject;
|
|
1812
|
-
if (this.inboxCompose.claimable) payload.claimable = true;
|
|
1943
|
+
if (this.inboxCompose.claimable && inboxComposeTargetAllowsClaimable.call(this)) payload.claimable = true;
|
|
1813
1944
|
await this.api("POST", "/messages", payload);
|
|
1814
1945
|
this.recordOperatorActivity({
|
|
1815
|
-
title:
|
|
1946
|
+
title: payload.claimable ? "Claimable task sent" : "Message sent",
|
|
1816
1947
|
body: this.inboxCompose.subject || this.inboxCompose.body,
|
|
1817
1948
|
meta: "to " + this.displayTarget(target),
|
|
1818
|
-
icon:
|
|
1819
|
-
kind:
|
|
1949
|
+
icon: payload.claimable ? "ti-hand-grab" : "ti-send",
|
|
1950
|
+
kind: payload.claimable ? "task" : "operator",
|
|
1820
1951
|
view: inboxPeer(payload) ? "inbox" : "messages",
|
|
1821
1952
|
peer: inboxPeer(payload),
|
|
1822
1953
|
});
|
|
@@ -1832,7 +1963,7 @@
|
|
|
1832
1963
|
this.replyTo = { id: msg.id, from: msg.from };
|
|
1833
1964
|
this.compose = {
|
|
1834
1965
|
...DEFAULT_COMPOSE,
|
|
1835
|
-
from:
|
|
1966
|
+
from: HUMAN_AGENT_ID,
|
|
1836
1967
|
to: replyTarget,
|
|
1837
1968
|
channel: msg.channel || "",
|
|
1838
1969
|
};
|
|
@@ -1852,7 +1983,7 @@
|
|
|
1852
1983
|
if (vm.compose.channel) payload.channel = vm.compose.channel;
|
|
1853
1984
|
if (vm.compose.subject) payload.subject = vm.compose.subject;
|
|
1854
1985
|
if (vm.replyTo) payload.replyTo = vm.replyTo.id;
|
|
1855
|
-
if (vm.compose.claimable) payload.claimable = true;
|
|
1986
|
+
if (vm.compose.claimable && composeTargetAllowsClaimable.call(vm)) payload.claimable = true;
|
|
1856
1987
|
return payload;
|
|
1857
1988
|
}
|
|
1858
1989
|
|
|
@@ -1876,7 +2007,7 @@
|
|
|
1876
2007
|
});
|
|
1877
2008
|
this.composeOpen = false;
|
|
1878
2009
|
this.replyTo = null;
|
|
1879
|
-
this.compose = { ...DEFAULT_COMPOSE };
|
|
2010
|
+
this.compose = { ...DEFAULT_COMPOSE, from: HUMAN_AGENT_ID };
|
|
1880
2011
|
await this.fetchMessages();
|
|
1881
2012
|
} catch (e) {
|
|
1882
2013
|
alert("Send failed: " + e.message);
|
|
@@ -2313,6 +2444,167 @@
|
|
|
2313
2444
|
}
|
|
2314
2445
|
}
|
|
2315
2446
|
|
|
2447
|
+
function openAgentSpawn() {
|
|
2448
|
+
this.agentSpawn = { ...DEFAULT_AGENT_SPAWN };
|
|
2449
|
+
this.agentDirectoryBrowser = { open: false, loading: false, path: "", parent: "", home: "", cwd: "", entries: [], error: "" };
|
|
2450
|
+
this.agentSpawnOpen = true;
|
|
2451
|
+
this.$nextTick(() => this.$refs?.agentSpawnCwd?.focus());
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
function closeAgentSpawn() {
|
|
2455
|
+
this.agentSpawnOpen = false;
|
|
2456
|
+
this.agentSpawn = { ...DEFAULT_AGENT_SPAWN };
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
async function openAgentDirectoryBrowser() {
|
|
2460
|
+
this.agentDirectoryBrowser = { ...this.agentDirectoryBrowser, open: true };
|
|
2461
|
+
await this.browseAgentDirectory(this.agentSpawn.cwd || "");
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
async function browseAgentDirectory(path) {
|
|
2465
|
+
this.agentDirectoryBrowser = { ...this.agentDirectoryBrowser, loading: true, error: "" };
|
|
2466
|
+
try {
|
|
2467
|
+
const query = path ? "?path=" + encodeURIComponent(path) : "";
|
|
2468
|
+
const listing = await this.api("GET", "/agents/spawn/directories" + query);
|
|
2469
|
+
this.agentDirectoryBrowser = {
|
|
2470
|
+
open: true,
|
|
2471
|
+
loading: false,
|
|
2472
|
+
path: listing.path || "",
|
|
2473
|
+
parent: listing.parent || "",
|
|
2474
|
+
home: listing.home || "",
|
|
2475
|
+
cwd: listing.cwd || "",
|
|
2476
|
+
entries: listing.entries || [],
|
|
2477
|
+
error: "",
|
|
2478
|
+
};
|
|
2479
|
+
this.agentSpawn = { ...this.agentSpawn, cwd: listing.path || this.agentSpawn.cwd };
|
|
2480
|
+
} catch (e) {
|
|
2481
|
+
this.agentDirectoryBrowser = { ...this.agentDirectoryBrowser, loading: false, error: e.message };
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2485
|
+
function selectAgentDirectory(path) {
|
|
2486
|
+
this.agentSpawn = { ...this.agentSpawn, cwd: path || this.agentDirectoryBrowser.path };
|
|
2487
|
+
this.agentDirectoryBrowser = { ...this.agentDirectoryBrowser, open: false };
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
async function doSpawnAgent() {
|
|
2491
|
+
if (this.agentSpawn.provider !== "codex") {
|
|
2492
|
+
alert("Only Codex live sessions can be spawned from the dashboard right now.");
|
|
2493
|
+
return;
|
|
2494
|
+
}
|
|
2495
|
+
try {
|
|
2496
|
+
const payload = {
|
|
2497
|
+
provider: "codex",
|
|
2498
|
+
approvalMode: this.agentSpawn.approvalMode || "guarded",
|
|
2499
|
+
};
|
|
2500
|
+
if (this.agentSpawn.cwd) payload.cwd = this.agentSpawn.cwd;
|
|
2501
|
+
if (this.agentSpawn.label) payload.label = this.agentSpawn.label;
|
|
2502
|
+
const result = await this.api("POST", "/agents/spawn", payload);
|
|
2503
|
+
this.recordOperatorActivity({
|
|
2504
|
+
title: "Codex agent spawn requested",
|
|
2505
|
+
body: result?.cwd || payload.cwd || "",
|
|
2506
|
+
meta: result?.pid ? "pid " + result.pid : "starting",
|
|
2507
|
+
icon: "ti-plus",
|
|
2508
|
+
kind: "state",
|
|
2509
|
+
view: "agents",
|
|
2510
|
+
});
|
|
2511
|
+
this.closeAgentSpawn();
|
|
2512
|
+
await Promise.all([this.fetchAgents(), this.fetchActivityEvents()]);
|
|
2513
|
+
} catch (e) {
|
|
2514
|
+
alert("Spawn failed: " + e.message);
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
// --- Orchestrator methods ---
|
|
2519
|
+
|
|
2520
|
+
function openOrchestratorSpawn() {
|
|
2521
|
+
const online = this.orchestrators.filter((o) => o.status === "online");
|
|
2522
|
+
if (online.length === 0) return alert("No orchestrators online");
|
|
2523
|
+
this.spawnOrchId = online[0].id;
|
|
2524
|
+
this.spawnProvider = "claude";
|
|
2525
|
+
this.spawnCwd = "";
|
|
2526
|
+
this.spawnLabel = "";
|
|
2527
|
+
this.spawnApproval = "guarded";
|
|
2528
|
+
this.spawnPrompt = "";
|
|
2529
|
+
this.spawnDirListing = null;
|
|
2530
|
+
this.orchestratorSpawnOpen = true;
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
function openOrchestratorSpawnFor(orchId) {
|
|
2534
|
+
this.spawnOrchId = orchId;
|
|
2535
|
+
this.spawnProvider = "claude";
|
|
2536
|
+
this.spawnCwd = "";
|
|
2537
|
+
this.spawnLabel = "";
|
|
2538
|
+
this.spawnApproval = "guarded";
|
|
2539
|
+
this.spawnPrompt = "";
|
|
2540
|
+
this.spawnDirListing = null;
|
|
2541
|
+
this.orchestratorSpawnOpen = true;
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
async function browseOrchestratorDirs() {
|
|
2545
|
+
try {
|
|
2546
|
+
const query = this.spawnCwd ? "?path=" + encodeURIComponent(this.spawnCwd) : "";
|
|
2547
|
+
this.spawnDirListing = await this.api("GET", "/agents/spawn/directories" + query);
|
|
2548
|
+
} catch (e) {
|
|
2549
|
+
alert("Directory browse failed: " + e.message);
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2553
|
+
async function submitOrchestratorSpawn() {
|
|
2554
|
+
if (!this.spawnOrchId) return alert("Select an orchestrator");
|
|
2555
|
+
try {
|
|
2556
|
+
const payload = {
|
|
2557
|
+
provider: this.spawnProvider,
|
|
2558
|
+
approvalMode: this.spawnApproval,
|
|
2559
|
+
};
|
|
2560
|
+
if (this.spawnCwd) payload.cwd = this.spawnCwd;
|
|
2561
|
+
if (this.spawnLabel) payload.label = this.spawnLabel;
|
|
2562
|
+
if (this.spawnPrompt) payload.prompt = this.spawnPrompt;
|
|
2563
|
+
await this.api("POST", "/orchestrators/" + encodeURIComponent(this.spawnOrchId) + "/spawn", payload);
|
|
2564
|
+
this.orchestratorSpawnOpen = false;
|
|
2565
|
+
this.recordOperatorActivity({
|
|
2566
|
+
title: `${this.spawnProvider} agent spawn requested`,
|
|
2567
|
+
body: this.spawnCwd || "",
|
|
2568
|
+
meta: this.spawnOrchId,
|
|
2569
|
+
icon: "ti-plus",
|
|
2570
|
+
kind: "state",
|
|
2571
|
+
view: "orchestrators",
|
|
2572
|
+
});
|
|
2573
|
+
await Promise.all([this.fetchOrchestrators(), this.fetchActivityEvents()]);
|
|
2574
|
+
} catch (e) {
|
|
2575
|
+
alert("Spawn failed: " + e.message);
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
async function orchestratorAction(orchId, action, agentId) {
|
|
2580
|
+
const label = action === "restart" ? "Restart" : "Shutdown";
|
|
2581
|
+
if (!confirm(`${label} agent "${agentId || "all"}" on orchestrator "${orchId}"?`)) return;
|
|
2582
|
+
try {
|
|
2583
|
+
await this.api("POST", "/orchestrators/" + encodeURIComponent(orchId) + "/actions", { action, agentId });
|
|
2584
|
+
this.recordOperatorActivity({
|
|
2585
|
+
title: `Agent ${action} requested`,
|
|
2586
|
+
body: agentId || "all agents",
|
|
2587
|
+
meta: orchId,
|
|
2588
|
+
icon: action === "restart" ? "ti-refresh" : "ti-power",
|
|
2589
|
+
kind: "state",
|
|
2590
|
+
view: "orchestrators",
|
|
2591
|
+
});
|
|
2592
|
+
await Promise.all([this.fetchOrchestrators(), this.fetchActivityEvents()]);
|
|
2593
|
+
} catch (e) {
|
|
2594
|
+
alert(`${label} failed: ` + e.message);
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
async function deleteOrchestrator(orchId) {
|
|
2599
|
+
if (!confirm(`Remove orchestrator "${orchId}"? This will NOT stop its managed agents.`)) return;
|
|
2600
|
+
try {
|
|
2601
|
+
await this.api("DELETE", "/orchestrators/" + encodeURIComponent(orchId));
|
|
2602
|
+
await this.fetchOrchestrators();
|
|
2603
|
+
} catch (e) {
|
|
2604
|
+
alert("Delete failed: " + e.message);
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2316
2608
|
function closePairMessage() {
|
|
2317
2609
|
this.pairMessageOpen = false;
|
|
2318
2610
|
this.pairMessage = { ...DEFAULT_PAIR_MESSAGE };
|
|
@@ -2409,6 +2701,16 @@
|
|
|
2409
2701
|
this.agentDetailOpen = false;
|
|
2410
2702
|
},
|
|
2411
2703
|
|
|
2704
|
+
openChannelDetail(channel) {
|
|
2705
|
+
if (!channel) return;
|
|
2706
|
+
this.channelDetailId = channel.id;
|
|
2707
|
+
this.channelDetailOpen = true;
|
|
2708
|
+
},
|
|
2709
|
+
|
|
2710
|
+
closeChannelDetail() {
|
|
2711
|
+
this.channelDetailOpen = false;
|
|
2712
|
+
},
|
|
2713
|
+
|
|
2412
2714
|
openRename(agent) {
|
|
2413
2715
|
this.renameModal = { show: true, agentId: agent.id, label: agent.label || "" };
|
|
2414
2716
|
this.$nextTick(() => this.$refs.renameInput?.focus());
|
|
@@ -2438,6 +2740,26 @@
|
|
|
2438
2740
|
alert("Delete failed: " + e.message);
|
|
2439
2741
|
}
|
|
2440
2742
|
},
|
|
2743
|
+
|
|
2744
|
+
async doAgentAction(agent, action) {
|
|
2745
|
+
if (!agent || !action) return;
|
|
2746
|
+
try {
|
|
2747
|
+
const result = await this.api("POST", "/agents/" + encodeURIComponent(agent.id) + "/actions", { action });
|
|
2748
|
+
this.recordOperatorActivity({
|
|
2749
|
+
title: action === "restart" ? "Agent restart requested" : "Agent shutdown requested",
|
|
2750
|
+
body: this.displayName(agent),
|
|
2751
|
+
meta: agent.id,
|
|
2752
|
+
icon: action === "restart" ? "ti-refresh" : "ti-power",
|
|
2753
|
+
kind: "state",
|
|
2754
|
+
view: "agents",
|
|
2755
|
+
agentId: agent.id,
|
|
2756
|
+
messageId: result?.message?.id,
|
|
2757
|
+
});
|
|
2758
|
+
await Promise.all([this.fetchMessages(), this.fetchActivityEvents()]);
|
|
2759
|
+
} catch (e) {
|
|
2760
|
+
alert("Agent action failed: " + e.message);
|
|
2761
|
+
}
|
|
2762
|
+
},
|
|
2441
2763
|
};
|
|
2442
2764
|
}
|
|
2443
2765
|
|
|
@@ -2495,7 +2817,7 @@
|
|
|
2495
2817
|
|
|
2496
2818
|
function renderStatusChart() {
|
|
2497
2819
|
const { labels, series } = countAgentStatuses(this.agents);
|
|
2498
|
-
const colorMap = {
|
|
2820
|
+
const colorMap = { idle: "#4299e1", busy: "#ecc94b" };
|
|
2499
2821
|
|
|
2500
2822
|
if (this.chartInstances.status) {
|
|
2501
2823
|
this.chartInstances.status.updateOptions({
|
|
@@ -2522,10 +2844,12 @@
|
|
|
2522
2844
|
}
|
|
2523
2845
|
|
|
2524
2846
|
function countAgentStatuses(agents) {
|
|
2525
|
-
const counts = {
|
|
2526
|
-
for (const agent of agents)
|
|
2847
|
+
const counts = { idle: 0, busy: 0 };
|
|
2848
|
+
for (const agent of agents) {
|
|
2849
|
+
if (agent.status in counts) counts[agent.status] += 1;
|
|
2850
|
+
}
|
|
2527
2851
|
|
|
2528
|
-
const labels = Object.keys(counts)
|
|
2852
|
+
const labels = Object.keys(counts);
|
|
2529
2853
|
return { labels, series: labels.map((key) => counts[key]) };
|
|
2530
2854
|
}
|
|
2531
2855
|
|