@decentnetwork/beagle 0.1.108 → 0.1.110

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 +434 -40
  2. package/package.json +2 -2
@@ -1,4 +1,4 @@
1
- window.__DK_UI_VERSION="0.1.108";
1
+ window.__DK_UI_VERSION="0.1.110";
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.108";
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.108";
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.108";
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.108";
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.108";
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.108";
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.108";
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.108";
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.108";
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.108";
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,148 @@ window.__DK_UI_VERSION="0.1.108";
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: "5SxEBHAx49Hknq1VzrA1HDtZHjW31vetX3iMEi4P6pK3",
874
+ name: "gfax118",
875
+ kind: "node",
876
+ role: "server"
877
+ },
878
+ {
879
+ userid: "72a4A7awtn6BqZnBt3eNhZH9MdYXRkM6Ht5HG2sqoiKR",
880
+ name: "nv4090-wsl",
881
+ kind: "node",
882
+ role: "GPU node"
883
+ },
884
+ {
885
+ userid: "6riqxxw18vjwXZEEtFUxbgNBMMe1dcv2kVHqVRQdihjE",
886
+ name: "node-3145",
887
+ kind: "node",
888
+ role: "GPU node (4090w)"
889
+ },
890
+ {
891
+ userid: "6D1PLSVqbSpcxnDMAd8ZXLWycYveeP2hU4g3igDQEqA7",
892
+ name: "node-5123",
893
+ kind: "node",
894
+ role: "node"
895
+ },
896
+ {
897
+ userid: "5EU9Ee3Vai1XwTr4pGaJFt4wmNVPUoYnJsdBM9LwNhrJ",
898
+ name: "node-2998",
899
+ kind: "node",
900
+ role: "node"
901
+ },
902
+ {
903
+ userid: "GujioyKQRucQgw8mwMPNWtYTdNdrooCuFq3R1D4RqjCW",
904
+ name: "beagle-help",
905
+ kind: "agent",
906
+ role: "product support"
907
+ },
908
+ {
909
+ userid: "FhbohSLrj5UjdyFKCEYNeEWAPq3QD9hRg6hsso5ipag2",
910
+ name: "help.beagles.eth",
911
+ kind: "agent",
912
+ role: "bridge agent"
913
+ },
914
+ {
915
+ userid: "9EaFGWuXH2UfREzYMkPPitzjoH6E8tvey4JvqTKtHFJh",
916
+ name: "openclaw",
917
+ kind: "agent",
918
+ role: "OpenClaw agent"
919
+ },
920
+ {
921
+ userid: "6z2o8ojPVjFGRzcffUdd5btvDYAt4tLuwJ8j1jvMBU8p",
922
+ name: "ClawAlan",
923
+ kind: "agent",
924
+ role: "OpenClaw agent on snoopy"
925
+ },
926
+ {
927
+ userid: "G7s3YjVfNLTY5pKBd83qQLAdzDddS7ZstsfrUySwK2Cb",
928
+ name: "interview",
929
+ kind: "agent",
930
+ role: "interview"
931
+ },
932
+ {
933
+ userid: "6jbXjrUWJ4zYvG8BDLnu8KVpoL7rhsdHYSwWER6WTfdt",
934
+ name: "interview editor",
935
+ kind: "agent",
936
+ role: "interview"
937
+ },
938
+ {
939
+ userid: "8BGemh6uqmh4X7KpnPVMeMSU8hZwAc6ZDo4MBRMFrq2L",
940
+ name: "readyplayer",
941
+ kind: "agent",
942
+ role: "readyplayer"
943
+ },
944
+ {
945
+ userid: "Ev1XmWRwtjQf8n33wgTwEhzXnYEyfqTyanehuDE6nczF",
946
+ name: "ReadyPlayer membership manager",
947
+ kind: "agent",
948
+ role: "readyplayer"
949
+ },
950
+ {
951
+ userid: "7xLPH2kjk7Nn8GrsAt7TYwa6b1WMPLkC2XRx7BzhiXYT",
952
+ name: "ai-btc-trader",
953
+ kind: "agent",
954
+ role: "btc signals (245)"
955
+ }
956
+ ]
957
+ };
677
958
  var ASSISTANT_ID = "__assistant__";
678
959
  var DAY_MS = 864e5;
679
960
  var RECENT_DAYS = 14;
@@ -688,11 +969,11 @@ window.__DK_UI_VERSION="0.1.108";
688
969
  if (!parts.length)
