dsh-cherry-bridge 0.1.10 → 0.2.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.
@@ -269,12 +269,19 @@ http://127.0.0.1:23023/cherry-remote/api/scope`(宿主换过端口就换端口
269
269
  - [ ] 该端点同样受信任 fence 约束:非回环 Host、非同源 Origin、`sec-fetch-site: cross-site`
270
270
  一律 403;无 Origin 的非浏览器客户端放行。
271
271
  - [ ] 全流程日志不含 token / invitationSecret / accessToken / 消息文本 / part 文本。
272
- - [ ] **上游版本核验**(桌面端为 2.1.4–2.1.6 或已比对过的更新版本;依据见 README
273
- 「与 Cherry Studio 版本的对应关系」):
272
+ - [ ] **上游版本核验**(桌面端为 2.1.4–2.1.8 或已比对过的更新版本;依据见
273
+ src/AGENTS.md「Protocol basis」):
274
274
  - [ ] 桌面端**升级后无需重新配对**:设备档案直接连上,`desktopIdentity` 未变。
275
275
  - [ ] 桌面端为 2.1.5+ 且会话里有**来自手机上传的附件**时:该 part 为 `kind:"file"`
276
276
  并带 `name`/`mediaType`/`byteLength`,`content` 为空,且**没有**任何
277
277
  `agent.content.read` 调用(一次都不该有——那些字节是文件,不是文本)。
278
+ - [ ] 桌面端为 2.1.7+ 且会话里有**未上传的附件**(如粘贴的图片)时:该 part 为
279
+ `kind:"data"` 且 `name:"file"`,元数据以 JSON 形式放在 `content.text` 里
280
+ (`{"mediaType":…,"filename":…}`)→ 读取报告 `name`(来自 `filename`,
281
+ 为 `null` 时不报)与 `mediaType`;该对象的每个键都是这两个上报字段时
282
+ `content` 为空,带其他键时保留原始文本;inline(≤4,096 字符)时没有任何
283
+ `agent.content.read` 调用,超过时恰有一次内容读取。其他 `name` 的
284
+ `data` part(任务事件等)不是附件,原样透传。
278
285
  - [ ] `cherry_send` 传空白文本(如 `" "`)→ 本地返回 `text-empty`,对端未收到
279
286
  `agent.messages.send`。
280
287
  - [ ] 桌面端广播了本插件不认识的支持字段时,`cherry_devices` 的 limits 投影仍只有
