@decentnetwork/beagle 0.1.107 → 0.1.109

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.
Files changed (2) hide show
  1. package/dist/desktop/app.js +380 -50
  2. package/package.json +2 -2
@@ -1,4 +1,4 @@
1
- window.__DK_UI_VERSION="0.1.107";
1
+ window.__DK_UI_VERSION="0.1.109";
2
2
  "use strict";
3
3
  (() => {
4
4
  // packages/beagle-ui/dist/beagle-ui.js
@@ -510,23 +510,39 @@ window.__DK_UI_VERSION="0.1.107";
510
510
  name: "help",
511
511
  usage: "/help",
512
512
  summary: "List what the Assistant can do.",
513
- params: { type: "object", properties: {}, required: [] },
513
+ params: {
514
+ type: "object",
515
+ properties: {},
516
+ required: []
517
+ },
514
518
  confirm: "none",
515
519
  scope: "assistant"
516
520
  },
517
521
  {
518
522
  name: "friends-list",
519
523
  usage: "/friends list [filter\u2026]",
520
- summary: "Show friends as a table: name, last heard from, messages in/out. With no filter, everyone.",
524
+ summary: "Show friends as a list: name, last heard from, messages in/out. With no filter, everyone. Service peers are never included \u2014 they are /nodes.",
521
525
  params: {
522
526
  type: "object",
523
527
  properties: {
524
528
  filters: {
525
529
  type: "array",
526
- items: { enum: ["never-messaged", "never-replied", "silent", "pending"] },
530
+ items: {
531
+ enum: [
532
+ "never-messaged",
533
+ "never-replied",
534
+ "silent",
535
+ "pending"
536
+ ]
537
+ },
527
538
  description: "Several filters are OR'd together."
528
539
  },
529
- silentDays: { type: "integer", minimum: 1, default: 90, description: "Argument of the silent filter: nothing from them in this many days." }
540
+ silentDays: {
541
+ type: "integer",
542
+ minimum: 1,
543
+ default: 90,
544
+ description: "Argument of the silent filter: nothing from them in this many days."
545
+ }
530
546
  },
531
547
  required: []
532
548
  },
@@ -536,18 +552,94 @@ window.__DK_UI_VERSION="0.1.107";
536
552
  {
537
553
  name: "friends-remove",
538
554
  usage: "/friends remove <filter\u2026>",
539
- summary: "Same table with a tick per row and a Remove button. Nothing is removed by typing alone.",
555
+ summary: "Same list with a tick per row and a Remove button. Nothing is removed by typing alone. Service peers are never matched.",
556
+ params: {
557
+ type: "object",
558
+ properties: {
559
+ filters: {
560
+ type: "array",
561
+ items: {
562
+ enum: [
563
+ "never-messaged",
564
+ "never-replied",
565
+ "silent",
566
+ "pending"
567
+ ]
568
+ },
569
+ minItems: 1
570
+ },
571
+ silentDays: {
572
+ type: "integer",
573
+ minimum: 1,
574
+ default: 90
575
+ }
576
+ },
577
+ required: [
578
+ "filters"
579
+ ]
580
+ },
581
+ confirm: "list",
582
+ scope: "assistant"
583
+ },
584
+ {
585
+ name: "nodes-list",
586
+ usage: "/nodes list [filter\u2026]",
587
+ summary: "Service peers \u2014 network nodes and agents \u2014 with their kind and role. The same filters as /friends list.",
588
+ params: {
589
+ type: "object",
590
+ properties: {
591
+ filters: {
592
+ type: "array",
593
+ items: {
594
+ enum: [
595
+ "never-messaged",
596
+ "never-replied",
597
+ "silent",
598
+ "pending"
599
+ ]
600
+ },
601
+ description: "Several filters are OR'd together."
602
+ },
603
+ silentDays: {
604
+ type: "integer",
605
+ minimum: 1,
606
+ default: 90
607
+ }
608
+ },
609
+ required: []
610
+ },
611
+ confirm: "none",
612
+ scope: "assistant"
613
+ },
614
+ {
615
+ name: "nodes-remove",
616
+ usage: "/nodes remove <filter\u2026>",
617
+ summary: "The same table with a tick per row and a Remove button, over service peers only.",
540
618
  params: {
541
619
  type: "object",
542
620
  properties: {
543
621
  filters: {
544
622
  type: "array",
545
- items: { enum: ["never-messaged", "never-replied", "silent", "pending"] },
623
+ items: {
624
+ enum: [
625
+ "never-messaged",
626
+ "never-replied",
627
+ "silent",
628
+ "pending"
629
+ ]
630
+ },
631
+ description: "Several filters are OR'd together.",
546
632
  minItems: 1
547
633
  },
548
- silentDays: { type: "integer", minimum: 1, default: 90 }
634
+ silentDays: {
635
+ type: "integer",
636
+ minimum: 1,
637
+ default: 90
638
+ }
549
639
  },
550
- required: ["filters"]
640
+ required: [
641
+ "filters"
642
+ ]
551
643
  },
552
644
  confirm: "list",
553
645
  scope: "assistant"
@@ -558,8 +650,15 @@ window.__DK_UI_VERSION="0.1.107";
558
650
  summary: "Friends whose name, alias, description or ens name contains the text.",
559
651
  params: {
560
652
  type: "object",
561
- properties: { text: { type: "string", minLength: 1 } },
562
- required: ["text"]
653
+ properties: {
654
+ text: {
655
+ type: "string",
656
+ minLength: 1
657
+ }
658
+ },
659
+ required: [
660
+ "text"
661
+ ]
563
662
  },
564
663
  confirm: "none",
565
664
  scope: "assistant"
@@ -571,10 +670,19 @@ window.__DK_UI_VERSION="0.1.107";
571
670
  params: {
572
671
  type: "object",
573
672
  properties: {
574
- friend: { type: "string", description: "Matches name, alias or userid prefix. Ambiguous = nothing happens, the candidates are listed." },
575
- alias: { type: "string", minLength: 1 }
673
+ friend: {
674
+ type: "string",
675
+ description: "Matches name, alias or userid prefix. Ambiguous = nothing happens, the candidates are listed."
676
+ },
677
+ alias: {
678
+ type: "string",
679
+ minLength: 1
680
+ }
576
681
  },
577
- required: ["friend", "alias"]
682
+ required: [
683
+ "friend",
684
+ "alias"
685
+ ]
578
686
  },
579
687
  confirm: "none",
580
688
  scope: "assistant"
@@ -585,8 +693,14 @@ window.__DK_UI_VERSION="0.1.107";
585
693
  summary: "Stop alerts from one conversation.",
586
694
  params: {
587
695
  type: "object",
588
- properties: { friend: { type: "string" } },
589
- required: ["friend"]
696
+ properties: {
697
+ friend: {
698
+ type: "string"
699
+ }
700
+ },
701
+ required: [
702
+ "friend"
703
+ ]
590
704
  },
591
705
  confirm: "none",
592
706
  scope: "assistant"
@@ -597,8 +711,14 @@ window.__DK_UI_VERSION="0.1.107";
597
711
  summary: "Alerts from that conversation again.",
598
712
  params: {
599
713
  type: "object",
600
- properties: { friend: { type: "string" } },
601
- required: ["friend"]
714
+ properties: {
715
+ friend: {
716
+ type: "string"
717
+ }
718
+ },
719
+ required: [
720
+ "friend"
721
+ ]
602
722
  },
603
723
  confirm: "none",
604
724
  scope: "assistant"
@@ -609,8 +729,14 @@ window.__DK_UI_VERSION="0.1.107";
609
729
  summary: "Sound on a new message.",
610
730
  params: {
611
731
  type: "object",
612
- properties: { on: { type: "boolean" } },
613
- required: ["on"]
732
+ properties: {
733
+ on: {
734
+ type: "boolean"
735
+ }
736
+ },
737
+ required: [
738
+ "on"
739
+ ]
614
740
  },
615
741
  confirm: "none",
616
742
  scope: "assistant"
@@ -621,8 +747,14 @@ window.__DK_UI_VERSION="0.1.107";
621
747
  summary: "Flash the window on a new message.",
622
748
  params: {
623
749
  type: "object",
624
- properties: { on: { type: "boolean" } },
625
- required: ["on"]
750
+ properties: {
751
+ on: {
752
+ type: "boolean"
753
+ }
754
+ },
755
+ required: [
756
+ "on"
757
+ ]
626
758
  },
627
759
  confirm: "none",
628
760
  scope: "assistant"
@@ -630,8 +762,12 @@ window.__DK_UI_VERSION="0.1.107";
630
762
  {
631
763
  name: "status",
632
764
  usage: "/status",
633
- summary: "Friends, pending requests, how far back the history on this device goes, app version.",
634
- params: { type: "object", properties: {}, required: [] },
765
+ summary: "Friends and service peers counted separately, pending requests, how far back the history on this device goes, app version.",
766
+ params: {
767
+ type: "object",
768
+ properties: {},
769
+ required: []
770
+ },
635
771
  confirm: "none",
636
772
  scope: "assistant"
637
773
  }
@@ -654,7 +790,12 @@ window.__DK_UI_VERSION="0.1.107";
654
790
  usage: "silent [days]",
655
791
  summary: "Nothing from them for this many days (default 90).",
656
792
  guarded: true,
657
- arg: { name: "days", type: "integer", default: 90, minimum: 1 }
793
+ arg: {
794
+ name: "days",
795
+ type: "integer",
796
+ default: 90,
797
+ minimum: 1
798
+ }
658
799
  },
659
800
  {
660
801
  name: "pending",
@@ -672,8 +813,142 @@ window.__DK_UI_VERSION="0.1.107";
672
813
  guard: {
673
814
  recentDays: 14,
674
815
  applies: "Filters marked guarded never match a friend accepted (or, with no acceptance stamp, requested) less than recentDays ago; the result says how many were skipped."
816
+ },
817
+ servicePeers: {
818
+ file: "service-peers.json",
819
+ loader: "loadServicePeers()",
820
+ rule: "A peer is a service peer if and only if its userid is in this list. The Assistant never infers it from a name or from having no messages. /friends excludes them entirely; /nodes is only them.",
821
+ later: "A dora / ENS / bridge / directory service returns the same shape; the bundled file is v1."
675
822
  }
676
823
  };
824
+ var service_peers_default = {
825
+ version: 1,
826
+ source: "static",
827
+ updated: "2026-09-24",
828
+ note: "Service providers and network nodes: peers that are infrastructure or agents, not people. The Assistant manages them with /nodes, never /friends. Later this list comes from a service (dora / ENS / bridge / directory) in this same shape; until then it ships inside the app.",
829
+ peers: [
830
+ {
831
+ userid: "7xmbDbouSYEA5mbAgWxV2BTwjc59XGcVPqXSZx6VibUd",
832
+ name: "lens",
833
+ kind: "node",
834
+ role: "bridge + web host"
835
+ },
836
+ {
837
+ userid: "4uNnLnAQHVJ7td657MDaz4UnEuhnTW9sRAPSVeAEEQe4",
838
+ name: "tokyo",
839
+ kind: "node",
840
+ role: "exit + relay + dora + meeting"
841
+ },
842
+ {
843
+ userid: "5Aj6uQMd1cNRb9cGT4AwHCN4RdVZjfaGLtgKGhdP1LzN",
844
+ name: "cn",
845
+ kind: "node",
846
+ role: "exit"
847
+ },
848
+ {
849
+ userid: "DZN2L9RV1YkHjqGHMTA6juZhskKA65AD2RyPt4umZVc7",
850
+ name: "callpass",
851
+ kind: "node",
852
+ role: "server"
853
+ },
854
+ {
855
+ userid: "BcGvk2h5XCxBZWJrYwzKpZ8GbxaZRoakEzt2pQpathyA",
856
+ name: "gojipower",
857
+ kind: "node",
858
+ role: "server"
859
+ },
860
+ {
861
+ userid: "BeMbuKf1poh3U4rjw3eeUAKZsbN2qBSjVZdPSvehmHsw",
862
+ name: "snoopy",
863
+ kind: "node",
864
+ role: "dora + meeting + openclaw host"
865
+ },
866
+ {
867
+ userid: "CzwUZv6LfiGM7EXd9vXr6DutG8ASkA3TnNbqQzjKRJN5",
868
+ name: "GFAX",
869
+ kind: "node",
870
+ role: "server"
871
+ },
872
+ {
873
+ userid: "72a4A7awtn6BqZnBt3eNhZH9MdYXRkM6Ht5HG2sqoiKR",
874
+ name: "nv4090-wsl",
875
+ kind: "node",
876
+ role: "GPU node"
877
+ },
878
+ {
879
+ userid: "6riqxxw18vjwXZEEtFUxbgNBMMe1dcv2kVHqVRQdihjE",
880
+ name: "node-3145",
881
+ kind: "node",
882
+ role: "GPU node (4090w)"
883
+ },
884
+ {
885
+ userid: "6D1PLSVqbSpcxnDMAd8ZXLWycYveeP2hU4g3igDQEqA7",
886
+ name: "node-5123",
887
+ kind: "node",
888
+ role: "node"
889
+ },
890
+ {
891
+ userid: "5EU9Ee3Vai1XwTr4pGaJFt4wmNVPUoYnJsdBM9LwNhrJ",
892
+ name: "node-2998",
893
+ kind: "node",
894
+ role: "node"
895
+ },
896
+ {
897
+ userid: "GujioyKQRucQgw8mwMPNWtYTdNdrooCuFq3R1D4RqjCW",
898
+ name: "beagle-help",
899
+ kind: "agent",
900
+ role: "product support"
901
+ },
902
+ {
903
+ userid: "FhbohSLrj5UjdyFKCEYNeEWAPq3QD9hRg6hsso5ipag2",
904
+ name: "help.beagles.eth",
905
+ kind: "agent",
906
+ role: "bridge agent"
907
+ },
908
+ {
909
+ userid: "9EaFGWuXH2UfREzYMkPPitzjoH6E8tvey4JvqTKtHFJh",
910
+ name: "openclaw",
911
+ kind: "agent",
912
+ role: "OpenClaw agent"
913
+ },
914
+ {
915
+ userid: "6z2o8ojPVjFGRzcffUdd5btvDYAt4tLuwJ8j1jvMBU8p",
916
+ name: "ClawAlan",
917
+ kind: "agent",
918
+ role: "OpenClaw agent on snoopy"
919
+ },
920
+ {
921
+ userid: "G7s3YjVfNLTY5pKBd83qQLAdzDddS7ZstsfrUySwK2Cb",
922
+ name: "interview",
923
+ kind: "agent",
924
+ role: "interview"
925
+ },
926
+ {
927
+ userid: "6jbXjrUWJ4zYvG8BDLnu8KVpoL7rhsdHYSwWER6WTfdt",
928
+ name: "interview editor",
929
+ kind: "agent",
930
+ role: "interview"
931
+ },
932
+ {
933
+ userid: "8BGemh6uqmh4X7KpnPVMeMSU8hZwAc6ZDo4MBRMFrq2L",
934
+ name: "readyplayer",
935
+ kind: "agent",
936
+ role: "readyplayer"
937
+ },
938
+ {
939
+ userid: "Ev1XmWRwtjQf8n33wgTwEhzXnYEyfqTyanehuDE6nczF",
940
+ name: "ReadyPlayer membership manager",
941
+ kind: "agent",
942
+ role: "readyplayer"
943
+ },
944
+ {
945
+ userid: "7xLPH2kjk7Nn8GrsAt7TYwa6b1WMPLkC2XRx7BzhiXYT",
946
+ name: "ai-btc-trader",
947
+ kind: "agent",
948
+ role: "btc signals (245)"
949
+ }
950
+ ]
951
+ };
677
952
  var ASSISTANT_ID = "__assistant__";
678
953
  var DAY_MS = 864e5;
679
954
  var RECENT_DAYS = 14;
@@ -688,11 +963,11 @@ window.__DK_UI_VERSION="0.1.107";
688
963
  if (!parts.length)
689
964
  return null;
690
965
  const head = parts[0].toLowerCase();
691
- if (head === "friends") {
966
+ if (head === "friends" || head === "nodes") {
692
967
  const sub = (parts[1] || "").toLowerCase();
693
968
  if (sub === "list" || sub === "remove")
694
- return { ok: true, name: `friends-${sub}`, args: parts.slice(2) };
695
- return { ok: false, name: "friends", args: parts.slice(1), error: "friends-subcommand" };
969
+ return { ok: true, name: `${head}-${sub}`, args: parts.slice(2) };
970
+ return { ok: false, name: head, args: parts.slice(1), error: `${head}-subcommand` };
696
971
  }
697
972
  return { ok: true, name: head, args: parts.slice(1) };
698
973
  }
@@ -719,6 +994,30 @@ window.__DK_UI_VERSION="0.1.107";
719
994
  }
720
995
  return { filters, silentDays, unknown };
721
996
  }