689
970
  return null;
690
971
  const head = parts[0].toLowerCase();
691
- if (head === "friends") {
972
+ if (head === "friends" || head === "nodes") {
692
973
  const sub = (parts[1] || "").toLowerCase();
693
974
  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" };
975
+ return { ok: true, name: `${head}-${sub}`, args: parts.slice(2) };
976
+ return { ok: false, name: head, args: parts.slice(1), error: `${head}-subcommand` };
696
977
  }
697
978
  return { ok: true, name: head, args: parts.slice(1) };
698
979
  }
@@ -719,6 +1000,30 @@ window.__DK_UI_VERSION="0.1.108";
719
1000
  }
720
1001
  return { filters, silentDays, unknown };
721
1002
  }
1003
+ function servicePeerMap(list) {
1004
+ const map = /* @__PURE__ */ new Map();
1005
+ const peers = list && list.peers || [];
1006
+ for (const p of peers) {
1007
+ if (p && typeof p.userid === "string" && p.userid) {
1008
+ map.set(p.userid, { kind: p.kind === "agent" ? "agent" : "node", role: p.role || "", name: p.name || "" });
1009
+ }
1010
+ }
1011
+ return map;
1012
+ }
1013
+ function partitionPeers(friends, svcMap) {
1014
+ const people = [];
1015
+ const nodes = [];
1016
+ for (const f of friends != null ? friends : []) {
1017
+ if (!f)
1018
+ continue;
1019
+ const svc = svcMap && svcMap.get(f.userid);
1020
+ if (svc)
1021
+ nodes.push(Object.assign({}, f, { kind: svc.kind, role: svc.role, service: true }));
1022
+ else
1023
+ people.push(f);
1024
+ }
1025
+ return { people, nodes };
1026
+ }
722
1027
  var acceptedOrRequestedAt = (f) => f.acceptedAt || f.requestedAt || 0;
723
1028
  function isRecent(friend, now, recentDays = RECENT_DAYS) {
724
1029
  const at = acceptedOrRequestedAt(friend);
@@ -787,6 +1092,9 @@ window.__DK_UI_VERSION="0.1.108";
787
1092
  return { match: partial[0], candidates: partial };
788
1093
  return { match: null, candidates: partial };
789
1094
  }
1095
+ function loadServicePeers() {
1096
+ return service_peers_default;
1097
+ }
790
1098
  var ASSISTANT_PEER = {
791
1099
  id: ASSISTANT_ID,
792
1100
  userId: ASSISTANT_ID,
@@ -829,7 +1137,10 @@ window.__DK_UI_VERSION="0.1.108";
829
1137
  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
1138
  }
831
1139
  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");
1140
+ return rows.map((f) => {
1141
+ const what = f.service ? ` \`${f.kind}${f.role ? " \xB7 " + f.role : ""}\`` : "";
1142
+ 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}`;
1143
+ }).join("\n");
833
1144
  }
834
1145
  function helpText(lang) {
835
1146
  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 +1209,15 @@ ${tail}`;
898
1209
  }, [lang]);
899
1210
  const fetchFriends = async () => {
900
1211
  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 || "" };
1212
+ const { people, nodes } = partitionPeers(r.friends || [], servicePeerMap(loadServicePeers()));
1213
+ return {
1214
+ friends: people,
1215
+ nodes,
1216
+ all: r.friends || [],
1217
+ historySince: r.historySince || null,
1218
+ now: r.now || Date.now(),
1219
+ ver: r.beagleVer || ""
1220
+ };
902
1221
  };
903
1222
  const listReply = (rows, skipped, data, title) => {
904
1223
  if (!rows.length) {
@@ -930,7 +1249,7 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
930
1249
  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
1250
  }
932
1251
  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`."));
1252
+ 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
1253
  }
935
1254
  try {
936
1255
  switch (cmd.name) {
@@ -938,37 +1257,48 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
938
1257
  return append(asMsg("them", helpText(lang)));
939
1258
  case "status": {
940
1259
  const d = await fetchFriends();
941
- const pending = d.friends.filter((f) => f.pending).length;
1260
+ const pending = d.all.filter((f) => f.pending).length;
1261
+ const nodeCount = d.nodes.filter((n) => n.kind === "node").length;
1262
+ const agentCount = d.nodes.length - nodeCount;
942
1263
  return append(asMsg("them", lang === "zh" ? `**\u72B6\u6001**
943
1264
 
944
1265
  - \u597D\u53CB\uFF1A${d.friends.length}
1266
+ - \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
1267
  - \u5F85\u5BF9\u65B9\u63A5\u53D7\uFF1A${pending}
946
1268
  - \u672C\u673A\u5386\u53F2\u8D77\u81EA\uFF1A${fmtDate(d.historySince, lang)}
947
1269
  - \u7248\u672C\uFF1A${d.ver}` : `**Status**
948
1270
 
949
1271
  - Friends: ${d.friends.length}
1272
+ - Service peers: ${d.nodes.length} (${nodeCount} node${nodeCount === 1 ? "" : "s"} \xB7 ${agentCount} agent${agentCount === 1 ? "" : "s"}) \u2014 see \`/nodes list\`
950
1273
  - Waiting to be accepted: ${pending}
951
1274
  - History on this device since: ${fmtDate(d.historySince, lang)}
952
1275
  - Version: ${d.ver}`));
