agent-relay-server 0.4.39 → 0.6.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/README.md +8 -8
- package/package.json +1 -1
- package/public/dashboard.js +233 -17
- package/public/icons/agent-relay-192.png +0 -0
- package/public/icons/agent-relay-512.png +0 -0
- package/public/icons/agent-relay.svg +14 -0
- package/public/index.html +276 -4
- package/public/manifest.webmanifest +33 -0
- package/public/sw.js +58 -0
- package/src/cli.ts +80 -17
- package/src/connectors.ts +256 -0
- package/src/db.ts +544 -25
- package/src/index.ts +25 -1
- package/src/routes.ts +632 -26
- package/src/security.ts +2 -1
- package/src/sse.ts +21 -1
- package/src/types.ts +152 -3
package/README.md
CHANGED
|
@@ -40,20 +40,20 @@ Agent Relay has two parts:
|
|
|
40
40
|
|
|
41
41
|
Pick your provider: install the Claude Code plugin, the Codex connector, or both. The shared config is the same four env vars either way. See the [full mental model](docs/mental-model.md) for routing, identity, and task lifecycle details, or the [provider spec](docs/provider-spec.md) if you want to build your own integration.
|
|
42
42
|
|
|
43
|
-
##
|
|
43
|
+
## Connector Model
|
|
44
44
|
|
|
45
|
-
Agent Relay
|
|
45
|
+
Agent Relay extensions run as external **Connectors**. The relay discovers them from `~/.agent-relay/connectors/<id>/manifest.json`, then can show status and run lifecycle actions without loading connector code in-process.
|
|
46
46
|
|
|
47
47
|
> **Agents do work. Providers host agents. Integrations create work. Channels carry conversations.**
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
Connector kinds:
|
|
50
50
|
|
|
51
|
-
- **
|
|
52
|
-
- **
|
|
53
|
-
- **
|
|
54
|
-
- **
|
|
51
|
+
- **Provider connectors** host AI runtimes. The Claude plugin and Codex connector register coding-agent sessions as Relay agents.
|
|
52
|
+
- **Event connectors** connect external systems that create or update work. CI, monitoring, support desks, and deployment tools fit here.
|
|
53
|
+
- **Channel connectors** connect communication surfaces where humans and systems already talk. Telegram is the first channel; Slack, email, SMS, Matrix, Discord, and web chat are natural next ones.
|
|
54
|
+
- **Orchestrator connectors** supervise hosts and can spawn or control provider sessions.
|
|
55
55
|
|
|
56
|
-
If you want to contribute, pick the shape that matches your system. Building a new AI runtime adapter? Build a provider. Sending alerts or tickets into Agent Relay? Build an
|
|
56
|
+
If you want to contribute, pick the shape that matches your system. Building a new AI runtime adapter? Build a provider connector. Sending alerts or tickets into Agent Relay? Build an event connector. Opening Agent Relay to another messaging app? Build a channel connector.
|
|
57
57
|
|
|
58
58
|
## Quick Start
|
|
59
59
|
|
package/package.json
CHANGED
package/public/dashboard.js
CHANGED
|
@@ -54,11 +54,13 @@
|
|
|
54
54
|
|
|
55
55
|
agents: [],
|
|
56
56
|
agentsById: {},
|
|
57
|
+
orchestrators: [],
|
|
57
58
|
pairs: [],
|
|
58
59
|
messages: [],
|
|
59
60
|
tasks: [],
|
|
60
61
|
integrations: [],
|
|
61
62
|
channels: [],
|
|
63
|
+
connectors: [],
|
|
62
64
|
taskEvents: [],
|
|
63
65
|
taskEventCache: {},
|
|
64
66
|
stats: {},
|
|
@@ -85,6 +87,14 @@
|
|
|
85
87
|
replyTo: null,
|
|
86
88
|
composeOpen: false,
|
|
87
89
|
agentSpawnOpen: false,
|
|
90
|
+
orchestratorSpawnOpen: false,
|
|
91
|
+
spawnOrchId: "",
|
|
92
|
+
spawnProvider: "claude",
|
|
93
|
+
spawnCwd: "",
|
|
94
|
+
spawnLabel: "",
|
|
95
|
+
spawnApproval: "guarded",
|
|
96
|
+
spawnPrompt: "",
|
|
97
|
+
spawnDirListing: null,
|
|
88
98
|
agentDirectoryBrowser: { open: false, loading: false, path: "", parent: "", home: "", cwd: "", entries: [], error: "" },
|
|
89
99
|
pairInviteOpen: false,
|
|
90
100
|
pairMessageOpen: false,
|
|
@@ -231,6 +241,7 @@
|
|
|
231
241
|
if (view === "work") await Promise.all([this.fetchMessages(), this.fetchTasks()]);
|
|
232
242
|
if (view === "pairs") this.fetchPairs();
|
|
233
243
|
if (view === "channels") await this.fetchChannels();
|
|
244
|
+
if (view === "connectors") await this.fetchConnectors();
|
|
234
245
|
if (view === "integrations") await this.fetchIntegrations();
|
|
235
246
|
if (view === "tasks") this.fetchTasks();
|
|
236
247
|
},
|
|
@@ -281,6 +292,8 @@
|
|
|
281
292
|
es.addEventListener("message.claimed", (event) => handleMessageClaimed(this, parseEventData(event)));
|
|
282
293
|
es.addEventListener("message.claim_released", (event) => handleMessageClaimReleased(this, parseEventData(event)));
|
|
283
294
|
es.addEventListener("message.deleted", (event) => handleMessageDeleted(this, parseEventData(event)));
|
|
295
|
+
es.addEventListener("orchestrator.status", (event) => handleOrchestratorStatus(this, parseEventData(event)));
|
|
296
|
+
es.addEventListener("orchestrator.removed", (event) => handleOrchestratorRemoved(this, parseEventData(event)));
|
|
284
297
|
registerTaskEvents(this, es);
|
|
285
298
|
}
|
|
286
299
|
|
|
@@ -320,6 +333,14 @@
|
|
|
320
333
|
refreshChartsIfVisible(vm);
|
|
321
334
|
}
|
|
322
335
|
|
|
336
|
+
function handleOrchestratorStatus(vm, orch) {
|
|
337
|
+
upsertById(vm.orchestrators, orch);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function handleOrchestratorRemoved(vm, data) {
|
|
341
|
+
vm.orchestrators = vm.orchestrators.filter((o) => o.id !== data.id);
|
|
342
|
+
}
|
|
343
|
+
|
|
323
344
|
function handleMessageClaimed(vm, data) {
|
|
324
345
|
const msg = vm.messages.find((item) => item.id === data.messageId);
|
|
325
346
|
if (!msg) return;
|
|
@@ -374,7 +395,7 @@
|
|
|
374
395
|
},
|
|
375
396
|
|
|
376
397
|
async refresh() {
|
|
377
|
-
await Promise.all([this.fetchStats(), this.fetchHealth(), this.fetchAgents(), this.fetchPairs(), this.fetchMessages(), this.fetchTasks(), this.fetchChannels(), this.fetchIntegrations(), this.fetchInboxState(), this.fetchActivityEvents()]);
|
|
398
|
+
await Promise.all([this.fetchStats(), this.fetchHealth(), this.fetchAgents(), this.fetchOrchestrators(), this.fetchPairs(), this.fetchMessages(), this.fetchTasks(), this.fetchChannels(), this.fetchConnectors(), this.fetchIntegrations(), this.fetchInboxState(), this.fetchActivityEvents()]);
|
|
378
399
|
},
|
|
379
400
|
|
|
380
401
|
async refreshLiveData() {
|
|
@@ -407,6 +428,12 @@
|
|
|
407
428
|
} catch {}
|
|
408
429
|
},
|
|
409
430
|
|
|
431
|
+
async fetchOrchestrators() {
|
|
432
|
+
try {
|
|
433
|
+
this.orchestrators = await this.api("GET", "/orchestrators");
|
|
434
|
+
} catch {}
|
|
435
|
+
},
|
|
436
|
+
|
|
410
437
|
async fetchPairs() {
|
|
411
438
|
try {
|
|
412
439
|
let pairs;
|
|
@@ -451,6 +478,12 @@
|
|
|
451
478
|
} catch {}
|
|
452
479
|
},
|
|
453
480
|
|
|
481
|
+
async fetchConnectors() {
|
|
482
|
+
try {
|
|
483
|
+
this.connectors = await this.api("GET", "/connectors");
|
|
484
|
+
} catch {}
|
|
485
|
+
},
|
|
486
|
+
|
|
454
487
|
async fetchChannels() {
|
|
455
488
|
try {
|
|
456
489
|
this.channels = await this.api("GET", "/channels");
|
|
@@ -523,6 +556,7 @@
|
|
|
523
556
|
activityItems: { get: getActivityItems },
|
|
524
557
|
workQueueItems: { get: getWorkQueueItems },
|
|
525
558
|
channelCards: { get: getChannelCards },
|
|
559
|
+
connectorCards: { get: getConnectorCards },
|
|
526
560
|
integrationCards: { get: getIntegrationCards },
|
|
527
561
|
openPairCount: { get: getOpenPairCount },
|
|
528
562
|
filteredMessages: { get: getFilteredMessages },
|
|
@@ -535,9 +569,15 @@
|
|
|
535
569
|
healthIssues: { get: getHealthIssues },
|
|
536
570
|
healthDiagnostics: { get: getHealthDiagnostics },
|
|
537
571
|
commandPaletteItems: { get: getCommandPaletteItems },
|
|
572
|
+
spawnAvailableProviders: { get: getSpawnAvailableProviders },
|
|
538
573
|
};
|
|
539
574
|
}
|
|
540
575
|
|
|
576
|
+
function getSpawnAvailableProviders() {
|
|
577
|
+
const orch = this.orchestrators.find((o) => o.id === this.spawnOrchId);
|
|
578
|
+
return orch ? orch.providers : ["claude", "codex"];
|
|
579
|
+
}
|
|
580
|
+
|
|
541
581
|
function getOnlineCount() {
|
|
542
582
|
return visibleAgents(this).filter((agent) => agent.status !== "offline").length;
|
|
543
583
|
}
|
|
@@ -613,6 +653,11 @@
|
|
|
613
653
|
if (!message || !channel) return false;
|
|
614
654
|
const channelKeys = [channel.id, channel.type, ...(channel.topicChannels || [])].filter(Boolean);
|
|
615
655
|
if (message.channel && channelKeys.includes(message.channel)) return true;
|
|
656
|
+
const payloadChannel = message.payload?.channel;
|
|
657
|
+
if (payloadChannel && typeof payloadChannel === "object") {
|
|
658
|
+
const payloadKeys = [payloadChannel.agentId, payloadChannel.provider, payloadChannel.accountId].filter(Boolean);
|
|
659
|
+
if (payloadKeys.includes(channel.id) || payloadKeys.includes(channel.type) || payloadKeys.includes(channel.accountId)) return true;
|
|
660
|
+
}
|
|
616
661
|
if (channel.agentId && (message.from === channel.agentId || message.to === channel.agentId)) return true;
|
|
617
662
|
return false;
|
|
618
663
|
}
|
|
@@ -679,7 +724,7 @@
|
|
|
679
724
|
const haystack = [
|
|
680
725
|
vm.displayTarget(thread.peer),
|
|
681
726
|
thread.peer,
|
|
682
|
-
...thread.messages.flatMap((msg) => [msg.subject || "",
|
|
727
|
+
...thread.messages.flatMap((msg) => [msg.subject || "", messageBody.call(vm, msg) || "", msg.channel || "", vm.displayTarget(msg.from), vm.displayTarget(msg.to)]),
|
|
683
728
|
].join("\n").toLowerCase();
|
|
684
729
|
return haystack.includes(search);
|
|
685
730
|
}
|
|
@@ -768,12 +813,12 @@
|
|
|
768
813
|
}));
|
|
769
814
|
|
|
770
815
|
const messageItems = (this.messages || [])
|
|
771
|
-
.filter(
|
|
816
|
+
.filter(isClaimableMessageWaiting)
|
|
772
817
|
.map((msg) => ({
|
|
773
818
|
id: "message-" + msg.id,
|
|
774
819
|
sourceType: "message",
|
|
775
820
|
title: msg.subject || "Claimable message #" + msg.id,
|
|
776
|
-
body: msg
|
|
821
|
+
body: messageBody(msg),
|
|
777
822
|
severity: "warning",
|
|
778
823
|
status: msg.claimedBy ? "claimed" : "open",
|
|
779
824
|
owner: msg.claimedBy || "",
|
|
@@ -821,7 +866,7 @@
|
|
|
821
866
|
return (vm.messages || []).flatMap((msg) => {
|
|
822
867
|
const ts = toTimestamp(msg.createdAt);
|
|
823
868
|
const items = [];
|
|
824
|
-
const pairEvent = msg.
|
|
869
|
+
const pairEvent = msg.payload?.pairEvent;
|
|
825
870
|
if (pairEvent) {
|
|
826
871
|
items.push(activityItem({
|
|
827
872
|
id: "pair-message-" + msg.id,
|
|
@@ -837,10 +882,10 @@
|
|
|
837
882
|
} else if (msg.from === HUMAN_AGENT_ID) {
|
|
838
883
|
items.push(activityItem({
|
|
839
884
|
id: "human-send-" + msg.id,
|
|
840
|
-
kind: msg.
|
|
885
|
+
kind: msg.kind === "task" ? "task" : "message",
|
|
841
886
|
ts,
|
|
842
|
-
icon: msg.
|
|
843
|
-
title: msg.
|
|
887
|
+
icon: msg.kind === "task" ? "ti-hand-grab" : "ti-send",
|
|
888
|
+
title: msg.kind === "task" ? "Claimable task sent" : "Message sent",
|
|
844
889
|
body: vm.messagePreview(msg),
|
|
845
890
|
meta: "to " + vm.displayTarget(msg.to),
|
|
846
891
|
view: inboxPeer(msg) ? "inbox" : "messages",
|
|
@@ -1018,7 +1063,7 @@
|
|
|
1018
1063
|
}
|
|
1019
1064
|
|
|
1020
1065
|
function messageLooksLikeQuestion(msg) {
|
|
1021
|
-
return /\?/.test(`${msg.subject || ""}\n${msg
|
|
1066
|
+
return /\?/.test(`${msg.subject || ""}\n${messageBody(msg)}`);
|
|
1022
1067
|
}
|
|
1023
1068
|
|
|
1024
1069
|
function countClaimableWaiting(vm) {
|
|
@@ -1038,7 +1083,7 @@
|
|
|
1038
1083
|
}
|
|
1039
1084
|
|
|
1040
1085
|
function isClaimableMessageWaiting(msg) {
|
|
1041
|
-
return Boolean(msg.claimable && !msg.claimedBy);
|
|
1086
|
+
return Boolean(msg.claimable && !msg.claimedBy && !(msg.kind === "task" && Number.isSafeInteger(msg.payload?.taskId)));
|
|
1042
1087
|
}
|
|
1043
1088
|
|
|
1044
1089
|
function targetMatchesAgent(target, agent) {
|
|
@@ -1110,6 +1155,17 @@
|
|
|
1110
1155
|
});
|
|
1111
1156
|
}
|
|
1112
1157
|
|
|
1158
|
+
function getConnectorCards() {
|
|
1159
|
+
return [...(this.connectors || [])].sort((a, b) => {
|
|
1160
|
+
const statusRank = { error: 0, warn: 1, unknown: 2, ok: 3 };
|
|
1161
|
+
const aStatus = a.runtime?.status || "unknown";
|
|
1162
|
+
const bStatus = b.runtime?.status || "unknown";
|
|
1163
|
+
const statusDiff = (statusRank[aStatus] ?? 2) - (statusRank[bStatus] ?? 2);
|
|
1164
|
+
if (statusDiff !== 0) return statusDiff;
|
|
1165
|
+
return String(a.displayName || a.id || "").localeCompare(String(b.displayName || b.id || ""));
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1113
1169
|
function getOpenPairCount() {
|
|
1114
1170
|
return (this.pairs || []).filter(isOpenPair).length;
|
|
1115
1171
|
}
|
|
@@ -1173,7 +1229,7 @@
|
|
|
1173
1229
|
}
|
|
1174
1230
|
|
|
1175
1231
|
function isChannelAgent(agent) {
|
|
1176
|
-
return agent?.meta?.kind === "channel" || agent?.tags?.includes("channel");
|
|
1232
|
+
return agent?.kind === "channel" || agent?.meta?.kind === "channel" || agent?.tags?.includes("channel");
|
|
1177
1233
|
}
|
|
1178
1234
|
|
|
1179
1235
|
function agentSupportsControlActions(agent) {
|
|
@@ -1312,6 +1368,7 @@
|
|
|
1312
1368
|
displayName,
|
|
1313
1369
|
displayTarget,
|
|
1314
1370
|
conversationTitle,
|
|
1371
|
+
messageBody,
|
|
1315
1372
|
messagePreview,
|
|
1316
1373
|
agentPair,
|
|
1317
1374
|
pairPeerId,
|
|
@@ -1332,6 +1389,7 @@
|
|
|
1332
1389
|
isBuiltInAgent,
|
|
1333
1390
|
agentChannels,
|
|
1334
1391
|
channelPresence,
|
|
1392
|
+
connectorPresence,
|
|
1335
1393
|
integrationPresence,
|
|
1336
1394
|
composeTargetAllowsClaimable,
|
|
1337
1395
|
inboxComposeTargetAllowsClaimable,
|
|
@@ -1365,10 +1423,43 @@
|
|
|
1365
1423
|
}
|
|
1366
1424
|
|
|
1367
1425
|
function messagePreview(msg) {
|
|
1368
|
-
const text = msg?.subject || msg
|
|
1426
|
+
const text = msg?.subject || messageBody(msg) || "";
|
|
1369
1427
|
return text.length > 90 ? text.slice(0, 90) + "..." : text;
|
|
1370
1428
|
}
|
|
1371
1429
|
|
|
1430
|
+
function messageBody(msg) {
|
|
1431
|
+
if (!msg) return "";
|
|
1432
|
+
const payload = msg.payload || {};
|
|
1433
|
+
const channelMessage = payload.message;
|
|
1434
|
+
if (channelMessage && typeof channelMessage === "object" && typeof channelMessage.text === "string" && channelMessage.text.trim()) {
|
|
1435
|
+
return channelMessage.text;
|
|
1436
|
+
}
|
|
1437
|
+
const interaction = payload.interaction;
|
|
1438
|
+
if (interaction && typeof interaction === "object") {
|
|
1439
|
+
const title = typeof interaction.title === "string" ? interaction.title.trim() : "";
|
|
1440
|
+
const description = typeof interaction.description === "string" ? interaction.description.trim() : "";
|
|
1441
|
+
if (title && description) return title + "\n" + description;
|
|
1442
|
+
if (title) return title;
|
|
1443
|
+
if (description) return description;
|
|
1444
|
+
}
|
|
1445
|
+
const reaction = payload.reaction;
|
|
1446
|
+
if (reaction && typeof reaction === "object") {
|
|
1447
|
+
const name = typeof reaction.name === "string" ? reaction.name : "";
|
|
1448
|
+
const emoji = typeof reaction.emoji === "string" ? reaction.emoji : "";
|
|
1449
|
+
const value = typeof reaction.value === "string" ? reaction.value : "";
|
|
1450
|
+
return ["Reaction", emoji || name || value].filter(Boolean).join(": ");
|
|
1451
|
+
}
|
|
1452
|
+
const activity = payload.activity;
|
|
1453
|
+
if (activity && typeof activity === "object") {
|
|
1454
|
+
const kind = typeof activity.kind === "string" ? activity.kind : "activity";
|
|
1455
|
+
const state = typeof activity.state === "string" ? activity.state : "";
|
|
1456
|
+
return [kind, state].filter(Boolean).join(" ");
|
|
1457
|
+
}
|
|
1458
|
+
if (typeof payload.text === "string" && payload.text.trim()) return payload.text;
|
|
1459
|
+
if (typeof payload.message === "string" && payload.message.trim()) return payload.message;
|
|
1460
|
+
return msg.body || "";
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1372
1463
|
function agentPair(agent) {
|
|
1373
1464
|
return agent ? this.pairsByAgentId[agent.id] : null;
|
|
1374
1465
|
}
|
|
@@ -1552,6 +1643,16 @@
|
|
|
1552
1643
|
return { label: "configured", tone: "primary", icon: "ti-plug-connected" };
|
|
1553
1644
|
}
|
|
1554
1645
|
|
|
1646
|
+
function connectorPresence(connector) {
|
|
1647
|
+
const runtime = connector?.runtime || {};
|
|
1648
|
+
if (runtime.status === "error") return { label: "error", tone: "danger", icon: "ti-alert-triangle" };
|
|
1649
|
+
if (runtime.status === "warn") return { label: "warning", tone: "warning", icon: "ti-alert-circle" };
|
|
1650
|
+
if (runtime.running) return { label: "running", tone: "success", icon: "ti-circle-check" };
|
|
1651
|
+
if (runtime.enabled === false) return { label: "disabled", tone: "secondary", icon: "ti-player-pause" };
|
|
1652
|
+
if (runtime.status === "ok") return { label: "ok", tone: "success", icon: "ti-circle-check" };
|
|
1653
|
+
return { label: "unknown", tone: "secondary", icon: "ti-help-circle" };
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1555
1656
|
function agentChannels(agent) {
|
|
1556
1657
|
if (!agent) return [];
|
|
1557
1658
|
return (this.channels || []).filter((ch) => ch.agentId === agent.id);
|
|
@@ -1644,6 +1745,7 @@
|
|
|
1644
1745
|
recordOperatorActivity,
|
|
1645
1746
|
openActivityItem,
|
|
1646
1747
|
runHealthAction,
|
|
1748
|
+
runConnectorAction,
|
|
1647
1749
|
openCommandPalette,
|
|
1648
1750
|
closeCommandPalette,
|
|
1649
1751
|
runCommand,
|
|
@@ -1667,6 +1769,12 @@
|
|
|
1667
1769
|
openAgentDirectoryBrowser,
|
|
1668
1770
|
browseAgentDirectory,
|
|
1669
1771
|
selectAgentDirectory,
|
|
1772
|
+
openOrchestratorSpawn,
|
|
1773
|
+
openOrchestratorSpawnFor,
|
|
1774
|
+
browseOrchestratorDirs,
|
|
1775
|
+
submitOrchestratorSpawn,
|
|
1776
|
+
orchestratorAction,
|
|
1777
|
+
deleteOrchestrator,
|
|
1670
1778
|
doSendPairMessage,
|
|
1671
1779
|
doAcceptPair,
|
|
1672
1780
|
doRejectPair,
|
|
@@ -1903,7 +2011,11 @@
|
|
|
1903
2011
|
};
|
|
1904
2012
|
if (this.inboxCompose.channel) payload.channel = this.inboxCompose.channel;
|
|
1905
2013
|
if (this.inboxCompose.subject) payload.subject = this.inboxCompose.subject;
|
|
1906
|
-
if (this.inboxCompose.claimable && inboxComposeTargetAllowsClaimable.call(this))
|
|
2014
|
+
if (this.inboxCompose.claimable && inboxComposeTargetAllowsClaimable.call(this)) {
|
|
2015
|
+
payload.claimable = true;
|
|
2016
|
+
payload.kind = "task";
|
|
2017
|
+
payload.payload = { title: this.inboxCompose.subject || "Claimable task" };
|
|
2018
|
+
}
|
|
1907
2019
|
await this.api("POST", "/messages", payload);
|
|
1908
2020
|
this.recordOperatorActivity({
|
|
1909
2021
|
title: payload.claimable ? "Claimable task sent" : "Message sent",
|
|
@@ -1946,7 +2058,11 @@
|
|
|
1946
2058
|
if (vm.compose.channel) payload.channel = vm.compose.channel;
|
|
1947
2059
|
if (vm.compose.subject) payload.subject = vm.compose.subject;
|
|
1948
2060
|
if (vm.replyTo) payload.replyTo = vm.replyTo.id;
|
|
1949
|
-
if (vm.compose.claimable && composeTargetAllowsClaimable.call(vm))
|
|
2061
|
+
if (vm.compose.claimable && composeTargetAllowsClaimable.call(vm)) {
|
|
2062
|
+
payload.claimable = true;
|
|
2063
|
+
payload.kind = "task";
|
|
2064
|
+
payload.payload = { title: vm.compose.subject || "Claimable task" };
|
|
2065
|
+
}
|
|
1950
2066
|
return payload;
|
|
1951
2067
|
}
|
|
1952
2068
|
|
|
@@ -2163,6 +2279,16 @@
|
|
|
2163
2279
|
if (action.copy) await copyText(action.copy);
|
|
2164
2280
|
}
|
|
2165
2281
|
|
|
2282
|
+
async function runConnectorAction(connector, action) {
|
|
2283
|
+
if (!connector || !action) return;
|
|
2284
|
+
try {
|
|
2285
|
+
await this.api("POST", "/connectors/" + encodeURIComponent(connector.id) + "/actions", { action });
|
|
2286
|
+
await this.fetchConnectors();
|
|
2287
|
+
} catch (e) {
|
|
2288
|
+
alert("Connector action failed: " + e.message);
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2166
2292
|
async function copyText(value) {
|
|
2167
2293
|
if (typeof navigator === "undefined") return;
|
|
2168
2294
|
try {
|
|
@@ -2252,8 +2378,8 @@
|
|
|
2252
2378
|
|
|
2253
2379
|
function pairMessages(vm, pair) {
|
|
2254
2380
|
return (vm.messages || []).filter((msg) =>
|
|
2255
|
-
msg.
|
|
2256
|
-
(msg.
|
|
2381
|
+
msg.payload?.pairId === pair.id ||
|
|
2382
|
+
(msg.payload?.pairEvent && [pair.requesterId, pair.targetId].includes(msg.from) && [pair.requesterId, pair.targetId].includes(msg.to))
|
|
2257
2383
|
);
|
|
2258
2384
|
}
|
|
2259
2385
|
|
|
@@ -2306,7 +2432,7 @@
|
|
|
2306
2432
|
if (msg.channel) lines.push("- Channel: " + msg.channel);
|
|
2307
2433
|
if (msg.subject) lines.push("- Subject: " + msg.subject);
|
|
2308
2434
|
if (msg.claimedBy) lines.push("- Claimed by: " + vm.displayTarget(msg.claimedBy));
|
|
2309
|
-
lines.push("",
|
|
2435
|
+
lines.push("", messageBody.call(vm, msg) || "", "");
|
|
2310
2436
|
}
|
|
2311
2437
|
}
|
|
2312
2438
|
|
|
@@ -2478,6 +2604,96 @@
|
|
|
2478
2604
|
}
|
|
2479
2605
|
}
|
|
2480
2606
|
|
|
2607
|
+
// --- Orchestrator methods ---
|
|
2608
|
+
|
|
2609
|
+
function openOrchestratorSpawn() {
|
|
2610
|
+
const online = this.orchestrators.filter((o) => o.status === "online");
|
|
2611
|
+
if (online.length === 0) return alert("No orchestrators online");
|
|
2612
|
+
this.spawnOrchId = online[0].id;
|
|
2613
|
+
this.spawnProvider = "claude";
|
|
2614
|
+
this.spawnCwd = "";
|
|
2615
|
+
this.spawnLabel = "";
|
|
2616
|
+
this.spawnApproval = "guarded";
|
|
2617
|
+
this.spawnPrompt = "";
|
|
2618
|
+
this.spawnDirListing = null;
|
|
2619
|
+
this.orchestratorSpawnOpen = true;
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2622
|
+
function openOrchestratorSpawnFor(orchId) {
|
|
2623
|
+
this.spawnOrchId = orchId;
|
|
2624
|
+
this.spawnProvider = "claude";
|
|
2625
|
+
this.spawnCwd = "";
|
|
2626
|
+
this.spawnLabel = "";
|
|
2627
|
+
this.spawnApproval = "guarded";
|
|
2628
|
+
this.spawnPrompt = "";
|
|
2629
|
+
this.spawnDirListing = null;
|
|
2630
|
+
this.orchestratorSpawnOpen = true;
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
async function browseOrchestratorDirs() {
|
|
2634
|
+
try {
|
|
2635
|
+
const query = this.spawnCwd ? "?path=" + encodeURIComponent(this.spawnCwd) : "";
|
|
2636
|
+
this.spawnDirListing = await this.api("GET", "/agents/spawn/directories" + query);
|
|
2637
|
+
} catch (e) {
|
|
2638
|
+
alert("Directory browse failed: " + e.message);
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
async function submitOrchestratorSpawn() {
|
|
2643
|
+
if (!this.spawnOrchId) return alert("Select an orchestrator");
|
|
2644
|
+
try {
|
|
2645
|
+
const payload = {
|
|
2646
|
+
provider: this.spawnProvider,
|
|
2647
|
+
approvalMode: this.spawnApproval,
|
|
2648
|
+
};
|
|
2649
|
+
if (this.spawnCwd) payload.cwd = this.spawnCwd;
|
|
2650
|
+
if (this.spawnLabel) payload.label = this.spawnLabel;
|
|
2651
|
+
if (this.spawnPrompt) payload.prompt = this.spawnPrompt;
|
|
2652
|
+
await this.api("POST", "/orchestrators/" + encodeURIComponent(this.spawnOrchId) + "/spawn", payload);
|
|
2653
|
+
this.orchestratorSpawnOpen = false;
|
|
2654
|
+
this.recordOperatorActivity({
|
|
2655
|
+
title: `${this.spawnProvider} agent spawn requested`,
|
|
2656
|
+
body: this.spawnCwd || "",
|
|
2657
|
+
meta: this.spawnOrchId,
|
|
2658
|
+
icon: "ti-plus",
|
|
2659
|
+
kind: "state",
|
|
2660
|
+
view: "orchestrators",
|
|
2661
|
+
});
|
|
2662
|
+
await Promise.all([this.fetchOrchestrators(), this.fetchActivityEvents()]);
|
|
2663
|
+
} catch (e) {
|
|
2664
|
+
alert("Spawn failed: " + e.message);
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
async function orchestratorAction(orchId, action, agentId) {
|
|
2669
|
+
const label = action === "restart" ? "Restart" : "Shutdown";
|
|
2670
|
+
if (!confirm(`${label} agent "${agentId || "all"}" on orchestrator "${orchId}"?`)) return;
|
|
2671
|
+
try {
|
|
2672
|
+
await this.api("POST", "/orchestrators/" + encodeURIComponent(orchId) + "/actions", { action, agentId });
|
|
2673
|
+
this.recordOperatorActivity({
|
|
2674
|
+
title: `Agent ${action} requested`,
|
|
2675
|
+
body: agentId || "all agents",
|
|
2676
|
+
meta: orchId,
|
|
2677
|
+
icon: action === "restart" ? "ti-refresh" : "ti-power",
|
|
2678
|
+
kind: "state",
|
|
2679
|
+
view: "orchestrators",
|
|
2680
|
+
});
|
|
2681
|
+
await Promise.all([this.fetchOrchestrators(), this.fetchActivityEvents()]);
|
|
2682
|
+
} catch (e) {
|
|
2683
|
+
alert(`${label} failed: ` + e.message);
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
async function deleteOrchestrator(orchId) {
|
|
2688
|
+
if (!confirm(`Remove orchestrator "${orchId}"? This will NOT stop its managed agents.`)) return;
|
|
2689
|
+
try {
|
|
2690
|
+
await this.api("DELETE", "/orchestrators/" + encodeURIComponent(orchId));
|
|
2691
|
+
await this.fetchOrchestrators();
|
|
2692
|
+
} catch (e) {
|
|
2693
|
+
alert("Delete failed: " + e.message);
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2481
2697
|
function closePairMessage() {
|
|
2482
2698
|
this.pairMessageOpen = false;
|
|
2483
2699
|
this.pairMessage = { ...DEFAULT_PAIR_MESSAGE };
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
2
|
+
<rect width="512" height="512" rx="96" fill="#0d1117"/>
|
|
3
|
+
<g stroke="#30363d" stroke-width="22" stroke-linecap="round">
|
|
4
|
+
<line x1="137" y1="151" x2="221" y2="229"/>
|
|
5
|
+
<line x1="375" y1="151" x2="291" y2="229"/>
|
|
6
|
+
<line x1="137" y1="361" x2="221" y2="283"/>
|
|
7
|
+
<line x1="375" y1="361" x2="291" y2="283"/>
|
|
8
|
+
</g>
|
|
9
|
+
<circle cx="256" cy="256" r="72" fill="#58a6ff"/>
|
|
10
|
+
<circle cx="96" cy="116" r="42" fill="#3fb950"/>
|
|
11
|
+
<circle cx="416" cy="116" r="42" fill="#3fb950"/>
|
|
12
|
+
<circle cx="96" cy="396" r="42" fill="#3fb950"/>
|
|
13
|
+
<circle cx="416" cy="396" r="42" fill="#3fb950"/>
|
|
14
|
+
</svg>
|