997
+ function servicePeerMap(list) {
998
+ const map = /* @__PURE__ */ new Map();
999
+ const peers = list && list.peers || [];
1000
+ for (const p of peers) {
1001
+ if (p && typeof p.userid === "string" && p.userid) {
1002
+ map.set(p.userid, { kind: p.kind === "agent" ? "agent" : "node", role: p.role || "", name: p.name || "" });
1003
+ }
1004
+ }
1005
+ return map;
1006
+ }
1007
+ function partitionPeers(friends, svcMap) {
1008
+ const people = [];
1009
+ const nodes = [];
1010
+ for (const f of friends != null ? friends : []) {
1011
+ if (!f)
1012
+ continue;
1013
+ const svc = svcMap && svcMap.get(f.userid);
1014
+ if (svc)
1015
+ nodes.push(Object.assign({}, f, { kind: svc.kind, role: svc.role, service: true }));
1016
+ else
1017
+ people.push(f);
1018
+ }
1019
+ return { people, nodes };
1020
+ }
722
1021
  var acceptedOrRequestedAt = (f) => f.acceptedAt || f.requestedAt || 0;
723
1022
  function isRecent(friend, now, recentDays = RECENT_DAYS) {
724
1023
  const at = acceptedOrRequestedAt(friend);
@@ -787,6 +1086,9 @@ window.__DK_UI_VERSION="0.1.107";
787
1086
  return { match: partial[0], candidates: partial };
788
1087
  return { match: null, candidates: partial };
789
1088
  }