953
1276
  }
1277
+ case "nodes-list":
954
1278
  case "friends-list": {
1279
+ const nodesMode = cmd.name === "nodes-list";
955
1280
  const { filters, silentDays, unknown } = parseFilters(cmd.args);
956
1281
  if (unknown.length) {
957
1282
  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
1283
  }
959
1284
  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}`;
1285
+ const pool = nodesMode ? d.nodes : d.friends;
1286
+ const { matched, skippedRecent } = selectFriends(pool, filters, { now: d.now, silentDays });
1287
+ 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}`;
1288
+ 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
1289
  return append(asMsg("them", listReply(matched, skippedRecent, d, title)));
963
1290
  }
1291
+ case "nodes-remove":
964
1292
  case "friends-remove": {
1293
+ const nodesMode = cmd.name === "nodes-remove";
965
1294
  const { filters, silentDays, unknown } = parseFilters(cmd.args);
966
1295
  if (unknown.length || !filters.length) {
967
1296
  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
1297
  }
969
1298
  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`;
1299
+ const { matched, skippedRecent } = selectFriends(nodesMode ? d.nodes : d.friends, filters, { now: d.now, silentDays });
1300
+ 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"}`;
1301
+ 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
1302
  return append(asMsg(
973
1303
  "them",
974
1304
  listReply(matched, skippedRecent, d, title),
@@ -980,7 +1310,7 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
980
1310
  if (!q)
981
1311
  return append(asMsg("them", lang === "zh" ? "\u7528\u6CD5\uFF1A`/find <\u6587\u5B57>`" : "Usage: `/find <text>`"));
982
1312
  const d = await fetchFriends();
983
- const rows = findFriends(d.friends, q);
1313
+ const rows = findFriends(d.friends.concat(d.nodes), q);
984
1314
  const title = lang === "zh" ? `**"${q}"** \u2014 ${rows.length} \u4F4D` : `**"${q}"** \u2014 ${rows.length} found`;
985
1315
  return append(asMsg("them", listReply(rows, [], d, title)));
986
1316
  }
@@ -990,7 +1320,7 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
990
1320
  if (!tok || !alias)
991
1321
  return append(asMsg("them", lang === "zh" ? "\u7528\u6CD5\uFF1A`/rename <\u597D\u53CB> <\u65B0\u540D\u5B57>`" : "Usage: `/rename <friend> <new name>`"));
992
1322
  const d = await fetchFriends();
993
- const { match, candidates } = resolveFriend(d.friends, tok);
1323
+ const { match, candidates } = resolveFriend(d.friends.concat(d.nodes), tok);
994
1324
  if (!match)
995
1325
  return append(asMsg("them", candidates.length ? ambiguous(candidates) : notFound(tok)));
996
1326
  const was = nameOf(match);
@@ -1005,7 +1335,7 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
1005
1335
  if (!tok)
1006
1336
  return append(asMsg("them", `${lang === "zh" ? "\u7528\u6CD5" : "Usage"}: \`/${cmd.name} <friend>\``));
1007
1337
  const d = await fetchFriends();
1008
- const { match, candidates } = resolveFriend(d.friends, tok);
1338
+ const { match, candidates } = resolveFriend(d.friends.concat(d.nodes), tok);
1009
1339
  if (!match)
1010
1340
  return append(asMsg("them", candidates.length ? ambiguous(candidates) : notFound(tok)));
1011
1341
  const isMuted = (muted || []).includes(match.userid);
@@ -1053,6 +1383,8 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
1053
1383
  }, [append, lang, onReloadFriends]);