package/lib/client.js CHANGED
@@ -36,6 +36,10 @@ window.__ModuleLoader__.load({
36
36
  "device.field.name": "设备名称",
37
37
  "device.field.hosts": "主机地址",
38
38
  "device.hosts.hint": "多个候选地址用逗号分隔(整体替换)",
39
+ "device.field.ports": "每地址端口",
40
+ "device.ports.hint": "部分候选地址的端口与上方的端口不同;在「地址维护」里改动端口会把所有候选地址改为同一端口。",
41
+ "device.ports.current": "当前端口映射:",
42
+ "device.port.hint": "保持当前值会保留各地址自己的端口;改成新值会把所有候选地址重指到该端口。",
39
43
  "device.save.name": "保存名称",
40
44
  "device.save.addresses": "保存地址",
41
45
  "device.remove.confirm": "确认移除",
@@ -136,6 +140,10 @@ window.__ModuleLoader__.load({
136
140
  "device.field.name": "Device name",
137
141
  "device.field.hosts": "Host addresses",
138
142
  "device.hosts.hint": "Separate candidate addresses with commas (the whole list is replaced)",
143
+ "device.field.ports": "Ports per address",
144
+ "device.ports.hint": "Some candidate addresses dial a different port than the one above; changing the port in address maintenance moves every candidate address to that one port.",
145
+ "device.ports.current": "Current port map:",
146
+ "device.port.hint": "Keeping the current value preserves each address’s own port; a new value re-points every candidate address.",
139
147
  "device.save.name": "Save name",
140
148
  "device.save.addresses": "Save addresses",
141
149
  "device.remove.confirm": "Confirm removal",
@@ -516,6 +524,20 @@ window.__ModuleLoader__.load({
516
524
  function parseHosts(raw) {
517
525
  return raw.split(",").map((entry) => entry.trim()).filter((entry) => entry !== "");
518
526
  }
527
+ /** The dial port for one host: its own stored port, else the record-level one. */
528
+ function dialPort(device, host) {
529
+ return device.ports?.[host] ?? device.port;
530
+ }
531
+ /**
532
+ * `host:port` as one readable address.
533
+ *
534
+ * An IPv6 literal contains colons, so `fe80::1:4343` reads as an ambiguous
535
+ * host; stored hosts are bare (`normalizeHost` strips the brackets), so the
536
+ * brackets are added here, for display only.
537
+ */
538
+ function formatAddress(host, port) {
539
+ return host.includes(":") ? `[${host}]:${port}` : `${host}:${port}`;
540
+ }
519
541
  function DeviceCard({ device, state, onSaved, onRemoved }) {
520
542
  const t = useT();
521
543
  const [editor, setEditor] = (0, react.useState)("none");
@@ -615,6 +637,8 @@ window.__ModuleLoader__.load({
615
637
  onRemoved(device.deviceId);
616
638
  });
617
639
  };
640
+ const mixedPorts = device.hosts.some((host) => dialPort(device, host) !== device.port);
641
+ const addressList = device.hosts.map((host) => formatAddress(host, dialPort(device, host))).join(", ");
618
642
  const label = state === void 0 ? t("device.state.unknown") : t(STATE_KEYS[state] ?? state);
619
643
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
620
644
  className: "cherry-card",
@@ -681,6 +705,10 @@ window.__ModuleLoader__.load({
681
705
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: device.hosts.join(", ") }),
682
706
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("device.field.port") }),
683
707
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: device.port }),
708
+ mixedPorts && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("device.field.ports") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
709
+ className: "cherry-mono",
710
+ children: addressList
711
+ })] }),
684
712
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("device.field.id") }),
685
713
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
686
714
  className: "cherry-mono",
@@ -688,6 +716,10 @@ window.__ModuleLoader__.load({
688
716
  })
689
717
  ]
690
718
  }),
719
+ mixedPorts && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
720
+ className: "cherry-hint",
721
+ children: t("device.ports.hint")
722
+ }),
691
723
  editor === "rename" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
692
724
  className: "cherry-inline-form",
693
725
  onSubmit: saveName,
@@ -725,6 +757,17 @@ window.__ModuleLoader__.load({
725
757
  className: "cherry-hint",
726
758
  children: t("device.hosts.hint")
727
759
  }),
760
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
761
+ className: "cherry-hint",
762
+ children: [
763
+ t("device.ports.current"),
764
+ " ",
765
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
766
+ className: "cherry-mono",
767
+ children: addressList
768
+ })
769
+ ]
770
+ }),
728
771
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
729
772
  htmlFor: `port-${device.deviceId}`,
730
773
  children: t("device.field.port")
@@ -735,6 +778,10 @@ window.__ModuleLoader__.load({
735
778
  value: portText,
736
779
  onChange: (event) => setPortText(event.target.value)
737
780
  }),
781
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
782
+ className: "cherry-hint",
783
+ children: t("device.port.hint")
784
+ }),
738
785
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
739
786
  type: "submit",
740
787
  disabled: busy,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-cherry-bridge",
3
- "version": "0.1.10",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "packageManager": "npm@10.9.9",
@@ -65,8 +65,9 @@
65
65
  "prepublishOnly": "npm run typecheck && npm test && npm run build && npm run build:client && npm pack --dry-run"
66
66
  },
67
67
  "dependencies": {
68
- "@cherrystudio/remote-protocol": "0.5.0",
69
- "@cherrystudio/remote-transport": "0.2.1",
68
+ "@cherrystudio/remote-protocol": "0.7.0",
69
+ "@cherrystudio/remote-transport": "0.2.5",
70
+ "@libp2p/peer-id": "6.0.15",
70
71
  "ws": "8.22.0",
71
72
  "zod": "4.3.4"
72
73
  },