1089
+ function loadServicePeers() {
1090
+ return service_peers_default;
1091
+ }
790
1092
  var ASSISTANT_PEER = {
791
1093
  id: ASSISTANT_ID,
792
1094
  userId: ASSISTANT_ID,
@@ -825,11 +1127,14 @@ window.__DK_UI_VERSION="0.1.107";
825
1127
  var nameOf = (f) => f.alias || f.name || f.userid;
826
1128
  function sinceLine(historySince, lang) {
827
1129
  if (!historySince)
828
- return lang === "zh" ? "_\u57FA\u4E8E\u672C\u8BBE\u5907\u4E0A\u7684\u6D88\u606F\uFF08\u672C\u673A\u6682\u65E0\u5386\u53F2\u8BB0\u5F55\uFF09_" : "_based on messages on this device (no history here yet)_";
829
- return lang === "zh" ? `_\u57FA\u4E8E\u672C\u8BBE\u5907\u81EA ${fmtDate(historySince, lang)} \u8D77\u7684\u6D88\u606F_` : `_based on messages on this device since ${fmtDate(historySince, lang)}_`;
1130
+ return lang === "zh" ? "*\u57FA\u4E8E\u672C\u8BBE\u5907\u4E0A\u7684\u6D88\u606F\uFF08\u672C\u673A\u6682\u65E0\u5386\u53F2\u8BB0\u5F55\uFF09*" : "*based on messages on this device (no history here yet)*";
1131
+ return lang === "zh" ? `*\u57FA\u4E8E\u672C\u8BBE\u5907\u81EA ${fmtDate(historySince, lang)} \u8D77\u7684\u6D88\u606F*` : `*based on messages on this device since ${fmtDate(historySince, lang)}*`;
830
1132
  }
831
1133
  function friendTable(rows, now, lang) {
832
- return rows.map((f) => lang === "zh" ? `- **${nameOf(f)}** \u2014 \u6700\u540E\u6536\u5230 ${fmtAgo(f.lastInboundTs, now, lang)} \xB7 \u6536 ${f.inboundCount || 0} / \u53D1 ${f.outboundCount || 0}` : `- **${nameOf(f)}** \u2014 last heard ${fmtAgo(f.lastInboundTs, now, lang)} \xB7 in ${f.inboundCount || 0} / out ${f.outboundCount || 0}`).join("\n");
1134
+ return rows.map((f) => {
1135
+ const what = f.service ? ` \`${f.kind}${f.role ? " \xB7 " + f.role : ""}\`` : "";
1136
+ return lang === "zh" ? `- **${nameOf(f)}**${what} \u2014 \u6700\u540E\u6536\u5230 ${fmtAgo(f.lastInboundTs, now, lang)} \xB7 \u6536 ${f.inboundCount || 0} / \u53D1 ${f.outboundCount || 0}` : `- **${nameOf(f)}**${what} \u2014 last heard ${fmtAgo(f.lastInboundTs, now, lang)} \xB7 in ${f.inboundCount || 0} / out ${f.outboundCount || 0}`;
1137
+ }).join("\n");
833
1138
  }
834
1139
  function helpText(lang) {
835
1140
  const intro = lang === "zh" ? "**Beagle \u52A9\u624B** \u2014 \u53EA\u5728\u672C\u673A\u8FD0\u884C\uFF0C\u4E0D\u4F1A\u628A\u4EFB\u4F55\u5185\u5BB9\u53D1\u5230\u7F51\u7EDC\u4E0A\u3002\n\n\u53EF\u7528\u547D\u4EE4\uFF1A" : "**Beagle Assistant** \u2014 runs on this machine only; nothing here is sent over the network.\n\nCommands:";
@@ -898,7 +1203,15 @@ ${tail}`;
898
1203
  }, [lang]);
899
1204
  const fetchFriends = async () => {
900
1205
  const r = await fetch("/api/assistant/friends").then((x) => x.json());
901
- return { friends: r.friends || [], historySince: r.historySince || null, now: r.now || Date.now(), ver: r.beagleVer || "" };
1206
+ const { people, nodes } = partitionPeers(r.friends || [], servicePeerMap(loadServicePeers()));
1207
+ return {
1208
+ friends: people,
1209
+ nodes,
1210
+ all: r.friends || [],
1211
+ historySince: r.historySince || null,
1212
+ now: r.now || Date.now(),
1213
+ ver: r.beagleVer || ""
1214
+ };
902
1215
  };
903
1216
  const listReply = (rows, skipped, data, title) => {
904
1217
  if (!rows.length) {
@@ -910,9 +1223,9 @@ ${sinceLine(data.historySince, lang)}`;
910
1223
  }
911
1224
  const skip = skipped.length ? lang === "zh" ? `
912
1225
 
913
- _\u8DF3\u8FC7\u4E86 ${skipped.length} \u4F4D\u6700\u8FD1 ${RECENT_DAYS} \u5929\u5185\u6DFB\u52A0\u7684\u597D\u53CB\u3002_` : `
1226
+ *\u8DF3\u8FC7\u4E86 ${skipped.length} \u4F4D\u6700\u8FD1 ${RECENT_DAYS} \u5929\u5185\u6DFB\u52A0\u7684\u597D\u53CB\u3002*` : `
914
1227
 
915
- _${skipped.length} friend${skipped.length > 1 ? "s" : ""} added in the last ${RECENT_DAYS} days ${skipped.length > 1 ? "were" : "was"} skipped._` : "";
1228
+ *${skipped.length} friend${skipped.length > 1 ? "s" : ""} added in the last ${RECENT_DAYS} days ${skipped.length > 1 ? "were" : "was"} skipped.*` : "";
916
1229
  return `${title}
917
1230
 
918
1231
  ${friendTable(rows, data.now, lang)}${skip}
@@ -930,7 +1243,7 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
930
1243
  return append(asMsg("them", lang === "zh" ? "\u6211\u53EA\u8BA4\u8BC6\u4EE5 `/` \u5F00\u5934\u7684\u547D\u4EE4\u3002\u8F93\u5165 `/help` \u770B\u770B\u80FD\u505A\u4EC0\u4E48\u3002" : "I only understand commands starting with `/`. Type `/help` to see them."));
931
1244
  }
