agent-relay-server 0.4.35 → 0.4.37

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay-server",
3
- "version": "0.4.35",
3
+ "version": "0.4.37",
4
4
  "description": "Lightweight HTTP message relay for inter-agent communication across machines",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
@@ -12,12 +12,16 @@
12
12
  const LIVE_REFRESH_MS = 5_000;
13
13
  const AGENT_TYPE_ICONS = {
14
14
  codex: "ti-terminal-2",
15
- claude: "ti-sparkles",
15
+ claude: "claude-sol",
16
+ user: "ti-user",
17
+ system: "ti-server",
16
18
  agent: "ti-robot",
17
19
  };
18
20
  const AGENT_TYPE_TITLES = {
19
21
  codex: "Codex agent",
20
22
  claude: "Claude agent",
23
+ user: "Human operator",
24
+ system: "System",
21
25
  agent: "Agent",
22
26
  };
23
27
 
@@ -62,6 +66,8 @@
62
66
  inboxArchivedThreads: loadPref("inboxArchivedThreads", {}),
63
67
  inboxDrafts: loadPref("inboxDrafts", {}),
64
68
  inboxSearch: "",
69
+ inboxSort: loadPref("inboxSort", "attention"),
70
+ inboxSortDir: loadPref("inboxSortDir", "desc"),
65
71
  inboxShowArchived: loadPref("inboxShowArchived", false),
66
72
  operatorActivity: loadPref("operatorActivity", []),
67
73
  activityEvents: [],
@@ -121,6 +127,8 @@
121
127
  vm.$watch("agentStatusFilter", (value) => vm.save("agentStatusFilter", value));
122
128
  vm.$watch("agentTagFilter", (value) => vm.save("agentTagFilter", value));
123
129
  vm.$watch("pairStatusFilter", (value) => vm.save("pairStatusFilter", value));
130
+ vm.$watch("inboxSort", (value) => vm.save("inboxSort", value));
131
+ vm.$watch("inboxSortDir", (value) => vm.save("inboxSortDir", value));
124
132
  vm.$watch("inboxShowArchived", (value) => vm.save("inboxShowArchived", value));
125
133
  vm.$watch("activityFilter", (value) => vm.save("activityFilter", value));