1054
1384
  return { thread, handle, removeFriends };
1055
1385
  }
1386
+ var DK_CMD_HIST_KEY = "beagle.assistant.history";
1387
+ var DK_CMD_HIST_MAX = 50;
1056
1388
  function RequestsBlock({ T, requests, onAct }) {
1057
1389
  const [acted, setActed] = React.useState({});
1058
1390
  const [failed, setFailed] = React.useState({});
@@ -2454,6 +2786,59 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
2454
2786
  setSelMode(false);
2455
2787
  };
2456
2788
  const [draft, setDraft] = React.useState("");
2789
+ const [hist, setHist] = React.useState([]);
2790
+ const histIdx = React.useRef(null);
2791
+ const histStash = React.useRef("");
2792
+ React.useEffect(() => {
2793
+ histIdx.current = null;
2794
+ histStash.current = "";
2795
+ if (!peer.app) {
2796
+ setHist([]);
2797
+ return;
2798
+ }
2799
+ try {
2800
+ setHist(JSON.parse(localStorage.getItem(DK_CMD_HIST_KEY) || "[]"));
2801
+ } catch (e) {
2802
+ setHist([]);
2803
+ }
2804
+ }, [peer.id, peer.app]);
2805
+ const rememberCommand = (text) => {
2806
+ setHist((h) => {
2807
+ const next = (h[h.length - 1] === text ? h : h.concat(text)).slice(-DK_CMD_HIST_MAX);
2808
+ if (peer.app) {
2809
+ try {
2810
+ localStorage.setItem(DK_CMD_HIST_KEY, JSON.stringify(next));
2811
+ } catch (e) {
2812
+ }
2813
+ }
2814
+ return next;
2815
+ });
2816
+ histIdx.current = null;
2817
+ histStash.current = "";
2818
+ };
2819
+ const walkHistory = (dir) => {
2820
+ if (!hist.length)
2821
+ return false;
2822
+ let i = histIdx.current;
2823
+ if (i === null) {
2824
+ if (dir > 0)
2825
+ return false;
2826
+ histStash.current = draft;
2827
+ i = hist.length - 1;
2828
+ } else {
2829
+ i += dir;
2830
+ if (i >= hist.length) {
2831
+ histIdx.current = null;
2832
+ setDraft(histStash.current);
2833
+ return true;
2834
+ }
2835
+ if (i < 0)
2836
+ i = 0;
2837
+ }
2838
+ histIdx.current = i;
2839
+ setDraft(hist[i]);
2840
+ return true;
2841
+ };
2457
2842
  const [dragOver, setDragOver] = React.useState(false);
2458
2843
  const [sending, setSending] = React.useState(null);
2459
2844
  const [theater, setTheater] = React.useState(null);
@@ -2476,6 +2861,7 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
2476
2861
  const sendDraft = () => {
2477
2862
  if (draft.trim()) {
2478
2863
  followScrollRef.current = true;
2864
+ rememberCommand(draft.trim());
2479
2865
  onSend(draft);
2480
2866
  setDraft("");
2481
2867
  requestAnimationFrame(scrollToBottom);
@@ -2661,11 +3047,19 @@ ${peer.address}`
2661
3047
  "input",
2662
3048
  {
2663
3049
  value: draft,
2664
- onChange: (e) => setDraft(e.target.value),
3050
+ onChange: (e) => {
3051
+ histIdx.current = null;
3052
+ setDraft(e.target.value);
3053
+ },
2665
3054
  onKeyDown: (e) => {
2666
3055
  if (e.key === "Enter" && !e.shiftKey) {
2667
3056
  e.preventDefault();
2668
3057
  sendDraft();
3058
+ return;
3059
+ }
3060
+ if (e.key === "ArrowUp" || e.key === "ArrowDown") {
3061
+ if (walkHistory(e.key === "ArrowUp" ? -1 : 1))
3062
+ e.preventDefault();
2669
3063
  }
2670
3064
  },
2671
3065
  placeholder: phone ? `${T.message}\u2026` : `${T.message} ${peer.alias || shortKey(peer.userId, 6, 4)}\u2026`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/beagle",
3
- "version": "0.1.108",
3
+ "version": "0.1.110",
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.22",
38
+ "@decentnetwork/beagle-ui": "^0.2.24",
39
39
  "@types/js-yaml": "^4.0.9",
40
40
  "@types/node": "^20.11.0",
41
41
  "@types/yargs": "^17.0.32",