932
1245
  if (cmd.ok === false) {
933
- return append(asMsg("them", lang === "zh" ? "`/friends` \u9700\u8981 `list` \u6216 `remove`\uFF0C\u4F8B\u5982 `/friends list silent 90`\u3002" : "`/friends` needs `list` or `remove`, for example `/friends list silent 90`."));
1246
+ return append(asMsg("them", lang === "zh" ? `\`/${cmd.name}\` \u9700\u8981 \`list\` \u6216 \`remove\`\uFF0C\u4F8B\u5982 \`/${cmd.name} list silent 90\`\u3002` : `\`/${cmd.name}\` needs \`list\` or \`remove\`, for example \`/${cmd.name} list silent 90\`.`));
934
1247
  }
935
1248
  try {
936
1249
  switch (cmd.name) {
@@ -938,37 +1251,48 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
938
1251
  return append(asMsg("them", helpText(lang)));
939
1252
  case "status": {
940
1253
  const d = await fetchFriends();
941
- const pending = d.friends.filter((f) => f.pending).length;
1254
+ const pending = d.all.filter((f) => f.pending).length;
1255
+ const nodeCount = d.nodes.filter((n) => n.kind === "node").length;
1256
+ const agentCount = d.nodes.length - nodeCount;
942
1257
  return append(asMsg("them", lang === "zh" ? `**\u72B6\u6001**
943
1258
 
944
1259
  - \u597D\u53CB\uFF1A${d.friends.length}
1260
+ - \u670D\u52A1\u8282\u70B9\uFF1A${d.nodes.length}\uFF08\u8282\u70B9 ${nodeCount} \xB7 \u667A\u80FD\u4F53 ${agentCount}\uFF09\uFF0C\u7528 \`/nodes list\` \u67E5\u770B
945
1261
  - \u5F85\u5BF9\u65B9\u63A5\u53D7\uFF1A${pending}
946
1262
  - \u672C\u673A\u5386\u53F2\u8D77\u81EA\uFF1A${fmtDate(d.historySince, lang)}
947
1263
  - \u7248\u672C\uFF1A${d.ver}` : `**Status**
948
1264
 
949
1265
  - Friends: ${d.friends.length}
1266
+ - Service peers: ${d.nodes.length} (${nodeCount} node${nodeCount === 1 ? "" : "s"} \xB7 ${agentCount} agent${agentCount === 1 ? "" : "s"}) \u2014 see \`/nodes list\`
950
1267
  - Waiting to be accepted: ${pending}
951
1268
  - History on this device since: ${fmtDate(d.historySince, lang)}
952
1269
  - Version: ${d.ver}`));
953
1270
  }
1271
+ case "nodes-list":
954
1272
  case "friends-list": {
1273
+ const nodesMode = cmd.name === "nodes-list";
955
1274
  const { filters, silentDays, unknown } = parseFilters(cmd.args);
956
1275
  if (unknown.length) {
957
1276
  return append(asMsg("them", lang === "zh" ? `\u4E0D\u8BA4\u8BC6\u7684\u7B5B\u9009\u6761\u4EF6\uFF1A${unknown.join(", ")}\u3002\u53EF\u7528\uFF1A${actions_default.filters.map((f) => f.name).join(", ")}\u3002` : `I don't know the filter${unknown.length > 1 ? "s" : ""} ${unknown.join(", ")}. Try: ${actions_default.filters.map((f) => f.name).join(", ")}.`));
958
1277
  }
959
1278
  const d = await fetchFriends();
960
- const { matched, skippedRecent } = selectFriends(d.friends, filters, { now: d.now, silentDays });
961
- const title = filters.length ? `**${filters.join(" + ")}**${filters.includes("silent") ? ` (${silentDays}d)` : ""} \u2014 ${matched.length}` : lang === "zh" ? `**\u5168\u90E8\u597D\u53CB** \u2014 ${matched.length}` : `**All friends** \u2014 ${matched.length}`;
1279
+ const pool = nodesMode ? d.nodes : d.friends;
1280
+ const { matched, skippedRecent } = selectFriends(pool, filters, { now: d.now, silentDays });
1281
+ const all = nodesMode ? lang === "zh" ? `**\u5168\u90E8\u670D\u52A1\u8282\u70B9** \u2014 ${matched.length}` : `**All service peers** \u2014 ${matched.length}` : lang === "zh" ? `**\u5168\u90E8\u597D\u53CB** \u2014 ${matched.length}` : `**All friends** \u2014 ${matched.length}`;
1282
+ const title = filters.length ? `**${nodesMode ? (lang === "zh" ? "\u670D\u52A1\u8282\u70B9" : "service peers") + ": " : ""}${filters.join(" + ")}**${filters.includes("silent") ? ` (${silentDays}d)` : ""} \u2014 ${matched.length}` : all;
962
1283
  return append(asMsg("them", listReply(matched, skippedRecent, d, title)));
963
1284
  }
1285
+ case "nodes-remove":
964
1286
  case "friends-remove": {
1287
+ const nodesMode = cmd.name === "nodes-remove";
965
1288
  const { filters, silentDays, unknown } = parseFilters(cmd.args);
966
1289
  if (unknown.length || !filters.length) {
967
1290
  return append(asMsg("them", lang === "zh" ? `\u5220\u9664\u9700\u8981\u7B5B\u9009\u6761\u4EF6\uFF0C\u4F8B\u5982 \`/friends remove never-messaged\`\u3002\u53EF\u7528\uFF1A${actions_default.filters.map((f) => f.name).join(", ")}\u3002` : `Removing needs a filter, for example \`/friends remove never-messaged\`. Available: ${actions_default.filters.map((f) => f.name).join(", ")}.`));
968
1291
  }
969
1292
  const d = await fetchFriends();
970
- const { matched, skippedRecent } = selectFriends(d.friends, filters, { now: d.now, silentDays });
971
- const title = lang === "zh" ? `**\u5C06\u8981\u5220\u9664 ${matched.length} \u4F4D\u597D\u53CB** \u2014 \u8BF7\u5148\u6838\u5BF9\u8FD9\u4EFD\u540D\u5355` : `**${matched.length} friend${matched.length === 1 ? "" : "s"} to remove** \u2014 check the list first`;
1293
+ const { matched, skippedRecent } = selectFriends(nodesMode ? d.nodes : d.friends, filters, { now: d.now, silentDays });
1294
+ const what = nodesMode ? lang === "zh" ? "\u4E2A\u670D\u52A1\u8282\u70B9" : `service peer${matched.length === 1 ? "" : "s"}` : lang === "zh" ? "\u4F4D\u597D\u53CB" : `friend${matched.length === 1 ? "" : "s"}`;
1295
+ const title = lang === "zh" ? `**\u5C06\u8981\u5220\u9664 ${matched.length} ${what}** \u2014 \u8BF7\u5148\u6838\u5BF9\u8FD9\u4EFD\u540D\u5355` : `**${matched.length} ${what} to remove** \u2014 check the list first`;
972
1296
  return append(asMsg(
973
1297
  "them",
974
1298
  listReply(matched, skippedRecent, d, title),
@@ -980,7 +1304,7 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
980
1304
  if (!q)
981
1305
  return append(asMsg("them", lang === "zh" ? "\u7528\u6CD5\uFF1A`/find <\u6587\u5B57>`" : "Usage: `/find <text>`"));
982
1306
  const d = await fetchFriends();
983
- const rows = findFriends(d.friends, q);
1307
+ const rows = findFriends(d.friends.concat(d.nodes), q);
984
1308
  const title = lang === "zh" ? `**"${q}"** \u2014 ${rows.length} \u4F4D` : `**"${q}"** \u2014 ${rows.length} found`;
985
1309
  return append(asMsg("them", listReply(rows, [], d, title)));
986
1310
  }
@@ -990,7 +1314,7 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
990
1314
  if (!tok || !alias)
991
1315
  return append(asMsg("them", lang === "zh" ? "\u7528\u6CD5\uFF1A`/rename <\u597D\u53CB> <\u65B0\u540D\u5B57>`" : "Usage: `/rename <friend> <new name>`"));
992
1316
  const d = await fetchFriends();
993
- const { match, candidates } = resolveFriend(d.friends, tok);
1317
+ const { match, candidates } = resolveFriend(d.friends.concat(d.nodes), tok);
994
1318
  if (!match)
995
1319
  return append(asMsg("them", candidates.length ? ambiguous(candidates) : notFound(tok)));
996
1320
  const was = nameOf(match);
@@ -1005,7 +1329,7 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
1005
1329
  if (!tok)
1006
1330
  return append(asMsg("them", `${lang === "zh" ? "\u7528\u6CD5" : "Usage"}: \`/${cmd.name} <friend>\``));
1007
1331
  const d = await fetchFriends();
1008
- const { match, candidates } = resolveFriend(d.friends, tok);
1332
+ const { match, candidates } = resolveFriend(d.friends.concat(d.nodes), tok);
1009
1333
  if (!match)
1010
1334
  return append(asMsg("them", candidates.length ? ambiguous(candidates) : notFound(tok)));
1011
1335
  const isMuted = (muted || []).includes(match.userid);
@@ -2535,7 +2859,11 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
2535
2859
  style: { width: 40, height: 44, flexShrink: 0, border: "none", background: "transparent", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", padding: 0 }
2536
2860
  },
2537
2861
  /* @__PURE__ */ React.createElement(Icon, { name: "chevronLeft", size: 26, stroke: 2.2, color: "var(--accent)" })
2538
- ), /* @__PURE__ */ React.createElement("div", { onClick: () => setPubProfile(true), title: T && T.pubOpen || "View public profile", style: { cursor: "pointer", flexShrink: 0 } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer, size: 34, radius: 8 })), /* @__PURE__ */ React.createElement("div", { style: { minWidth: 0, flex: phone ? 1 : void 0 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, minWidth: 0 } }, /* @__PURE__ */ React.createElement("span", { onClick: () => setPubProfile(true), style: { fontFamily: peer.alias ? "var(--ui)" : "var(--mono)", fontSize: 15, fontWeight: 700, color: "var(--text)", cursor: "pointer", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", minWidth: 0 } }, peer.alias || shortKey(peer.userId, phone ? 6 : 10, phone ? 4 : 6)), peer.app && !phone && /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, "APP"), peer.agent && !phone && /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, "agent"), isGroup && !phone && /* @__PURE__ */ React.createElement(Tag, null, T && T.groupTag || "group"), !phone && !peer.app && /* @__PURE__ */ React.createElement(RouteTag, { peer })), phone ? /* @__PURE__ */ React.createElement("div", { style: { marginTop: 2, display: "flex" } }, /* @__PURE__ */ React.createElement(RouteTag, { peer })) : /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 2 } }, /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: peer.userId, title: peer.userId }, shortKey(peer.userId, 10, 6)), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), /* @__PURE__ */ React.createElement("button", { onClick: () => onOpenNet(peer), title: T && T.openNet || "Network view", style: { background: "none", border: "none", cursor: "pointer", padding: 0, color: "var(--accent)", display: "inline-flex", alignItems: "center" } }, /* @__PURE__ */ React.createElement(Icon, { name: "network", size: 12, stroke: 2 })), /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: peer.ip, title: peer.ip }, peer.ip), peer.address && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), /* @__PURE__ */ React.createElement(
2862
+ ), /* @__PURE__ */ React.createElement("div", { onClick: () => setPubProfile(true), title: T && T.pubOpen || "View public profile", style: { cursor: "pointer", flexShrink: 0 } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer, size: 34, radius: 8 })), /* @__PURE__ */ React.createElement("div", { style: { minWidth: 0, flex: phone ? 1 : void 0 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, minWidth: 0 } }, /* @__PURE__ */ React.createElement("span", { onClick: () => setPubProfile(true), style: { fontFamily: peer.alias ? "var(--ui)" : "var(--mono)", fontSize: 15, fontWeight: 700, color: "var(--text)", cursor: "pointer", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", minWidth: 0 } }, peer.alias || shortKey(peer.userId, phone ? 6 : 10, phone ? 4 : 6)), peer.app && !phone && /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, "APP"), peer.agent && !phone && /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, "agent"), isGroup && !phone && /* @__PURE__ */ React.createElement(Tag, null, T && T.groupTag || "group"), !phone && !peer.app && /* @__PURE__ */ React.createElement(RouteTag, { peer })), peer.app ? (
2863
+ // The internal id is not an address anyone can use and not a name
2864
+ // anyone chose; saying what this row IS is more use than showing it.
2865
+ /* @__PURE__ */ React.createElement("div", { style: { marginTop: 2, fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--faint)" } }, T && T.assistantSub || "Local \xB7 APP")
2866
+ ) : phone ? /* @__PURE__ */ React.createElement("div", { style: { marginTop: 2, display: "flex" } }, /* @__PURE__ */ React.createElement(RouteTag, { peer })) : /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 2 } }, /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: peer.userId, title: peer.userId }, shortKey(peer.userId, 10, 6)), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), /* @__PURE__ */ React.createElement("button", { onClick: () => onOpenNet(peer), title: T && T.openNet || "Network view", style: { background: "none", border: "none", cursor: "pointer", padding: 0, color: "var(--accent)", display: "inline-flex", alignItems: "center" } }, /* @__PURE__ */ React.createElement(Icon, { name: "network", size: 12, stroke: 2 })), /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: peer.ip, title: peer.ip }, peer.ip), peer.address && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), /* @__PURE__ */ React.createElement(
2539
2867
  Mono,
2540
2868
  {
2541
2869
  size: 11.5,
@@ -2553,7 +2881,7 @@ ${peer.address}`
2553
2881
  title: muted ? T && T.unmuteSound || "Turn sound back on for this chat" : T && T.muteSound || "Mute sound for this chat",
2554
2882
  onClick: () => onToggleMute(peer.id)
2555
2883
  }
2556
- ), onCall && /* @__PURE__ */ React.createElement(Btn, { icon: "phone", title: T && T.audioCall || "Audio call", onClick: () => onCall(peer.userId, false) }), onCall && /* @__PURE__ */ React.createElement(Btn, { icon: "video", title: T && T.videoCall || "Video call", onClick: () => onCall(peer.userId, true) }), onSendRtcFile && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
2884
+ ), onCall && !peer.app && /* @__PURE__ */ React.createElement(Btn, { icon: "phone", title: T && T.audioCall || "Audio call", onClick: () => onCall(peer.userId, false) }), onCall && !peer.app && /* @__PURE__ */ React.createElement(Btn, { icon: "video", title: T && T.videoCall || "Video call", onClick: () => onCall(peer.userId, true) }), onSendRtcFile && !peer.app && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
2557
2885
  "input",
2558
2886
  {
2559
2887
  ref: rtcFileRef,
@@ -2567,19 +2895,19 @@ ${peer.address}`
2567
2895
  ), !phone && /* @__PURE__ */ React.createElement(Btn, { icon: "file", title: T && T.sendWebrtcFile || "Send file via WebRTC", onClick: () => rtcFileRef.current && rtcFileRef.current.click() })), /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React.createElement(Btn, { icon: "more", onClick: () => setMenu((v) => !v) }), menu && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { onClick: () => setMenu(false), style: { position: "fixed", inset: 0, zIndex: 40 } }), /* @__PURE__ */ React.createElement("div", { style: { position: "absolute", right: 0, top: 36, zIndex: 50, width: 180, background: "var(--panel-2)", border: "1px solid var(--line)", borderRadius: 9, padding: 6, boxShadow: "0 14px 40px rgba(0,0,0,0.4)" } }, phone && onToggleMute && /* @__PURE__ */ React.createElement(MenuItem, { icon: muted ? "bell" : "bellOff", label: muted ? T && T.unmuteSound || "Turn sound back on" : T && T.muteSound || "Mute sound", onClick: () => {
2568
2896
  setMenu(false);
2569
2897
  onToggleMute(peer.id);
2570
- } }), phone && onSendRtcFile && /* @__PURE__ */ React.createElement(MenuItem, { icon: "file", label: T && T.sendWebrtcFile || "Send file via WebRTC", onClick: () => {
2898
+ } }), phone && onSendRtcFile && !peer.app && /* @__PURE__ */ React.createElement(MenuItem, { icon: "file", label: T && T.sendWebrtcFile || "Send file via WebRTC", onClick: () => {
2571
2899
  setMenu(false);
2572
2900
  rtcFileRef.current && rtcFileRef.current.click();
2573
- } }), phone && /* @__PURE__ */ React.createElement(MenuItem, { icon: "network", label: T && T.openNet || "Network view", onClick: () => {
2901
+ } }), phone && !peer.app && /* @__PURE__ */ React.createElement(MenuItem, { icon: "network", label: T && T.openNet || "Network view", onClick: () => {
2574
2902
  setMenu(false);
2575
2903
  onOpenNet(peer);
2576
- } }), /* @__PURE__ */ React.createElement(MenuItem, { icon: "hash", label: T.alias, onClick: () => {
2904
+ } }), !peer.app && /* @__PURE__ */ React.createElement(MenuItem, { icon: "hash", label: T.alias, onClick: () => {
2577
2905
  setMenu(false);
2578
2906
  onAlias(peer);
2579
2907
  } }), /* @__PURE__ */ React.createElement(MenuItem, { icon: "check", label: T.selectDelete || "Select & delete", onClick: () => {
2580
2908
  setMenu(false);
2581
2909
  setSelMode(true);
2582
- } }), /* @__PURE__ */ React.createElement("div", { style: { height: 1, background: "var(--line)", margin: "5px 4px" } }), /* @__PURE__ */ React.createElement(MenuItem, { icon: "trash", label: T.remove, danger: true, onClick: () => {
2910
+ } }), !peer.app && /* @__PURE__ */ React.createElement("div", { style: { height: 1, background: "var(--line)", margin: "5px 4px" } }), !peer.app && /* @__PURE__ */ React.createElement(MenuItem, { icon: "trash", label: T.remove, danger: true, onClick: () => {
2583
2911
  setMenu(false);
2584
2912
  onRemove(peer);
2585
2913
  } }))))),