126
134
  vm.$watch("view", (value, oldValue) => {
@@ -580,11 +588,32 @@
580
588
 
581
589
  function getInboxThreads() {
582
590
  const search = this.inboxSearch.trim().toLowerCase();
583
- return this.allInboxThreads.filter((thread) => {
591
+ const filtered = this.allInboxThreads.filter((thread) => {
584
592
  if (!this.inboxShowArchived && thread.archived) return false;
585
593
  if (search && !threadMatchesSearch(this, thread, search)) return false;
586
594
  return true;
587
595
  });
596
+ return sortInboxThreads(filtered, this.inboxSort, this.inboxSortDir);
597
+ }
598
+
599
+ function sortInboxThreads(threads, sort, dir) {
600
+ const mul = dir === "asc" ? 1 : -1;
601
+ return [...threads].sort((a, b) => {
602
+ switch (sort) {
603
+ case "updated":
604
+ return mul * ((b.lastMessage?.id || 0) - (a.lastMessage?.id || 0));
605
+ case "created": {
606
+ const aFirst = a.messages[0]?.id || 0;
607
+ const bFirst = b.messages[0]?.id || 0;
608
+ return mul * (bFirst - aFirst);
609
+ }
610
+ case "name":
611
+ return mul * String(a.peer).localeCompare(String(b.peer));
612
+ case "attention":
613
+ default:
614
+ return compareInboxThreads(a, b) * mul;
615
+ }
616
+ });
588
617
  }
589
618
 
590
619
  function buildInboxThreads(vm) {
@@ -647,16 +676,15 @@
647
676
  const pendingPairInvites = this.pairs.filter((pair) => pair.status === "pending").length;
648
677
  const claimableTasks = countClaimableWaiting(this);
649
678
  const unreadInbox = threads.reduce((sum, thread) => sum + (thread.attention?.unread || 0), 0);
650
- const needsHumanResponse = threads.filter((thread) => thread.attention?.needsHumanResponse).length;
651
679
  const agentQuestions = threads.filter((thread) => thread.attention?.agentQuestion).length;
652
680
 
653
681
  return {
654
682
  unreadInbox,
655
- needsHumanResponse,
683
+ needsHumanResponse: 0,
656
684
  agentQuestions,
657
685
  pendingPairInvites,
658
686
  claimableTasks,
659
- total: unreadInbox + needsHumanResponse + agentQuestions + pendingPairInvites + claimableTasks,
687
+ total: unreadInbox + agentQuestions + pendingPairInvites + claimableTasks,
660
688
  };
661
689
  }
662
690
 
@@ -910,18 +938,16 @@
910
938
 
911
939
  function getThreadAttention(vm, thread) {
912
940
  const lastHumanReplyId = maxMessageId(thread.messages, (msg) => msg.from === HUMAN_AGENT_ID);
913
- const lastInboundId = maxMessageId(thread.messages, isHumanInboundMessage);
914
941
  const unread = thread.messages.filter((msg) => isUnreadHumanMessage(vm, thread.peer, msg)).length;
915
- const needsHumanResponse = lastInboundId > lastHumanReplyId;
916
942
  const agentQuestion = thread.messages.some((msg) =>
917
943
  isHumanInboundMessage(msg) && msg.id > lastHumanReplyId && messageLooksLikeQuestion(msg)
918
944
  );
919
945
 
920
946
  return {
921
947
  unread,
922
- needsHumanResponse,
948
+ needsHumanResponse: false,
923
949
  agentQuestion,
924
- score: unread * 10 + (needsHumanResponse ? 5 : 0) + (agentQuestion ? 3 : 0),
950
+ score: unread * 10 + (agentQuestion ? 3 : 0),
925
951
  };
926
952
  }
927
953
 
@@ -1219,6 +1245,7 @@
1219
1245
  agentStatusClass,
1220
1246
  severityClass,
1221
1247
  agentStatusTitle,
1248
+ isBuiltInAgent,
1222
1249
  agentChannels,
1223
1250
  channelPresence,
1224
1251
  integrationPresence,
@@ -1301,18 +1328,16 @@
1301
1328
  const claimableTasks = countAgentClaimableWaiting(this, agent);
1302
1329
  const attention = {
1303
1330
  unread: thread?.attention?.unread || 0,
1304
- needsHumanResponse: Boolean(thread?.attention?.needsHumanResponse),
1331
+ needsHumanResponse: false,
1305
1332
  agentQuestion: Boolean(thread?.attention?.agentQuestion),
1306
1333
  pendingPairInvite,
1307
1334
  claimableTasks,
1308
1335
  };
1309
1336
  attention.total = attention.unread +
1310
- (attention.needsHumanResponse ? 1 : 0) +
1311
1337
  (attention.agentQuestion ? 1 : 0) +
1312
1338
  (attention.pendingPairInvite ? 1 : 0) +
1313
1339
  attention.claimableTasks;
1314
1340
  attention.score = attention.unread * 10 +
1315
- (attention.needsHumanResponse ? 5 : 0) +
1316
1341
  (attention.agentQuestion ? 3 : 0) +
1317
1342
  (attention.pendingPairInvite ? 4 : 0) +
1318
1343
  attention.claimableTasks * 2;
@@ -1322,7 +1347,6 @@
1322
1347
  function emptyAttention() {
1323
1348
  return {
1324
1349
  unread: 0,
1325
- needsHumanResponse: false,
1326
1350
  agentQuestion: false,
1327
1351
  pendingPairInvite: false,
1328
1352
  claimableTasks: 0,
@@ -1335,7 +1359,6 @@
1335
1359
  const attention = agentAttention.call(this, agent);
1336
1360
  const parts = [];
1337
1361
  if (attention.unread) parts.push(`${attention.unread} unread`);
1338
- if (attention.needsHumanResponse) parts.push("needs human response");
1339
1362
  if (attention.agentQuestion) parts.push("agent asked a question");
1340
1363
  if (attention.pendingPairInvite) parts.push("pair invite pending");
1341
1364
  if (attention.claimableTasks) parts.push(`${attention.claimableTasks} claimable waiting`);
@@ -1343,6 +1366,9 @@
1343
1366
  }
1344
1367
 
1345
1368
  function agentType(agent) {
1369
+ if (agent?.id === HUMAN_AGENT_ID) return "user";
1370
+ if (agent?.id === "system") return "system";
1371
+
1346
1372
  const values = [
1347
1373
  ...(agent?.tags || []),
1348
1374
  agent?.meta?.provider,
@@ -1397,18 +1423,13 @@
1397
1423
  return { label: agent?.status === "idle" ? "idle" : "ready", tone: "success", icon: "ti-circle-check", stale, reconnecting, badges: presenceBadges(attention, pair, {}) };
1398
1424
  }
1399
1425
 
1400
- function presenceBadges(attention, pair, flags) {
1426
+ function presenceBadges(attention, pair, _flags) {
1401
1427
  const badges = [];
1402
- if (flags.reconnecting) badges.push({ label: "reconnecting", className: "bg-danger-lt" });
1403
- if (flags.starting) badges.push({ label: "online, not ready", className: "bg-warning-lt" });
1404
- if (flags.busy) badges.push({ label: "busy in turn", className: "bg-warning-lt" });
1405
1428
  if (pair?.status === "active") badges.push({ label: "paired", className: "bg-success-lt" });
1406
1429
  if (pair?.status === "pending") badges.push({ label: "pair invite pending", className: "bg-warning-lt" });
1407
1430
  if (attention.unread) badges.push({ label: attention.unread + " unread", className: "bg-danger-lt" });
1408
- if (attention.needsHumanResponse) badges.push({ label: "needs response", className: "bg-warning-lt" });
1409
1431
  if (attention.agentQuestion) badges.push({ label: "question", className: "bg-info-lt" });
1410
1432
  if (attention.claimableTasks) badges.push({ label: attention.claimableTasks + " claimable", className: "bg-orange-lt" });
1411
- if (!badges.length && !flags.offline) badges.push({ label: "ready", className: "bg-success-lt" });
1412
1433
  return badges;
1413
1434
  }
1414
1435
 
package/public/index.html CHANGED
@@ -74,6 +74,24 @@
74
74
  background: rgba(var(--tblr-warning-rgb), 0.12);
75
75
  border-color: rgba(var(--tblr-warning-rgb), 0.25);
76
76
  }
77
+ .agent-type-icon.claude .claude-sol {
78
+ display: inline-block;
79
+ width: 14px;
80
+ height: 14px;
81
+ background-color: currentColor;
82
+ -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='black' transform='translate(12,12)'%3E%3Crect x='-1.8' y='-10' width='3.6' height='7' rx='1.8'/%3E%3Crect x='-1.8' y='-10' width='3.6' height='7' rx='1.8' transform='rotate(60)'/%3E%3Crect x='-1.8' y='-10' width='3.6' height='7' rx='1.8' transform='rotate(120)'/%3E%3Crect x='-1.8' y='-10' width='3.6' height='7' rx='1.8' transform='rotate(180)'/%3E%3Crect x='-1.8' y='-10' width='3.6' height='7' rx='1.8' transform='rotate(240)'/%3E%3Crect x='-1.8' y='-10' width='3.6' height='7' rx='1.8' transform='rotate(300)'/%3E%3C/g%3E%3C/svg%3E") center/contain no-repeat;
83
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='black' transform='translate(12,12)'%3E%3Crect x='-1.8' y='-10' width='3.6' height='7' rx='1.8'/%3E%3Crect x='-1.8' y='-10' width='3.6' height='7' rx='1.8' transform='rotate(60)'/%3E%3Crect x='-1.8' y='-10' width='3.6' height='7' rx='1.8' transform='rotate(120)'/%3E%3Crect x='-1.8' y='-10' width='3.6' height='7' rx='1.8' transform='rotate(180)'/%3E%3Crect x='-1.8' y='-10' width='3.6' height='7' rx='1.8' transform='rotate(240)'/%3E%3Crect x='-1.8' y='-10' width='3.6' height='7' rx='1.8' transform='rotate(300)'/%3E%3C/g%3E%3C/svg%3E") center/contain no-repeat;
84
+ }
85
+ .agent-type-icon.user {
86
+ color: var(--tblr-green);
87
+ background: rgba(var(--tblr-green-rgb), 0.12);
88
+ border-color: rgba(var(--tblr-green-rgb), 0.25);
89
+ }
90
+ .agent-type-icon.system {
91
+ color: var(--tblr-purple);
92
+ background: rgba(var(--tblr-purple-rgb), 0.12);
93
+ border-color: rgba(var(--tblr-purple-rgb), 0.25);
94
+ }
77
95
  .agent-type-icon.agent {
78
96
  color: var(--tblr-secondary);
79
97
  background: var(--tblr-bg-surface-secondary);
@@ -389,14 +407,6 @@
389
407
  </div>
390
408
  </button>
391
409
  </div>
392
- <div class="col-md-6 col-xl">
393
- <button class="card attention-card w-100 text-start" :class="{ 'attention-empty': attentionSummary.needsHumanResponse === 0 }" @click="switchView('inbox')">
394
- <div class="card-body py-3">
395
- <div class="text-secondary small">Needs response</div>
396
- <div class="h2 mb-0" :class="attentionSummary.needsHumanResponse ? 'text-warning' : ''" x-text="attentionSummary.needsHumanResponse"></div>
397
- </div>
398
- </button>
399
- </div>
400
410
  <div class="col-md-6 col-xl">
401
411
  <button class="card attention-card w-100 text-start" :class="{ 'attention-empty': attentionSummary.agentQuestions === 0 }" @click="switchView('inbox')">
402
412
  <div class="card-body py-3">
@@ -597,7 +607,7 @@
597
607
  </span>
598
608
  </template>
599
609
  </div>
600
- <div class="text-secondary small mt-1" x-text="'Last seen: ' + timeAgo(a.lastSeen)"></div>
610
+ <div class="text-secondary small mt-1" x-show="!isBuiltInAgent(a)" x-text="'Last seen: ' + timeAgo(a.lastSeen)"></div>
601
611
  </div>
602
612
  <div class="agent-actions d-flex gap-1">
603
613
  <button class="btn btn-sm btn-ghost-secondary p-1" title="Send message" @click.stop="openComposeToAgent(a)">
@@ -801,10 +811,18 @@
801
811
  <div class="d-flex align-items-center mb-3 gap-2 flex-wrap">
802
812
  <h2 class="page-title mb-0">Inbox</h2>
803
813
  <span class="badge bg-danger-lt" x-show="attentionSummary.unreadInbox > 0" x-text="attentionSummary.unreadInbox + ' unread'"></span>
804
- <span class="badge bg-warning-lt" x-show="attentionSummary.needsHumanResponse > 0" x-text="attentionSummary.needsHumanResponse + ' need response'"></span>
805
814
  <span class="badge bg-info-lt" x-show="attentionSummary.agentQuestions > 0" x-text="attentionSummary.agentQuestions + ' questions'"></span>
806
815
  <div class="ms-auto d-flex gap-2 align-items-center flex-wrap">
807
- <input type="search" class="form-control form-control-sm" style="width: 220px" placeholder="Search inbox" x-model.debounce.200ms="inboxSearch">
816
+ <input type="search" class="form-control form-control-sm" style="width: 180px" placeholder="Search inbox" x-model.debounce.200ms="inboxSearch">
817
+ <select class="form-select form-select-sm" style="width: 130px" x-model="inboxSort">
818
+ <option value="attention">Sort: Attention</option>
819
+ <option value="updated">Sort: Updated</option>
820
+ <option value="created">Sort: Created</option>
821
+ <option value="name">Sort: Name</option>
822
+ </select>
823
+ <button class="btn btn-sm btn-ghost-secondary" @click="inboxSortDir = inboxSortDir === 'asc' ? 'desc' : 'asc'">
824
+ <i class="ti" :class="inboxSortDir === 'asc' ? 'ti-sort-ascending' : 'ti-sort-descending'"></i>
825
+ </button>
808
826
  <label class="form-check form-switch mb-0">
809
827
  <input type="checkbox" class="form-check-input" x-model="inboxShowArchived">
810
828
  <span class="form-check-label small">Archived</span>
@@ -876,10 +894,11 @@
876
894
  <span class="text-secondary small ms-auto" x-text="timeAgo(thread.lastMessage?.createdAt)"></span>
877
895
  </div>
878
896
  <div class="text-secondary small text-truncate mt-1 inbox-thread-snippet" x-text="messagePreview(thread.lastMessage)"></div>
879
- <div class="attention-badges d-flex gap-1 mt-1 flex-wrap" x-show="thread.attention?.score > 0">
880
- <span class="badge bg-warning-lt" x-show="thread.attention?.needsHumanResponse">needs response</span>
881
- <span class="badge bg-info-lt" x-show="thread.attention?.agentQuestion">agent asked a question</span>
882
- </div>
897
+ <template x-if="thread.attention?.agentQuestion">
898
+ <div class="d-flex gap-1 mt-1 flex-wrap">
899
+ <span class="badge bg-info-lt">agent asked a question</span>
900
+ </div>
901
+ </template>
883
902
  <div class="d-flex align-items-center gap-1 mt-2">
884
903
  <span class="badge bg-secondary-lt" x-show="thread.archived">archived</span>
885
904
  <span class="badge bg-primary-lt" x-show="thread.draft">draft</span>
@@ -912,7 +931,6 @@
912
931
  <h3 class="card-title mb-0 text-truncate" x-text="conversationTitle(selectedInboxThreadData)"></h3>
913
932
  <div class="attention-badges d-flex gap-1 flex-wrap">
914
933
  <span class="badge bg-danger-lt" x-show="selectedInboxThreadData.attention?.unread" x-text="selectedInboxThreadData.attention.unread + ' unread'"></span>
915
- <span class="badge bg-warning-lt" x-show="selectedInboxThreadData.attention?.needsHumanResponse">needs response</span>
916
934
  <span class="badge bg-info-lt" x-show="selectedInboxThreadData.attention?.agentQuestion">agent asked a question</span>
917
935
  <span class="badge bg-secondary-lt" x-show="selectedInboxThreadData.archived">archived</span>
918
936
  </div>
@@ -1603,7 +1621,6 @@
1603
1621
  <div class="fw-bold small mb-1">Needs attention</div>
1604
1622
  <div class="attention-badges d-flex gap-1 flex-wrap">
1605
1623
  <span class="badge bg-danger-lt" x-show="agentAttention(selectedAgentDetail).unread" x-text="agentAttention(selectedAgentDetail).unread + ' unread'"></span>
1606
- <span class="badge bg-warning-lt" x-show="agentAttention(selectedAgentDetail).needsHumanResponse">needs human response</span>
1607
1624
  <span class="badge bg-info-lt" x-show="agentAttention(selectedAgentDetail).agentQuestion">agent asked a question</span>
1608
1625
  <span class="badge bg-warning-lt" x-show="agentAttention(selectedAgentDetail).pendingPairInvite">pair invite pending</span>
1609
1626
  <span class="badge bg-orange-lt" x-show="agentAttention(selectedAgentDetail).claimableTasks" x-text="agentAttention(selectedAgentDetail).claimableTasks + ' claimable waiting'"></span>
@@ -1619,15 +1636,14 @@
1619
1636
  <template x-for="badge in agentPresenceBadges(selectedAgentDetail)" :key="badge.label">
1620
1637
  <span class="badge ms-1" :class="badge.className" x-text="badge.label"></span>
1621
1638
  </template>
1622
- <template x-if="selectedAgentDetail.ready">
1623
- <span class="badge bg-success-lt ms-1">ready</span>
1624
- </template>
1625
1639
  </div>
1626
1640
  </div>
1627
- <div class="detail-row mb-2">
1628
- <div class="text-secondary small">Last seen</div>
1629
- <div class="small" x-text="timeAgo(selectedAgentDetail.lastSeen)"></div>
1630
- </div>
1641
+ <template x-if="!isBuiltInAgent(selectedAgentDetail)">
1642
+ <div class="detail-row mb-2">
1643
+ <div class="text-secondary small">Last seen</div>
1644
+ <div class="small" x-text="timeAgo(selectedAgentDetail.lastSeen)"></div>
1645
+ </div>
1646
+ </template>
1631
1647
  <div class="detail-row mb-2">
1632
1648
  <div class="text-secondary small">Created</div>
1633
1649
  <div class="small" x-text="fmtTime(selectedAgentDetail.createdAt)"></div>