@@ -2653,7 +2981,7 @@ ${peer.address}`
2653
2981
  e.target.value = "";
2654
2982
  }
2655
2983
  }
2656
- ), /* @__PURE__ */ React.createElement(Btn, { icon: "paperclip", title: T.sendFile, onClick: () => fileRef.current && fileRef.current.click() }), /* @__PURE__ */ React.createElement(
2984
+ ), !peer.app && /* @__PURE__ */ React.createElement(Btn, { icon: "paperclip", title: T.sendFile, onClick: () => fileRef.current && fileRef.current.click() }), /* @__PURE__ */ React.createElement(
2657
2985
  "input",
2658
2986
  {
2659
2987
  value: draft,
@@ -5524,6 +5852,7 @@ ${peer.address}`
5524
5852
  autoAcceptLabel: "Auto-accept friend requests",
5525
5853
  autoAcceptSub: "Off: requests wait in the sidebar for your approval.",
5526
5854
  groupSenderUnknown: "The group relays only this display name. Register Beagle as a group agent (/agent add <beagle address> in the group) to receive full member identities you can add as friends.",
5855
+ assistantSub: "Local \xB7 APP",
5527
5856
  loadEarlier: "Load earlier messages",
5528
5857
  loadingEarlier: "Loading\u2026",
5529
5858
  pendingFriend: "Waiting for them to accept your friend request \u2014 messages will queue and deliver once they do.",
@@ -5718,6 +6047,7 @@ ${peer.address}`
5718
6047
  autoAcceptLabel: "\u81EA\u52A8\u63A5\u53D7\u597D\u53CB\u8BF7\u6C42",
5719
6048
  autoAcceptSub: "\u5173\u95ED\u540E\uFF0C\u597D\u53CB\u8BF7\u6C42\u4F1A\u7B49\u5F85\u4F60\u5728\u4FA7\u8FB9\u680F\u624B\u52A8\u786E\u8BA4\u3002",
5720
6049
  groupSenderUnknown: "\u7FA4\u670D\u52A1\u53EA\u8F6C\u53D1\u4E86\u663E\u793A\u540D\uFF0C\u62FF\u4E0D\u5230\u8BE5\u6210\u5458\u7684\u5B8C\u6574\u8EAB\u4EFD\u3002\u5728\u7FA4\u91CC\u6267\u884C /agent add <beagle \u5730\u5740> \u628A Beagle \u6CE8\u518C\u4E3A\u7FA4 agent \u540E\uFF0C\u5373\u53EF\u6536\u5230\u5B8C\u6574\u8EAB\u4EFD\u5E76\u4E00\u952E\u52A0\u597D\u53CB\u3002",
6050
+ assistantSub: "\u672C\u673A \xB7 APP",
5721
6051
  loadEarlier: "\u52A0\u8F7D\u66F4\u65E9\u7684\u6D88\u606F",
5722
6052
  loadingEarlier: "\u52A0\u8F7D\u4E2D\u2026",
5723
6053
  pendingFriend: "\u7B49\u5F85\u5BF9\u65B9\u63A5\u53D7\u597D\u53CB\u8BF7\u6C42 \u2014 \u6D88\u606F\u4F1A\u5148\u6392\u961F\uFF0C\u5BF9\u65B9\u63A5\u53D7\u540E\u81EA\u52A8\u9001\u8FBE\u3002",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/beagle",
3
- "version": "0.1.107",
3
+ "version": "0.1.109",
4
4
  "description": "Beagle — P2P chat, file transfer and calls for regular users, on the Decent Network. No admin privilege required.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -35,7 +35,7 @@
35
35
  "yargs": "^17.7.2"
36
36
  },
37
37
  "devDependencies": {
38
- "@decentnetwork/beagle-ui": "^0.2.21",
38
+ "@decentnetwork/beagle-ui": "^0.2.23",
39
39
  "@types/js-yaml": "^4.0.9",
40
40
  "@types/node": "^20.11.0",
41
41
  "@types/yargs": "^17.0.32",